Skip to content

Commit

Permalink
feat: send transaction rpc method, validation and lib update
Browse files Browse the repository at this point in the history
  • Loading branch information
andreabadesso committed Feb 24, 2025
1 parent a05531d commit fcfa87e
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 79 deletions.
2 changes: 1 addition & 1 deletion packages/hathor-rpc-handler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"typescript-eslint": "7.13.0"
},
"dependencies": {
"@hathor/wallet-lib": "1.11.0",
"@hathor/wallet-lib": "2.0.1",
"zod": "^3.24.1"
}
}
14 changes: 14 additions & 0 deletions packages/hathor-rpc-handler/src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ export class NoUtxosAvailableError extends Error {};

export class SignMessageError extends Error {};

export class InsufficientFundsError extends Error {
constructor(message: string) {
super(message);
this.name = 'InsufficientFundsError';
}
}

export class InvalidParamsError extends Error {
constructor(message: string) {
super(message);
Expand All @@ -41,3 +48,10 @@ export class InvalidParamTypeError extends Error {
this.name = 'InvalidParamTypeError';
}
}

export class SendTransactionError extends Error {
constructor(message: string) {
super(message);
this.name = 'SendTransactionError';
}
}
10 changes: 9 additions & 1 deletion packages/hathor-rpc-handler/src/rpcHandler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
RpcResponse,
CreateTokenRpcRequest,
SignOracleDataRpcRequest,
SendTransactionRpcRequest,
} from '../types';
import {
getAddress,
Expand All @@ -29,9 +30,10 @@ import {
getConnectedNetwork,
signOracleData,
signWithAddress,
createToken,
sendTransaction,
} from '../rpcMethods';
import { InvalidRpcMethod } from '../errors';
import { createToken } from '../rpcMethods/createToken';

export const handleRpcRequest = async (
request: RpcRequest,
Expand Down Expand Up @@ -88,6 +90,12 @@ export const handleRpcRequest = async (
requestMetadata,
promptHandler,
);
case RpcMethods.SendTransaction: return sendTransaction(
request as SendTransactionRpcRequest,
wallet,
requestMetadata,
promptHandler,
);
default: throw new InvalidRpcMethod();
}
};
145 changes: 107 additions & 38 deletions packages/hathor-rpc-handler/src/rpcMethods/sendTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/

