diff --git a/src/types/rpc.ts b/src/types/rpc.ts new file mode 100644 index 0000000..230cc97 --- /dev/null +++ b/src/types/rpc.ts @@ -0,0 +1,131 @@ +// Basic structure of the JSON-RPC response +export interface JSONRPCResponse { + jsonrpc: string; + result: Result; + id: string; +} + +// Result contains various fields like final execution status, an array of receipts, etc. +export interface Result { + final_execution_status: string; + receipts: Receipt[]; + receipts_outcome: ReceiptOutcome[]; + status: TransactionStatus; + transaction: Transaction; + transaction_outcome: TransactionOutcome; +} + +// Define Receipt type with its structure +interface Receipt { + predecessor_id: string; + receipt: ReceiptDetail; + receipt_id: string; + receiver_id: string; +} + +// Detailed structure of a receipt which includes actions and other properties +interface ReceiptDetail { + Action: ActionDetail; +} + +// Actions within the receipt +interface ActionDetail { + actions: Action[]; + gas_price: string; + input_data_ids: any[]; + output_data_receivers: any[]; + signer_id: string; + signer_public_key: string; +} + +// Action can have different types like FunctionCall or Transfer +interface Action { + FunctionCall?: FunctionCall; + Transfer?: Transfer; +} + +// FunctionCall action specifics +interface FunctionCall { + args: string; + deposit: string; + gas: number; + method_name: string; +} + +// Transfer action specifics +interface Transfer { + deposit: string; +} + +// Receipt outcomes are listed in an array +export interface ReceiptOutcome { + block_hash: string; + id: string; + outcome: Outcome; + proof: Proof[]; +} + +// Outcome of executing the action +interface Outcome { + executor_id: string; + gas_burnt: number; + logs: string[]; + metadata: Metadata; + receipt_ids: string[]; + status: OutcomeStatus; + tokens_burnt: string; +} + +// Metadata may contain various gas profiling information +interface Metadata { + gas_profile: GasProfile[]; + version: number; +} + +// Detailed gas usage per action or computation step +interface GasProfile { + cost: string; + cost_category: string; + gas_used: number; +} + +// Status of the outcome, success or failure specifics +interface OutcomeStatus { + SuccessReceiptId?: string; + SuccessValue?: string; +} + +// Proofs for the transaction validation +interface Proof { + direction: string; + hash: string; +} + +// Status field detailing the transaction execution result +interface TransactionStatus { + SuccessValue: string; +} + +// Transaction detail structure +interface Transaction { + actions: TransactionAction[]; + hash: string; + nonce: number; + public_key: string; + receiver_id: string; + signature: string; + signer_id: string; +} + +// Actions within a transaction +interface TransactionAction { + FunctionCall: FunctionCall; +} + +// Transaction outcome mirrors structure similar to receipt outcomes +interface TransactionOutcome { + block_hash: string; + id: string; + outcome: Outcome; + proof: Proof[]; +} diff --git a/src/types/types.ts b/src/types/types.ts new file mode 100644 index 0000000..60d1be1 --- /dev/null +++ b/src/types/types.ts @@ -0,0 +1,87 @@ +import { MultichainContract } from "../mpcContract"; +import { FunctionCallAction } from "@near-wallet-selector/core"; +import BN from "bn.js"; +import { Hex } from "viem"; + +export interface BaseTx { + /// Recipient of the transaction + to: `0x${string}`; + /// ETH value of transaction + value?: bigint; + /// Call Data of the transaction + data?: `0x${string}`; + /// integer ID of the network for the transaction. + chainId: number; + /// Specified transaction nonce + nonce?: number; +} + +export interface NearEthAdapterParams { + /// An instance of the NearMPC contract connected to the associated near account. + mpcContract: MultichainContract; + /// path used to generate ETH account from Near account (e.g. "ethereum,1") + derivationPath?: string; +} + +export interface GasPrices { + maxFeePerGas: bigint; + maxPriorityFeePerGas: bigint; +} + +/// Near Contract Type for change methods +export interface ChangeMethodArgs { + /// Change method function agruments. + args: T; + /// GasLimit on transaction execution. + gas: BN; + /// Deposit (i.e. payable amount) to attach to transaction. + attachedDeposit: BN; +} + +/** + * Arguments required for signature request from MPC Contract + * cf. https://github.com/near/mpc-recovery/blob/ac040bcbb31ba9362a6641a5899647105a53ee4a/contract/src/lib.rs#L297-L320 + */ +export interface SignArgs { + /// Derivation Path of for ETH account associated with Near AccountId + path: string; + /// Serialized Ethereum Transaction Bytes. + payload: number[]; + /// version number associated with derived ETH Address (must be increasing). + key_version: number; +} + +export interface TxPayload { + /// Deserialized Ethereum Transaction. + transaction: Hex; + /// Arguments required by Near MPC Contract signature request. + signArgs: SignArgs; +} + +export interface NearContractFunctionPayload { + /// Signer of function call. + signerId: string; + /// Transaction Recipient (a Near ContractId). + receiverId: string; + /// Function call actions. + actions: Array; +} + +/** + * Result Type of MPC contract signature request. + * Representing Affine Points on eliptic curve. + */ +export interface MPCSignature { + big_r: string; + big_s: string; +} + +/** + * Sufficient data required to construct a signed Ethereum Transaction. + */ +export interface TransactionWithSignature { + /// Unsigned Ethereum transaction data. + transaction: Hex; + /// Representation of the transaction's signature. + signature: MPCSignature; +} diff --git a/src/utils/getSignature.ts b/src/utils/getSignature.ts new file mode 100644 index 0000000..7919915 --- /dev/null +++ b/src/utils/getSignature.ts @@ -0,0 +1,43 @@ +import { JSONRPCResponse, ReceiptOutcome } from "../types/rpc"; + +export async function signatureFromTxHash( + txHash: string, + accountId: string +): Promise<[string, string]> { + const url: string = "https://archival-rpc.testnet.near.org"; + const payload = { + jsonrpc: "2.0", + id: "dontcare", + method: "EXPERIMENTAL_tx_status", + params: [txHash, accountId], + }; + + // Make the POST request with the fetch API + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + const jsonResponse = (await response.json()) as JSONRPCResponse; + + const receiptsOutcome = jsonResponse.result.receipts_outcome; + const successValues = receiptsOutcome.map( + (outcome: ReceiptOutcome) => outcome.outcome.status.SuccessValue + ); + + // Find the first non-undefined value + const firstValidValue = successValues.find((value) => value !== undefined); + + if (firstValidValue) { + // Decode from base64 + const decodedValue = Buffer.from(firstValidValue, "base64").toString( + "utf-8" + ); + return JSON.parse(decodedValue); + } else { + throw new Error("No valid values found in the array."); + } +} diff --git a/tests/utils.getSignature.test.ts b/tests/utils.getSignature.test.ts new file mode 100644 index 0000000..9a4ebb5 --- /dev/null +++ b/tests/utils.getSignature.test.ts @@ -0,0 +1,9 @@ +import { signatureFromTxHash } from "../src/utils/getSignature"; + +describe("utility: get Signature", () => { + it("signatureFromTxHash", async () => { + const hash = "88LS5pkj99pd6B6noZU6sagQ1QDwHHoSy3qpHr5xLNsR"; + const sig = await signatureFromTxHash(hash, "neareth-dev.testnet"); + console.log(sig); + }); +});