near-ca
Version:
An SDK for controlling Ethereum Accounts from a Near Account.
140 lines (139 loc) • 5.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MpcContract = void 0;
const near_api_js_1 = require("near-api-js");
const utils_1 = require("./utils");
const chains_1 = require("./chains");
/**
* High-level interface for the Near MPC-Recovery Contract
* located in: https://github.com/near/mpc-recovery
*/
class MpcContract {
/**
* Creates a new MPC Contract instance
*
* @param account - The NEAR account to use
* @param contractId - The contract ID
* @param rootPublicKey - Optional root public key
*/
constructor(account, contractId, rootPublicKey) {
/**
* Derives an Ethereum address from a derivation path
*
* @param derivationPath - The path to derive the address from
* @returns The derived Ethereum address
*/
this.deriveEthAddress = async (derivationPath) => {
if (!this.rootPublicKey) {
this.rootPublicKey = await this.contract.public_key();
}
const publicKey = (0, utils_1.deriveChildPublicKey)((0, utils_1.najPublicKeyStrToUncompressedHexPoint)(this.rootPublicKey), this.connectedAccount.accountId, derivationPath);
return (0, utils_1.uncompressedHexPointToEvmAddress)(publicKey);
};
/**
* Gets the required deposit for the signature
*
* @returns The required deposit amount as a string
*/
this.getDeposit = async () => {
try {
const deposit = await this.contract.experimental_signature_deposit();
return BigInt(deposit.toLocaleString("fullwide", { useGrouping: false })).toString();
}
catch {
// They are phasing out experimental_signature_deposit.
// required deposit is 1 yocto (see v1.signer-prod.testnet).
return "1";
}
};
/**
* Requests a signature from the MPC contract
*
* @param signArgs - The arguments for the signature request
* @param gas - Optional gas limit
* @returns The signature
*/
this.requestSignature = async (signArgs, gas) => {
const transaction = await this.encodeSignatureRequestTx(signArgs, gas);
const outcome = await this.signAndSendSignRequest(transaction);
return (0, utils_1.signatureFromOutcome)(outcome);
};
this.connectedAccount = account;
this.rootPublicKey = rootPublicKey;
this.contract = new near_api_js_1.Contract(account.connection, contractId, {
changeMethods: ["sign"],
viewMethods: ["public_key", "experimental_signature_deposit"],
useLocalViewExecution: false,
});
}
/**
* Gets the contract ID
*
* @returns The contract ID
*/
accountId() {
return this.contract.contractId;
}
/**
* Encodes a signature request into a transaction
*
* @param signArgs - The arguments for the signature request
* @param gas - Optional gas limit
* @returns The encoded transaction
*/
async encodeSignatureRequestTx(signArgs, gas) {
return {
signerId: this.connectedAccount.accountId,
receiverId: this.contract.contractId,
actions: [
{
type: "FunctionCall",
params: {
methodName: "sign",
args: { request: signArgs },
gas: gasOrDefault(gas),
deposit: await this.getDeposit(),
},
},
],
};
}
/**
* Signs and sends a signature request
*
* @param transaction - The transaction to sign and send
* @param blockTimeout - Optional timeout in blocks
* @returns The execution outcome
*/
async signAndSendSignRequest(transaction, blockTimeout = 30) {
const account = this.connectedAccount;
// @ts-expect-error: Account.signTransaction is protected (for no apparently good reason)
const [txHash, signedTx] = await account.signTransaction(this.contract.contractId, transaction.actions.map(({ params: { args, gas, deposit } }) => near_api_js_1.transactions.functionCall("sign", args, BigInt(gas), BigInt(deposit))));
const provider = account.connection.provider;
let outcome = await provider.sendTransactionAsync(signedTx);
let pings = 0;
while (outcome.final_execution_status != "EXECUTED" &&
pings < blockTimeout) {
await new Promise((resolve) => setTimeout(resolve, 1000));
outcome = await provider.txStatus(txHash, account.accountId, "INCLUDED");
pings += 1;
}
if (pings >= blockTimeout) {
console.warn(`Request status polling exited before desired outcome.\n Current status: ${outcome.final_execution_status}\nSignature Request will likely fail.`);
}
return outcome;
}
}
exports.MpcContract = MpcContract;
/**
* Returns the gas value or a default if not provided
*
* @param gas - Optional gas value
* @returns The gas value as a string
*/
function gasOrDefault(gas) {
if (gas !== undefined) {
return gas.toString();
}
return (chains_1.TGAS * 250n).toString();
}