import { z } from 'zod';
import type { HathorWallet } from '@hathor/wallet-lib';
import type { HathorWallet, Transaction } from '@hathor/wallet-lib';
import type { OutputSendTransaction } from '@hathor/wallet-lib/lib/wallet/types';
import {
TriggerTypes,
PinConfirmationPrompt,
Expand All @@ -22,7 +23,12 @@ import {
RpcResponse,
RpcMethods,
} from '../types';
import { PromptRejectedError, InvalidParamsError } from '../errors';
import {
PromptRejectedError,
InvalidParamsError,
SendTransactionError,
InsufficientFundsError,
} from '../errors';
import { validateNetwork } from '../helpers';

const sendTransactionSchema = z.object({
Expand All @@ -31,7 +37,7 @@ const sendTransactionSchema = z.object({
network: z.string().min(1),
outputs: z.array(z.object({
address: z.string().optional(),
value: z.number().positive(),
value: z.string().transform(val => BigInt(val)),
token: z.string().optional(),
type: z.string().optional(),
data: z.array(z.string()).optional(),
Expand All @@ -57,6 +63,7 @@ const sendTransactionSchema = z.object({
*
* @throws {PromptRejectedError} If the user rejects any of the prompts.
* @throws {InvalidParamsError} If the request parameters are invalid.
* @throws {SendTransactionError} If there's an error preparing or sending the transaction.
*/
export async function sendTransaction(
rpcRequest: SendTransactionRpcRequest,
Expand All @@ -65,73 +72,135 @@ export async function sendTransaction(
promptHandler: TriggerHandler,
) {
try {
console.log('[sendTransaction] Starting transaction process');
console.log('[sendTransaction] Request params:', JSON.stringify(rpcRequest.params, null, 2));

const validatedRequest = sendTransactionSchema.parse(rpcRequest);
const { params } = validatedRequest;
// console.log('[sendTransaction] Validation passed, parsed params:', JSON.stringify(params, null, 2));

validateNetwork(wallet, params.network);
console.log('[sendTransaction] Network validation passed');

try {
// Prepare the transaction outputs
console.log('[sendTransaction] Preparing transaction outputs');
const sendTransactionOutputs = params.outputs.map(output => {
const outputData = {
address: output.address,
value: output.value,
token: output.token,
type: output.type,
data: output.data,
};
const typedOutput = outputData as unknown as OutputSendTransaction;

if (typedOutput.type === 'data') {
(typedOutput as unknown as { value: bigint }).value = BigInt(1);
typedOutput.token = '00';
}

const prompt: SendTransactionConfirmationPrompt = {
type: TriggerTypes.SendTransactionConfirmationPrompt,
method: rpcRequest.method,
data: {
outputs: params.outputs,
inputs: params.inputs,
console.log('[sendTransaction] Prepared output:', {
...typedOutput,
value: typedOutput.value.toString(),
});

return typedOutput;
});

// Create the transaction service but don't run it yet
const sendTransaction = await wallet.sendManyOutputsSendTransaction(sendTransactionOutputs, {
inputs: params.inputs || [],
changeAddress: params.changeAddress,
pinCode: '111111',
});

// Prepare the transaction to get all inputs (including automatically selected ones)
console.log('[sendTransaction] Preparing transaction data');
try {
const preparedTx: Transaction = await sendTransaction.prepareTxData();
console.log('[sendTransaction] Prepared Tx: ', preparedTx);
} catch (err) {
console.error('[sendTransaction] Error preparing transaction:', err);
if (err instanceof Error) {
// Check if the error is about insufficient funds
if (err.message.includes('Insufficient amount of tokens')) {
throw new InsufficientFundsError(err.message);
}
throw new SendTransactionError(err.message);
}
throw new SendTransactionError('An unknown error occurred while preparing the transaction');
}
};

const sendResponse = await promptHandler(prompt, requestMetadata) as SendTransactionConfirmationResponse;
// Show the complete transaction (with all inputs) to the user
console.log('[sendTransaction] Creating confirmation prompt');
const prompt: SendTransactionConfirmationPrompt = {
type: TriggerTypes.SendTransactionConfirmationPrompt,
method: rpcRequest.method,
data: {
outputs: params.outputs as unknown as Array<{
address?: string;
value: number;
token?: string;
type?: string;
data?: string[];
}>,
// @ts-expect-error Transaction inputs type mismatch with prompt interface
inputs: preparedTx.inputs,
changeAddress: params.changeAddress,
}
};

const sendResponse = await promptHandler(prompt, requestMetadata) as SendTransactionConfirmationResponse;

if (!sendResponse.data.accepted) {
throw new PromptRejectedError('User rejected send transaction prompt');
}
if (!sendResponse.data.accepted) {
throw new PromptRejectedError('User rejected send transaction prompt');
}

const pinPrompt: PinConfirmationPrompt = {
type: TriggerTypes.PinConfirmationPrompt,
method: rpcRequest.method,
};
const pinPrompt: PinConfirmationPrompt = {
type: TriggerTypes.PinConfirmationPrompt,
method: rpcRequest.method,
};

const pinResponse = await promptHandler(pinPrompt, requestMetadata) as PinRequestResponse;
const pinResponse = await promptHandler(pinPrompt, requestMetadata) as PinRequestResponse;

if (!pinResponse.data.accepted) {
throw new PromptRejectedError('User rejected PIN prompt');
}
if (!pinResponse.data.accepted) {
throw new PromptRejectedError('User rejected PIN prompt');
}

const loadingTrigger: SendTransactionLoadingTrigger = {
type: TriggerTypes.SendTransactionLoadingTrigger,
};
promptHandler(loadingTrigger, requestMetadata);
const loadingTrigger: SendTransactionLoadingTrigger = {
type: TriggerTypes.SendTransactionLoadingTrigger,
};
promptHandler(loadingTrigger, requestMetadata);

try {
const response = await wallet.sendManyOutputsTransaction(
params.outputs,
{
inputs: params.inputs,
changeAddress: params.changeAddress,
pinCode: pinResponse.data.pinCode,
}
);
sendTransaction.pin = pinResponse.data.pinCode;

// Now execute the prepared transaction
const response = await sendTransaction.run();

const loadingFinishedTrigger: SendTransactionLoadingFinishedTrigger = {
type: TriggerTypes.SendTransactionLoadingFinishedTrigger,
};
promptHandler(loadingFinishedTrigger, requestMetadata);
console.log('[sendTransaction] Loading finished trigger sent');

return {
type: RpcResponseTypes.SendTransactionResponse,
response,
} as RpcResponse;
} catch (err) {
console.error('[sendTransaction] Error in transaction execution:', err);
if (err instanceof Error) {
throw new Error(err.message);
throw new SendTransactionError(err.message);
} else {
throw new Error('An unknown error occurred while sending the transaction');
throw new SendTransactionError('An unknown error occurred while sending the transaction');
}
}
} catch (err) {
console.error('[sendTransaction] Error in main try block:', err);
if (err instanceof z.ZodError) {
throw new InvalidParamsError(err.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '));
}
throw err;
}
}
}
5 changes: 4 additions & 1 deletion packages/hathor-rpc-handler/src/types/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,12 @@ export interface SendTransactionConfirmationPrompt extends BaseConfirmationPromp
type?: string;
data?: string[];
}>;
inputs?: Array<{
inputs: Array<{
txId: string;
index: number;
value: number;
address: string;
token: string;
}>;
changeAddress?: string;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/hathor-rpc-handler/src/types/rpcRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export interface SendTransactionRpcRequest {
network: string;
outputs: Array<{
address?: string;
value: number;
value: string | number | bigint;
token?: string;
type?: string;
data?: string[];
Expand Down
Loading

0 comments on commit fcfa87e

Please sign in to comment.