Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: param validation for all RPC methods #29

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
924e14e
feat: added validation for createToken and sendNanoContractTx
andreabadesso Jan 15, 2025
de3b144
feat: added parameter validation for the sign oracle data RPC
andreabadesso Jan 15, 2025
d9cf6ce
feat: added parameter validation for signWithAddress
andreabadesso Jan 15, 2025
906f6c4
feat: added parameter validation for getBalance
andreabadesso Jan 15, 2025
6fd7768
feat: added parameter validation for getAddress RPC
andreabadesso Jan 15, 2025
157bdc0
feat: added parameter validation for getConnectedNetwork
andreabadesso Jan 15, 2025
a7a11b6
feat: parameter validation for getUtxos
andreabadesso Jan 15, 2025
ecb47ea
feat: added parameter validation for signWithAddress
andreabadesso Jan 15, 2025
5ba5526
refactor: amountSmallerThan and BiggerThan should not be negative
andreabadesso Jan 29, 2025
5fcf239
refactor: blueprint_id is nullable
andreabadesso Jan 29, 2025
0c3e6d5
refactor: nullable -> nullish
andreabadesso Feb 24, 2025
bc5d375
refactor: use null as default on zod schema for create Token RPC
andreabadesso Feb 24, 2025
eb4838c
refactor: using zod's .trasform method to change params to camelCase
andreabadesso Feb 24, 2025
4542861
refactor: transforming options into a specific options object
andreabadesso Feb 24, 2025
727404e
refactor: better errors on getAddress
andreabadesso Feb 24, 2025
c42f5d9
refactor: using discriminatedUnion in getAddress RPC
andreabadesso Feb 24, 2025
7b55ca6
refactor: using safeParse instead of wrapping the entire code in a tr…
andreabadesso Feb 24, 2025
26c92d6
refactor: better zod
andreabadesso Feb 25, 2025
b9fceef
refactor: default is already zero
andreabadesso Feb 25, 2025
ecf6825
refactor: stop multiplying options in getUtxos
andreabadesso Feb 25, 2025
2788c52
refactor: using params directly in send nano contract tx
andreabadesso Feb 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 34 additions & 27 deletions packages/hathor-rpc-handler/src/rpcMethods/createToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,31 @@ const createTokenSchema = z.object({
name: z.string().min(1),
symbol: z.string().min(1),
amount: z.number().positive(),
address: z.string().nullable().optional(),
change_address: z.string().nullable().optional(),
create_mint: z.boolean().optional().default(true),
mint_authority_address: z.string().nullable().optional(),
address: z.string().nullish().default(null),
change_address: z.string().nullish().default(null),
create_mint: z.boolean().default(true),
mint_authority_address: z.string().nullish().default(null),
allow_external_mint_authority_address: z.boolean().optional().default(false),
create_melt: z.boolean().optional().default(true),
melt_authority_address: z.string().nullable().optional(),
create_melt: z.boolean().default(true),
melt_authority_address: z.string().nullish().default(null),
allow_external_melt_authority_address: z.boolean().optional().default(false),
data: z.string().array().nullable().optional(),
});
data: z.string().array().nullish().default(null),
}).transform(data => ({
name: data.name,
symbol: data.symbol,
amount: data.amount,
options: {
address: data.address,
changeAddress: data.change_address,
createMint: data.create_mint,
mintAuthorityAddress: data.mint_authority_address,
allowExternalMintAuthorityAddress: data.allow_external_mint_authority_address,
createMelt: data.create_melt,
meltAuthorityAddress: data.melt_authority_address,
allowExternalMeltAuthorityAddress: data.allow_external_melt_authority_address,
data: data.data,
}
}));

