UNPKG

near-ca-test

Version:

An SDK for controlling Ethereum Accounts from a Near Account.

95 lines (94 loc) 3.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.signatureFromTxHash = signatureFromTxHash; exports.transformSignature = transformSignature; exports.signatureFromOutcome = signatureFromOutcome; async function signatureFromTxHash(nodeUrl, txHash, /// 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 = "non-empty") { const payload = { jsonrpc: "2.0", id: "dontcare", // This could be replaced with `tx`. // method: "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), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const json = await response.json(); if (json.error) { throw new Error(`JSON-RPC error: ${json.error.message}`); } if (json.result) { return signatureFromOutcome(json.result); } else { throw new Error(`No FinalExecutionOutcome in response: ${json}`); } } function transformSignature(mpcSig) { const { big_r, s, recovery_id } = mpcSig; return { r: `0x${big_r.affine_point.substring(2)}`, s: `0x${s.scalar}`, yParity: recovery_id, }; } function signatureFromOutcome( // The Partial object is intended to make up for the // difference between all the different near-api versions and wallet-selector bullshit // the field `final_execution_status` is in one, but not the other and we don't use it anyway. outcome) { const txHash = outcome.transaction_outcome?.id; // TODO - find a scenario when outcome.status is `FinalExecutionStatusBasic`! let b64Sig = outcome.status.SuccessValue; if (!b64Sig) { // This scenario occurs when sign call is relayed (i.e. executed by someone else). // E.g. https://testnet.nearblocks.io/txns/G1f1HVUxDBWXAEimgNWobQ9yCx1EgA2tzYHJBFUfo3dj // We have to dig into `receipts_outcome` and extract the signature from within. // We want the second occurence of the signature because // the first is nested inside `{ Ok: MPCSignature }`) b64Sig = outcome.receipts_outcome // Map to get SuccessValues: The Signature will appear twice. .map((receipt) => receipt.outcome.status.SuccessValue) // Reverse the to "find" the last non-empty value! .reverse() .find((value) => value && value.trim().length > 0); } if (!b64Sig) { throw new Error(`No detectable signature found in transaction ${txHash}`); } if (b64Sig === "eyJFcnIiOiJGYWlsZWQifQ==") { // {"Err": "Failed"} throw new Error(`Signature Request Failed in ${txHash}`); } const decodedValue = Buffer.from(b64Sig, "base64").toString("utf-8"); const signature = JSON.parse(decodedValue); if (isMPCSignature(signature)) { return transformSignature(signature); } else { throw new Error(`No detectable signature found in transaction ${txHash}`); } } // type guard for MPCSignature object. function isMPCSignature(obj) { return (typeof obj === "object" && obj !== null && typeof obj.big_r === "object" && typeof obj.big_r.affine_point === "string" && typeof obj.s === "object" && typeof obj.s.scalar === "string" && typeof obj.recovery_id === "number"); }