Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 48 additions & 1 deletion packages/hathor-rpc-handler/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
import type { IHathorWallet } from '@hathor/wallet-lib';
import { DifferentNetworkError } from '../errors';
import { config, nanoUtils, Network } from '@hathor/wallet-lib';
import { DifferentNetworkError, SendNanoContractTxError } from '../errors';

export function validateNetwork(wallet: IHathorWallet, network: string) {
const currentNetwork = wallet.getNetwork();
Expand All @@ -14,3 +15,49 @@ export function validateNetwork(wallet: IHathorWallet, network: string) {
throw new DifferentNetworkError();
}
}

export async function parseNanoArgs(
wallet: IHathorWallet,
blueprintId: string | undefined | null,
ncId: string | undefined | null,
method: string,
args: unknown[],
network: string
) {
if (!blueprintId && !ncId) {
throw new SendNanoContractTxError(
'Either blueprintId or ncId is required.'
);
}

if (!blueprintId) {
let response;
try {
response = await wallet.getFullTxById(ncId!);
} catch {
// Error getting nano contract transaction data from the full node
throw new SendNanoContractTxError(
`Error getting nano contract transaction data with id ${ncId} from the full node`
);
}

if (!response.tx.nc_id) {
throw new SendNanoContractTxError(
`Transaction with id ${ncId} is not a nano contract transaction.`
);
}

blueprintId = response.tx.nc_blueprint_id;
}

config.setServerUrl(wallet.getServerUrl());
const result = await nanoUtils.validateAndParseBlueprintMethodArgs(
blueprintId!,
method,
args,
new Network(network)
);
return result.map((data) => {
return { ...data, parsed: data.field.toUser() };
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { z } from 'zod';
import type { HathorWallet } from '@hathor/wallet-lib';
import type { IHathorWallet } from '@hathor/wallet-lib';
import {
TriggerTypes,
PinConfirmationPrompt,
Expand All @@ -21,29 +21,23 @@ import {
CreateNanoContractCreateTokenTxLoadingTrigger,
CreateNanoContractCreateTokenTxLoadingFinishedTrigger,
NanoContractParams,
CreateTokenParams,
NanoContractCreateTokenParams,
} from '../types';
import { PromptRejectedError, InvalidParamsError } from '../errors';
import { INanoContractActionSchema } from '@hathor/wallet-lib';
import { createTokenBaseSchema } from '../schemas';

// Extend CreateTokenParams to include nano contract specific fields
type NanoContractCreateTokenParams = CreateTokenParams & {
contractPaysTokenDeposit: boolean;
};
import { PromptRejectedError, SendNanoContractTxError, InvalidParamsError } from '../errors';
import { CreateTokenTransaction, INanoContractActionSchema } from '@hathor/wallet-lib';
import { createNanoCreateTokenRpcSchema } from '../schemas';
import { parseNanoArgs } from '../helpers';

const createNanoContractCreateTokenTxSchema = z.object({
network: z.string().min(1),
method: z.string().min(1),
address: z.string().min(1),
data: z.object({
blueprint_id: z.string().nullable().optional(),
nc_id: z.string().nullable().optional(),
actions: z.array(INanoContractActionSchema).optional(),
args: z.array(z.unknown()).optional(),
}).optional(),
createTokenOptions: createTokenBaseSchema.extend({
contractPaysTokenDeposit: z.boolean(),
}).optional(),
actions: z.array(INanoContractActionSchema),
args: z.array(z.unknown()).default([]),
}),
createTokenOptions: createNanoCreateTokenRpcSchema,
push_tx: z.boolean().default(true),
});

Expand All @@ -64,41 +58,43 @@ const createNanoContractCreateTokenTxSchema = z.object({
*/
export async function createNanoContractCreateTokenTx(
rpcRequest: CreateNanoContractCreateTokenTxRpcRequest,
wallet: HathorWallet,
wallet: IHathorWallet,
requestMetadata: RequestMetadata,
promptHandler: TriggerHandler,
): Promise<CreateNanoContractCreateTokenTxResponse> {
const validationResult = createNanoContractCreateTokenTxSchema.safeParse(rpcRequest.params);
if (!validationResult.success) {
throw new InvalidParamsError(validationResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '));
}
const { method, address, data, createTokenOptions, push_tx } = validationResult.data;
const { method, data, createTokenOptions, push_tx, network } = validationResult.data;

const parsedArgs = await parseNanoArgs(wallet, data.blueprint_id, data.nc_id, method, data.args, network);

// Prepare nano and token params for the confirmation prompt
const nanoParams: NanoContractParams = {
blueprintId: data?.blueprint_id ?? null,
ncId: data?.nc_id ?? null,
actions: data?.actions ?? [],
blueprintId: data.blueprint_id ?? null,
ncId: data.nc_id ?? null,
actions: data.actions ?? [],
method,
args: data?.args ?? [],
parsedArgs: [],
args: data.args ?? [],
parsedArgs,
pushTx: push_tx,
};
// Only pass CreateTokenParams fields, fallback to null/empty for missing
const tokenParams: NanoContractCreateTokenParams = {
name: createTokenOptions?.name ?? '',
symbol: createTokenOptions?.symbol ?? '',
amount: typeof createTokenOptions?.amount === 'string' ? BigInt(createTokenOptions.amount) : (createTokenOptions?.amount ?? 0n),
mintAddress: createTokenOptions?.mintAddress ?? null,
changeAddress: createTokenOptions?.changeAddress ?? null,
createMint: createTokenOptions?.createMint ?? true,
mintAuthorityAddress: createTokenOptions?.mintAuthorityAddress ?? null,
allowExternalMintAuthorityAddress: createTokenOptions?.allowExternalMintAuthorityAddress ?? false,
createMelt: createTokenOptions?.createMelt ?? true,
meltAuthorityAddress: createTokenOptions?.meltAuthorityAddress ?? null,
allowExternalMeltAuthorityAddress: createTokenOptions?.allowExternalMeltAuthorityAddress ?? false,
data: createTokenOptions?.data ?? null,
contractPaysTokenDeposit: createTokenOptions?.contractPaysTokenDeposit ?? false,
name: createTokenOptions.name ?? '',
symbol: createTokenOptions.symbol ?? '',
amount: typeof createTokenOptions.amount === 'string' ? BigInt(createTokenOptions.amount) : (createTokenOptions.amount ?? 0n),
mintAddress: createTokenOptions.mintAddress ?? null,
changeAddress: createTokenOptions.changeAddress ?? null,
createMint: createTokenOptions.createMint ?? true,
mintAuthorityAddress: createTokenOptions.mintAuthorityAddress ?? null,
allowExternalMintAuthorityAddress: createTokenOptions.allowExternalMintAuthorityAddress ?? false,
createMelt: createTokenOptions.createMelt ?? true,
meltAuthorityAddress: createTokenOptions.meltAuthorityAddress ?? null,
allowExternalMeltAuthorityAddress: createTokenOptions.allowExternalMeltAuthorityAddress ?? false,
data: createTokenOptions.data ?? null,
contractPaysTokenDeposit: createTokenOptions.contractPaysTokenDeposit ?? false,
};

const confirmationPrompt: CreateNanoContractCreateTokenTxConfirmationPrompt = {
Expand Down Expand Up @@ -139,19 +135,26 @@ export async function createNanoContractCreateTokenTx(
if (push_tx) {
response = await wallet.createAndSendNanoContractCreateTokenTransaction(
nano.method,
address,
nano.caller,
nano,
token,
{ pinCode: pinResponse.data.pinCode }
);
) as CreateTokenTransaction;
} else {
response = await wallet.createNanoContractCreateTokenTransaction(
const sendTransactionObj = await wallet.createNanoContractCreateTokenTransaction(
nano.method,
address,
nano.caller,
nano,
token,
{ pinCode: pinResponse.data.pinCode }
);

if (!sendTransactionObj.transaction) {
// This should never happen, but we'll check anyway
throw new SendNanoContractTxError('Unable to create transaction object');
}
// Convert the transaction object to hex format for the response
response = sendTransactionObj.transaction.toHex();
}

// Emit loading finished trigger
Expand Down
30 changes: 3 additions & 27 deletions packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
SendNanoContractTxLoadingFinishedTrigger,
} from '../types';
import { PromptRejectedError, SendNanoContractTxError, InvalidParamsError } from '../errors';
import { INanoContractActionSchema, NanoContractAction, nanoUtils, Network, config } from '@hathor/wallet-lib';
import { parseNanoArgs } from '../helpers';
import { INanoContractActionSchema, NanoContractAction } from '@hathor/wallet-lib';

export type NanoContractActionWithStringAmount = Omit<NanoContractAction, 'amount'> & {
amount: string,
Expand Down Expand Up @@ -75,32 +76,7 @@ export async function sendNanoContractTx(
type: TriggerTypes.PinConfirmationPrompt,
};

let blueprintId = params.blueprintId;
if (!blueprintId) {
let response;
try {
response = await wallet.getFullTxById(params.ncId!);
} catch {
// Error getting nano contract transaction data from the full node
throw new SendNanoContractTxError(
`Error getting nano contract transaction data with id ${params.ncId} from the full node`
);
}

if (!response.tx.nc_id) {
throw new SendNanoContractTxError(
`Transaction with id ${params.ncId} is not a nano contract transaction.`
);
}

blueprintId = response.tx.nc_blueprint_id!;
}

config.setServerUrl(wallet.getServerUrl());
const result = await nanoUtils.validateAndParseBlueprintMethodArgs(blueprintId!, params.method, params.args, new Network(params.network));
const parsedArgs = result.map((data) => {
return { ...data, parsed: data.field.toUser() };
});
const parsedArgs = await parseNanoArgs(wallet, params.blueprintId, params.ncId, params.method, params.args, params.network);

const sendNanoContractTxPrompt: SendNanoContractTxConfirmationPrompt = {
...rpcRequest,
Expand Down
39 changes: 22 additions & 17 deletions packages/hathor-rpc-handler/src/schemas/createTokenSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,7 @@

import { z } from 'zod';

export const createTokenBaseSchema = z.object({
name: z.string().min(1).max(30),
symbol: z.string().min(2).max(5),
amount: z.union([z.string(), z.bigint()]),
changeAddress: z.string().nullable().optional(),
createMint: z.boolean().optional(),
mintAuthorityAddress: z.string().nullable().optional(),
allowExternalMintAuthorityAddress: z.boolean().optional(),
createMelt: z.boolean().optional(),
meltAuthorityAddress: z.string().nullable().optional(),
allowExternalMeltAuthorityAddress: z.boolean().optional(),
data: z.array(z.string()).nullable().optional(),
mintAddress: z.string().nullable().optional(),
});

// Schema for the original createToken RPC method with snake_case fields
export const createTokenRpcSchema = z.object({
const createTokenBaseSchema = z.object({
name: z.string().min(1).max(30),
symbol: z.string().min(2).max(5),
amount: z.string().regex(/^\d+$/)
Expand All @@ -37,7 +21,28 @@ export const createTokenRpcSchema = z.object({
melt_authority_address: z.string().nullish().default(null),
allow_external_melt_authority_address: z.boolean().default(false),
data: z.string().array().nullish().default(null),
});

export const createNanoCreateTokenRpcSchema = createTokenBaseSchema.extend({
contract_pays_token_deposit: z.boolean()
}).transform(data => ({
name: data.name,
symbol: data.symbol,
amount: data.amount,
mintAddress: 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,
contractPaysTokenDeposit: data.contract_pays_token_deposit,
}));

// Schema for the original createToken RPC method with snake_case fields
export const createTokenRpcSchema = createTokenBaseSchema.transform(data => ({
name: data.name,
symbol: data.symbol,
amount: data.amount,
Expand Down
2 changes: 1 addition & 1 deletion packages/hathor-rpc-handler/src/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
* LICENSE file in the root directory of this source tree.
*/

export { createTokenBaseSchema, createTokenRpcSchema } from './createTokenSchema';
export { createNanoCreateTokenRpcSchema, createTokenRpcSchema } from './createTokenSchema';
8 changes: 6 additions & 2 deletions packages/hathor-rpc-handler/src/types/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ export interface CreateTokenParams {
data: string[] | null,
}

export type NanoContractCreateTokenParams = CreateTokenParams & {
contractPaysTokenDeposit: boolean;
};

export type CreateTokenConfirmationPrompt = BaseConfirmationPrompt & {
type: TriggerTypes.CreateTokenConfirmationPrompt;
data: CreateTokenParams;
Expand Down Expand Up @@ -294,7 +298,7 @@ export interface SendTransactionConfirmationResponse {

export interface CreateNanoContractCreateTokenTxParams {
nano: NanoContractParams;
token: CreateTokenParams;
token: NanoContractCreateTokenParams;
}

export type CreateNanoContractCreateTokenTxConfirmationPrompt = BaseConfirmationPrompt & {
Expand All @@ -307,7 +311,7 @@ export interface CreateNanoContractCreateTokenTxConfirmationResponse {
data: {
accepted: true;
nano: NanoContractParams & { caller: string };
token: CreateTokenParams;
token: NanoContractCreateTokenParams;
} | {
accepted: false;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/hathor-rpc-handler/src/types/rpcResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export interface SendTransactionResponse extends BaseRpcResponse {

export interface CreateNanoContractCreateTokenTxResponse extends BaseRpcResponse {
type: RpcResponseTypes.CreateNanoContractCreateTokenTxResponse;
response: Transaction;
response: CreateTokenTransaction | string;
}

export interface ChangeNetworkResponse extends BaseRpcResponse {
Expand Down
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Hathor Network Snap integration",
"proposedName": "Hathor Wallet",
"source": {
"shasum": "K6uIBlCuUj5/93meVMmLeXpFuOFoiUR1mB6jLVpeS24=",
"shasum": "jny8ScQpCkc5cUCtRIzxtXVAHC6yP1hsZzKN5/ZkDAw=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Loading
Loading