Skip to content

Commit

Permalink
Extract Signature By Near-TxHash (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith authored May 7, 2024
1 parent 614d407 commit f9aaa17
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 16 deletions.
18 changes: 8 additions & 10 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ jobs:
- name: Install & Build
run: yarn && yarn build

- name: Lint & Test
- name: Lint & Unit Test
run: |
yarn lint
yarn test
env:
NODE_URL: https://rpc2.sepolia.org
SCAN_URL: https://sepolia.etherscan.io
GAS_STATION_URL: https://sepolia.beaconcha.in/api/v1/execution/gasnow

NEAR_MULTICHAIN_CONTRACT: multichain-testnet-2.testnet
NEAR_ACCOUNT_ID: ${{secrets.NEAR_ACCOUNT_ID}}
NEAR_ACCOUNT_PRIVATE_KEY: ${{secrets.NEAR_PK}}
yarn test utils
# These are only relevant for e2e tests
# env:
# NEAR_MULTICHAIN_CONTRACT: multichain-testnet-2.testnet
# NEAR_MULTICHAIN_CONTRACT: v5.multichain-mpc-dev.testnet
# NEAR_ACCOUNT_ID: ${{secrets.NEAR_ACCOUNT_ID}}
# NEAR_ACCOUNT_PRIVATE_KEY: ${{secrets.NEAR_PK}}
2 changes: 1 addition & 1 deletion src/chains/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
NearContractFunctionPayload,
TxPayload,
TransactionWithSignature,
} from "../types";
} from "../types/types";
import { MultichainContract } from "../mpcContract";
import BN from "bn.js";
import { queryGasPrice } from "../utils/gasPrice";
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./chains/ethereum";
export * from "./mpcContract";
export * from "./chains/near";
export * from "./types";
export * from "./types/types";
export * from "./network";
2 changes: 1 addition & 1 deletion src/mpcContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
MPCSignature,
NearContractFunctionPayload,
SignArgs,
} from "./types";
} from "./types/types";

interface MultichainContractInterface extends Contract {
// Define the signature for the `public_key` view method
Expand Down
133 changes: 133 additions & 0 deletions src/types/rpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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;
// TODO - determine types here and find a non-trivial example.
// cf: https://github.com/Mintbase/near-ca/issues/31
// 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[];
}
2 changes: 1 addition & 1 deletion src/types.ts → src/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MultichainContract } from "./mpcContract";
import { MultichainContract } from "../mpcContract";
import { FunctionCallAction } from "@near-wallet-selector/core";
import BN from "bn.js";
import { Hex } from "viem";
Expand Down
2 changes: 1 addition & 1 deletion src/utils/gasPrice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GasPrices } from "../types";
import { GasPrices } from "../types/types";

interface GasPriceResponse {
code: number;
Expand Down
37 changes: 37 additions & 0 deletions src/utils/getSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { JSONRPCResponse } from "../types/rpc";

export async function signatureFromTxHash(
nodeUrl: string,
txHash: string,
/// This field doesn't appear to be necessary although (possibly for efficiency),
/// the docs mention that it is "used to determine which shard to query for transaction".
accountId: string = "non-empty"
): Promise<[string, string]> {
const payload = {
jsonrpc: "2.0",
id: "dontcare",
// This could be replaced with `tx`.
method: "EXPERIMENTAL_tx_status",
params: [txHash, accountId],
};

// Make the POST request with the fetch API
const response = await fetch(nodeUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});

const jsonResponse = (await response.json()) as JSONRPCResponse;
const base64Sig = jsonResponse.result.status.SuccessValue;

if (base64Sig) {
// Decode from base64
const decodedValue = Buffer.from(base64Sig, "base64").toString("utf-8");
return JSON.parse(decodedValue);
} else {
throw new Error("No valid values found in the array.");
}
}
2 changes: 1 addition & 1 deletion src/utils/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
serializeTransaction,
signatureToHex,
} from "viem";
import { TransactionWithSignature } from "../types";
import { TransactionWithSignature } from "../types/types";
import { secp256k1 } from "@noble/curves/secp256k1";

import { publicKeyToAddress } from "viem/utils";
Expand Down
21 changes: 21 additions & 0 deletions tests/utils.getSignature.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { signatureFromTxHash } from "../src/utils/getSignature";

describe("utility: get Signature", () => {
const url: string = "https://archival-rpc.testnet.near.org";
// const accountId = "neareth-dev.testnet";
const successHash = "88LS5pkj99pd6B6noZU6sagQ1QDwHHoSy3qpHr5xLNsR";
const failedHash = "HaG9L4HnP69v6wSnAmKfzsCUhDaVMRZWNGhGqnepsMTD";

it("successful: signatureFromTxHash", async () => {
const sig = await signatureFromTxHash(url, successHash);
expect(sig).toEqual([
"03EA06CECA2B7D71F6F4DA729A681B4DE44C6402F5F5BB9FC88C6706959D4FEDD4",
"67986E234DEC5D51CF6AED452FE1C4544924218AC20B009F81BAAE53C02AFE76",
]);
});
it("failed: signatureFromTxHash", async () => {
await expect(signatureFromTxHash(url, failedHash)).rejects.toThrow(
"No valid values found in the array."
);
});
});

0 comments on commit f9aaa17

Please sign in to comment.