Skip to content
Merged
4 changes: 0 additions & 4 deletions packages/wallet-service/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,6 @@ functions:
method: get
cors: true
authorizer: ${self:custom.authorizer.walletBearer}
request:
parameters:
paths:
index: false
Comment on lines -327 to -330
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would only need this block if we wanted to enforce the presence of a specific query in api gateway, which is not the case

warmup:
walletWarmer:
enabled: true
Expand Down
8 changes: 4 additions & 4 deletions packages/wallet-service/src/api/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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) => ({
Expand Down
12 changes: 6 additions & 6 deletions packages/wallet-service/src/api/healthcheck.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
56 changes: 28 additions & 28 deletions packages/wallet-service/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1201,7 +1201,7 @@ export const updateAddressLockedBalance = async (
)
WHERE \`address\` = ?
AND \`token_id\` = ?`,
[address, token, address, token]);
[address, token, address, token]);
}
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1418,7 +1418,7 @@ export const getWalletTokens = async (
);

for (const result of results) {
tokenList.push(<string> result.token_id);
tokenList.push(<string>result.token_id);
}

return tokenList;
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -2074,7 +2074,7 @@ export const markUtxosAsVoided = async (
UPDATE \`tx_output\`
SET \`voided\` = TRUE
WHERE \`tx_id\` IN (?)`,
[txIds]);
[txIds]);
};

/**
Expand Down Expand Up @@ -2812,15 +2812,15 @@ export const existsPushDevice = async (
mysql: ServerlessMysql,
deviceId: string,
walletId: string,
) : Promise<boolean> => {
): Promise<boolean> => {
const [{ count }] = await mysql.query(
`
SELECT COUNT(1) as \`count\`
FROM \`push_devices\` pd
WHERE device_id = ?
AND wallet_id = ?`,
[deviceId, walletId],
) as unknown as Array<{count}>;
) as unknown as Array<{ count }>;

return count > 0;
};
Expand All @@ -2840,7 +2840,7 @@ export const registerPushDevice = async (
enablePush: boolean,
enableShowAmounts: boolean,
},
) : Promise<void> => {
): Promise<void> => {
await mysql.query(
`
INSERT
Expand Down Expand Up @@ -2889,7 +2889,7 @@ export const updatePushDevice = async (
enablePush: boolean,
enableShowAmounts: boolean,
},
) : Promise<void> => {
): Promise<void> => {
await mysql.query(
`
UPDATE \`push_devices\`
Expand All @@ -2912,7 +2912,7 @@ export const unregisterPushDevice = async (
mysql: ServerlessMysql,
deviceId: string,
walletId?: string,
) : Promise<void> => {
): Promise<void> => {
if (walletId) {
await mysql.query(
`
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -2995,7 +2995,7 @@ export const getTransactionById = async (
export const existsWallet = async (
mysql: ServerlessMysql,
walletId: string,
) : Promise<boolean> => {
): Promise<boolean> => {
const [{ count }] = (await mysql.query(
`
SELECT COUNT(1) as \`count\`
Expand All @@ -3016,15 +3016,15 @@ export const existsWallet = async (
export const getPushDevice = async (
mysql: ServerlessMysql,
deviceId: string,
) : Promise<PushDevice|null> => {
): Promise<PushDevice | null> => {
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;
Expand All @@ -3049,7 +3049,7 @@ export const getPushDevice = async (
export const getPushDeviceSettingsList = async (
mysql: ServerlessMysql,
walletIdList: string[],
) : Promise<PushDeviceSettings[]> => {
): Promise<PushDeviceSettings[]> => {
const pushDeviceSettingsResult = await mysql.query(
`
SELECT wallet_id
Expand All @@ -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,
Expand Down Expand Up @@ -3121,7 +3121,7 @@ export const getTokenSymbols = async (
);

if (results.length === 0) return null;
return results.reduce((prev: Record<string, string>, token: { id: string, symbol: string}) => {
return results.reduce((prev: Record<string, string>, token: { id: string, symbol: string }) => {
// eslint-disable-next-line no-param-reassign
prev[token.id] = token.symbol;
return prev;
Expand Down Expand Up @@ -3176,7 +3176,7 @@ export const getAddressAtIndex = async (
WHERE \`index\` = ?
AND \`wallet_id\` = ?
LIMIT 1`,
[walletId, index],
[index, walletId],
);

if (addresses.length <= 0) {
Expand Down
89 changes: 86 additions & 3 deletions packages/wallet-service/tests/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', {
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -2264,3 +2264,86 @@ 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);
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' });
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);
});