near-ca-test
Version:
An SDK for controlling Ethereum Accounts from a Near Account.
112 lines (111 loc) • 4.91 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MpcContract = void 0;
const near_api_js_1 = require("near-api-js");
const kdf_1 = require("./utils/kdf");
const near_1 = require("./chains/near");
const signature_1 = require("./utils/signature");
/**
* High-level interface for the Near MPC-Recovery Contract
* located in: https://github.com/near/mpc-recovery
*/
class MpcContract {
constructor(account, contractId, rootPublicKey) {
this.deriveEthAddress = async (derivationPath) => {
if (!this.rootPublicKey) {
this.rootPublicKey = await this.contract.public_key();
}
const publicKey = (0, kdf_1.deriveChildPublicKey)((0, kdf_1.najPublicKeyStrToUncompressedHexPoint)(this.rootPublicKey), this.connectedAccount.accountId, derivationPath);
return (0, kdf_1.uncompressedHexPointToEvmAddress)(publicKey);
};
this.getDeposit = async () => {
let deposit = 1e23;
try {
deposit = await this.contract.experimental_signature_deposit();
}
catch (error) {
if (error instanceof Error &&
error.message === "Contract method is not found") {
// Clown town
deposit = await this.contract.experimantal_signature_deposit();
}
else {
console.warn(`Failed to get deposit with ${error} - using fallback of 0.1 Near`);
}
}
return BigInt(deposit.toLocaleString("fullwide", { useGrouping: false })).toString();
};
this.requestSignature = async (signArgs, gas) => {
// near-api-js SUX so bad we can't configure this RPC timeout.
// const mpcSig = await this.contract.sign({
// signerAccount: this.connectedAccount,
// args: { request: signArgs },
// gas: gasOrDefault(gas),
// amount: await this.getDeposit(),
// });
const transaction = await this.encodeSignatureRequestTx(signArgs, gas);
const outcome = await this.signAndSendSignRequest(transaction);
return (0, signature_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",
"experimantal_signature_deposit",
],
useLocalViewExecution: false,
});
}
accountId() {
return this.contract.contractId;
}
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(),
},
},
],
};
}
async signAndSendSignRequest(transaction, blockTimeout = 30) {
const account = this.connectedAccount;
// @ts-expect-error: Account.signTransaction is protected (for no apparantly 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) {
// Sleep 1 second before next ping.
await new Promise((resolve) => setTimeout(resolve, 1000));
// txStatus times out when waiting for 'EXECUTED'.
// Instead we wait for an earlier status type, sleep between and keep pinging.
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 likley fail.`);
}
return outcome;
}
}
exports.MpcContract = MpcContract;
function gasOrDefault(gas) {
if (gas !== undefined) {
return gas.toString();
}
// Default of 250 TGAS
return (near_1.TGAS * 250n).toString();
}