/**
* Handles the creation of a new token on the Hathor blockchain.
Expand Down Expand Up @@ -66,7 +81,7 @@ export async function createToken(
try {
const params = createTokenSchema.parse(rpcRequest.params);

if (params.change_address && !await wallet.isAddressMine(params.change_address)) {
if (params.options.changeAddress && !await wallet.isAddressMine(params.options.changeAddress)) {
throw new Error('Change address is not from this wallet');
}

Expand All @@ -82,15 +97,15 @@ export async function createToken(
name: params.name,
symbol: params.symbol,
amount: params.amount,
address: params.address ?? null,
changeAddress: params.change_address ?? null,
createMint: params.create_mint,
mintAuthorityAddress: params.mint_authority_address ?? null,
allowExternalMintAuthorityAddress: params.allow_external_mint_authority_address,
createMelt: params.create_melt,
meltAuthorityAddress: params.melt_authority_address ?? null,
allowExternalMeltAuthorityAddress: params.allow_external_melt_authority_address,
data: params.data ?? null,
address: params.options.address,
changeAddress: params.options.changeAddress,
createMint: params.options.createMint,
mintAuthorityAddress: params.options.mintAuthorityAddress,
allowExternalMintAuthorityAddress: params.options.allowExternalMintAuthorityAddress,
createMelt: params.options.createMelt,
meltAuthorityAddress: params.options.meltAuthorityAddress,
allowExternalMeltAuthorityAddress: params.options.allowExternalMeltAuthorityAddress,
data: params.options.data,
},
};

Expand Down Expand Up @@ -119,15 +134,7 @@ export async function createToken(
params.symbol,
params.amount,
{
changeAddress: params.change_address,
address: params.address,
createMint: params.create_mint,
mintAuthorityAddress: params.mint_authority_address,
allowExternalMintAuthorityAddress: params.allow_external_mint_authority_address,
createMelt: params.create_melt,
meltAuthorityAddress: params.melt_authority_address,
allowExternalMeltAuthorityAddress: params.allow_external_melt_authority_address,
data: params.data,
...params.options,
pinCode: pinCodeResponse.data.pinCode,
}
);
Expand Down
57 changes: 35 additions & 22 deletions packages/hathor-rpc-handler/src/rpcMethods/getAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,38 @@ import { NotImplementedError, PromptRejectedError, InvalidParamsError } from '..
import { validateNetwork } from '../helpers';
import type { AddressInfoObject } from '@hathor/wallet-lib/lib/wallet/types';

const getAddressSchema = z.object({
type: z.enum(['first_empty', 'full_path', 'index', 'client']),
index: z.number().int().nonnegative().optional(),
full_path: z.string().min(1).optional(),
const baseSchema = {
network: z.string().min(1),
}).refine(
(data) => {
if (data.type === 'index') {
return data.index !== undefined;
}
if (data.type === 'full_path') {
return data.full_path !== undefined;
}
return true;
},
{
message: "index is required when type is 'index', full_path is required when type is 'full_path'",
};

const getAddressSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal('first_empty'),
...baseSchema,
}),
z.object({
type: z.literal('full_path'),
full_path: z.string().min(1),
...baseSchema,
}),
z.object({
type: z.literal('index'),
index: z.number().int().nonnegative(),
...baseSchema,
}),
z.object({
type: z.literal('client'),
...baseSchema,
}),
]).transform(data => {
if (data.type === 'full_path') {
return {
...data,
fullPath: data.full_path,
};
}
);
return data;
});

/**
* Gets an address based on the provided rpcRequest and wallet.
Expand All @@ -64,26 +77,26 @@ export async function getAddress(
const params = getAddressSchema.parse(rpcRequest.params);
validateNetwork(wallet, params.network);

let address: string;
let address: string = '';

switch (params.type) {
case 'first_empty':
address = await wallet.getCurrentAddress();
break;
break;
case 'full_path':
throw new NotImplementedError();
case 'index':
address = await wallet.getAddressAtIndex(params.index!);
break;
address = await wallet.getAddressAtIndex(params.index);
break;
case 'client': {
const response = (await promptHandler({
type: TriggerTypes.AddressRequestClientPrompt,
method: rpcRequest.method,
}, requestMetadata)) as AddressRequestClientResponse;

address = response.data.address;
break;
}
break;
}

// We already confirmed with the user and he selected the address he wanted
Expand Down
53 changes: 26 additions & 27 deletions packages/hathor-rpc-handler/src/rpcMethods/getBalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,39 +46,38 @@ export async function getBalance(
requestMetadata: RequestMetadata,
promptHandler: TriggerHandler,
) {
try {
const params = getBalanceSchema.parse(rpcRequest.params);
const parseResult = getBalanceSchema.safeParse(rpcRequest.params);

if (!parseResult.success) {
throw new InvalidParamsError(parseResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '));
}

if (params.addressIndexes) {
throw new NotImplementedError();
}
const params = parseResult.data;

validateNetwork(wallet, params.network);
if (params.addressIndexes) {
throw new NotImplementedError();
}

const balances: GetBalanceObject[] = await Promise.all(
params.tokens.map(token => wallet.getBalance(token)),
);
validateNetwork(wallet, params.network);

const prompt: GetBalanceConfirmationPrompt = {
type: TriggerTypes.GetBalanceConfirmationPrompt,
method: rpcRequest.method,
data: balances
};
const balances: GetBalanceObject[] = await Promise.all(
params.tokens.map(token => wallet.getBalance(token)),
);

const confirmed = await promptHandler(prompt, requestMetadata);
const prompt: GetBalanceConfirmationPrompt = {
type: TriggerTypes.GetBalanceConfirmationPrompt,
method: rpcRequest.method,
data: balances
};

if (!confirmed) {
throw new PromptRejectedError();
}
const confirmed = await promptHandler(prompt, requestMetadata);

return {
type: RpcResponseTypes.GetBalanceResponse,
response: balances,
} as RpcResponse;
} catch (err) {
if (err instanceof z.ZodError) {
throw new InvalidParamsError(err.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '));
}
throw err;
if (!confirmed) {
throw new PromptRejectedError();
}

return {
type: RpcResponseTypes.GetBalanceResponse,
response: balances,
} as RpcResponse;
}
31 changes: 14 additions & 17 deletions packages/hathor-rpc-handler/src/rpcMethods/getConnectedNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,21 @@ export async function getConnectedNetwork(
_requestMetadata: RequestMetadata,
_promptHandler: TriggerHandler,
) {
try {
getConnectedNetworkSchema.parse(rpcRequest);
const parseResult = getConnectedNetworkSchema.safeParse(rpcRequest);

if (!parseResult.success) {
throw new InvalidParamsError(parseResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '));
}

const network: string = wallet.getNetwork();
const network: string = wallet.getNetwork();

const result = {
network,
genesisHash: '', // TODO
};
const result = {
network,
genesisHash: '', // TODO
};

return {
type: RpcResponseTypes.GetConnectedNetworkResponse,
response: result,
} as RpcResponse;
} catch (err) {
if (err instanceof z.ZodError) {
throw new InvalidParamsError(err.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '));
}
throw err;
}
return {
type: RpcResponseTypes.GetConnectedNetworkResponse,
response: result,
} as RpcResponse;
}
30 changes: 19 additions & 11 deletions packages/hathor-rpc-handler/src/rpcMethods/getUtxos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@ const getUtxosSchema = z.object({
maxUtxos: z.number().default(255),
token: z.string().default('HTR'),
filterAddress: z.string(),
authorities: z.number().nullable().optional(),
amountSmallerThan: z.number().min(0).nullable().optional(),
amountBiggerThan: z.number().min(0).nullable().optional(),
maximumAmount: z.number().nullable().optional(),
authorities: z.number().nullish().optional(),
amountSmallerThan: z.number().min(0).nullish().optional(),
amountBiggerThan: z.number().min(0).nullish().optional(),
maximumAmount: z.number().nullish().optional(),
onlyAvailableUtxos: z.boolean().default(true),
}),
}).transform(data => ({
...data,
max_utxos: data.maxUtxos,
filter_address: data.filterAddress,
amount_smaller_than: data.amountSmallerThan,
amount_bigger_than: data.amountBiggerThan,
max_amount: data.maximumAmount,
only_available_utxos: data.onlyAvailableUtxos,
})),
});

/**
Expand Down Expand Up @@ -66,12 +74,12 @@ export async function getUtxos(
'token': params.token,
// Defaults to 0 otherwise the lib fails
'authorities': params.authorities || 0,
'max_utxos': params.maxUtxos,
'filter_address': params.filterAddress,
'amount_smaller_than': params.amountSmallerThan,
'amount_bigger_than': params.amountBiggerThan,
'max_amount': params.maximumAmount,
'only_available_utxos': params.onlyAvailableUtxos,
'max_utxos': params.max_utxos,
'filter_address': params.filter_address,
'amount_smaller_than': params.amount_smaller_than,
'amount_bigger_than': params.amount_bigger_than,
'max_amount': params.max_amount,
'only_available_utxos': params.only_available_utxos,
};

// We have the same issues here that we do have in the headless wallet:
Expand Down
23 changes: 14 additions & 9 deletions packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ import { NanoContractAction } from '@hathor/wallet-lib/lib/nano_contracts/types'

const sendNanoContractSchema = z.object({
method: z.string().min(1),
blueprint_id: z.string().nullable(),
nc_id: z.string().nullable(),
blueprint_id: z.string().nullish(),
nc_id: z.string().nullish(),
actions: z.array(z.custom<NanoContractAction>()),
args: z.array(z.unknown()).default([]),
push_tx: z.boolean().default(true),
}).refine(
(data) => data.blueprint_id || data.nc_id,
}).transform(data => ({
...data,
blueprintId: data.blueprint_id || null,
ncId: data.nc_id || null,
pushTx: data.push_tx,
})).refine(
(data) => data.blueprintId || data.ncId,
"Either blueprint_id or nc_id must be provided"
);

Expand Down Expand Up @@ -69,12 +74,12 @@ export async function sendNanoContractTx(
type: TriggerTypes.SendNanoContractTxConfirmationPrompt,
method: rpcRequest.method,
data: {
blueprintId: params.blueprint_id,
ncId: params.nc_id,
blueprintId: params.blueprintId,
ncId: params.ncId,
actions: params.actions as NanoContractAction[],
method: params.method,
args: params.args,
pushTx: params.push_tx,
pushTx: params.pushTx,
},
};

Expand All @@ -97,7 +102,7 @@ export async function sendNanoContractTx(
throw new PromptRejectedError('Pin prompt rejected');
}

const sendMethod = params.push_tx
const sendMethod = params.pushTx
? wallet.createAndSendNanoContractTransaction.bind(wallet)
: wallet.createNanoContractTransaction.bind(wallet);

Expand All @@ -111,7 +116,7 @@ export async function sendNanoContractTx(
params.method,
caller,
{
ncId: params.nc_id,
ncId: params.ncId,
blueprintId: confirmedBluePrintId,
actions: confirmedActions,
args: confirmedArgs,
Expand Down
Loading