UNPKG

@reef-defi/evm-provider

Version:
316 lines 13.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Signer = void 0; const abstract_signer_1 = require("@ethersproject/abstract-signer"); const address_1 = require("@ethersproject/address"); const bignumber_1 = require("@ethersproject/bignumber"); const bytes_1 = require("@ethersproject/bytes"); const logger_1 = require("@ethersproject/logger"); const properties_1 = require("@ethersproject/properties"); const strings_1 = require("@ethersproject/strings"); const util_1 = require("@polkadot/util"); const util_crypto_1 = require("@polkadot/util-crypto"); const utils_1 = require("./utils"); const logger = new logger_1.Logger('evm-provider'); class Signer extends abstract_signer_1.Signer { constructor(provider, address, signingKey) { super(); (0, properties_1.defineReadOnly)(this, 'provider', provider); (0, properties_1.defineReadOnly)(this, 'signingKey', signingKey); // @ts-ignore this.provider.api.setSigner(signingKey); if (typeof address === 'string' && (0, util_crypto_1.isEthereumAddress)(address)) { logger.throwError('expect substrate address'); } else { try { (0, util_crypto_1.decodeAddress)(address); (0, properties_1.defineReadOnly)(this, '_substrateAddress', address); } catch (_a) { logger.throwArgumentError('invalid address', 'address', address); } } } connect(provider) { return logger.throwError('cannot alter JSON-RPC Signer connection', logger_1.Logger.errors.UNSUPPORTED_OPERATION, { operation: 'connect' }); } /** * * @param evmAddress The EVM address to check * @returns A promise that resolves to true if the EVM address is claimed * or false if the address is not claimed */ isClaimed(evmAddress) { return __awaiter(this, void 0, void 0, function* () { const rpcEvmAddress = yield this.queryEvmAddress(); if (!rpcEvmAddress) return false; if (!evmAddress) return true; if (rpcEvmAddress === evmAddress) { return true; } return logger.throwError('An evm account already exists to bind to this account'); }); } /** * Get the signer's EVM address, and claim an EVM address if it has not claimed one. * @returns A promise resolving to the EVM address of the signer's substrate * address */ getAddress() { return __awaiter(this, void 0, void 0, function* () { const address = yield this.queryEvmAddress(); if (address) { return address; } else { // default address return this.computeDefaultEvmAddress(); } }); } /** * Get the signers EVM address if it has claimed one. * @returns A promise resolving to the EVM address of the signer's substrate * address or an empty string if the EVM address isn't claimed */ queryEvmAddress() { return __awaiter(this, void 0, void 0, function* () { const address = yield this.provider.api.query.evmAccounts.evmAddresses(this._substrateAddress); if (!address.isEmpty) { const evmAddress = (0, address_1.getAddress)(address.toString()); return evmAddress; } return ''; }); } /** * * @returns The default EVM address generated for the signer's substrate address */ computeDefaultEvmAddress() { const address = this._substrateAddress; const publicKey = (0, util_crypto_1.decodeAddress)(address); const isStartWithEvm = (0, util_1.u8aEq)('evm:', publicKey.slice(0, 4)); if (isStartWithEvm) { return (0, address_1.getAddress)((0, util_1.u8aToHex)(publicKey.slice(4, 24))); } return (0, address_1.getAddress)((0, util_1.u8aToHex)((0, util_crypto_1.blake2AsU8a)((0, util_1.u8aConcat)('evm:', publicKey), 256).slice(0, 20))); } /** * * @returns The substrate account stored in this Signer */ getSubstrateAddress() { return __awaiter(this, void 0, void 0, function* () { return this._substrateAddress; }); } claimEvmAccount(evmAddress) { return __awaiter(this, void 0, void 0, function* () { const isConnented = yield this.isClaimed(evmAddress); if (isConnented) return; const publicKey = (0, util_crypto_1.decodeAddress)(this._substrateAddress); const data = 'Reef evm:' + Buffer.from(publicKey).toString('hex'); const signature = yield this._signMessage(evmAddress, data); const extrinsic = this.provider.api.tx.evmAccounts.claimAccount(evmAddress, signature); yield extrinsic.signAsync(this._substrateAddress); yield new Promise((resolve, reject) => { extrinsic .send((result) => { (0, utils_1.handleTxResponse)(result, this.provider.api) .then(() => { resolve(); }) .catch(({ message, result }) => { if (message === 'evmAccounts.AccountIdHasMapped') { resolve(); } reject(message); }); }) .catch((error) => { reject(error && error.message); }); }); }); } /** * Claims a default EVM address for this signer's substrate address */ claimDefaultAccount() { return __awaiter(this, void 0, void 0, function* () { const extrinsic = this.provider.api.tx.evmAccounts.claimDefaultAccount(); yield extrinsic.signAsync(this._substrateAddress); yield new Promise((resolve, reject) => { extrinsic .send((result) => { (0, utils_1.handleTxResponse)(result, this.provider.api) .then(() => { resolve(); }) .catch(({ message, result }) => { if (message === 'evmAccounts.AccountIdHasMapped') { resolve(); } reject(message); }); }) .catch((error) => { reject(error && error.message); }); }); }); } getBalance(blockTag) { return this.provider.getBalance(this._substrateAddress, blockTag); } signTransaction(transaction) { return logger.throwError('signing transactions is unsupported', logger_1.Logger.errors.UNSUPPORTED_OPERATION, { operation: 'signTransaction' }); } /** * * @param transaction * @returns A promise that resolves to the transaction's response */ sendTransaction(_transaction) { return __awaiter(this, void 0, void 0, function* () { this._checkProvider('sendTransaction'); const signerAddress = yield this.getSubstrateAddress(); const evmAddress = yield this.getAddress(); // estimateResources requires the from parameter. // However, when creating the contract, there is no from parameter in the tx const transaction = Object.assign({ from: evmAddress }, _transaction); const resources = yield this.provider.estimateResources(transaction); // Multiply by 3.1 const gasLimit = resources.gas.mul(31).div(10); let storageLimit; // If the storage limit is supplied, override it from the estimateResources if (transaction.customData) { if ('storageLimit' in transaction.customData) { storageLimit = transaction.customData.storageLimit; if ((0, util_1.isNumber)(storageLimit)) { storageLimit = bignumber_1.BigNumber.from(storageLimit); } } } else { storageLimit = resources.storage.mul(31).div(10); } let totalLimit = yield transaction.gasLimit; if (totalLimit === null || totalLimit === undefined) { totalLimit = gasLimit.add(storageLimit); } transaction.gasLimit = totalLimit; const tx = yield this.populateTransaction(transaction); const data = tx.data; const from = tx.from; if (!data) { return logger.throwError('Request data not found'); } if (!from) { return logger.throwError('Request from not found'); } let extrinsic; // @TODO create contract if (!tx.to) { extrinsic = this.provider.api.tx.evm.create(tx.data, (0, utils_1.toBN)(tx.value), (0, utils_1.toBN)(gasLimit), (0, utils_1.toBN)(storageLimit.isNegative() ? 0 : storageLimit)); } else { extrinsic = this.provider.api.tx.evm.call(tx.to, tx.data, (0, utils_1.toBN)(tx.value), (0, utils_1.toBN)(gasLimit), (0, utils_1.toBN)(storageLimit.isNegative() ? 0 : storageLimit)); } yield extrinsic.signAsync(signerAddress); return new Promise((resolve, reject) => { extrinsic .send((result) => { (0, utils_1.handleTxResponse)(result, this.provider.api) .then(() => { resolve({ hash: extrinsic.hash.toHex(), from: from || '', confirmations: 0, nonce: (0, utils_1.toBN)(tx.nonce).toNumber(), gasLimit: bignumber_1.BigNumber.from(tx.gasLimit || '0'), gasPrice: bignumber_1.BigNumber.from(0), data: (0, utils_1.dataToString)(data), value: bignumber_1.BigNumber.from(tx.value || '0'), chainId: 13939, wait: (confirmations) => { return this.provider._resolveTransactionReceipt(extrinsic.hash.toHex(), result.status.asInBlock.toHex(), from); } }); }) .catch(({ message, result }) => { reject(message); }); }) .catch((error) => { reject(error && error.message); }); }); }); } /** * * @param message The message to sign * @returns A promise that resolves to the signed hash of the message */ signMessage(message) { return __awaiter(this, void 0, void 0, function* () { const evmAddress = yield this.queryEvmAddress(); return this._signMessage(evmAddress, message); }); } _signMessage(evmAddress, message) { return __awaiter(this, void 0, void 0, function* () { if (!evmAddress) { return logger.throwError('No binding evm address'); } const messagePrefix = '\x19Ethereum Signed Message:\n'; if (typeof message === 'string') { message = (0, strings_1.toUtf8Bytes)(message); } const msg = (0, util_1.u8aToHex)((0, bytes_1.concat)([ (0, strings_1.toUtf8Bytes)(messagePrefix), (0, strings_1.toUtf8Bytes)(String(message.length)), message ])); if (!this.signingKey.signRaw) { return logger.throwError('Need to implement signRaw method'); } const result = yield this.signingKey.signRaw({ address: evmAddress, data: msg, type: 'bytes' }); return (0, bytes_1.joinSignature)(result.signature); }); } _signTypedData(domain, types, // eslint-disable-next-line @typescript-eslint/no-explicit-any value) { return __awaiter(this, void 0, void 0, function* () { return logger.throwError('_signTypedData is unsupported', logger_1.Logger.errors.UNSUPPORTED_OPERATION, { operation: '_signTypedData' }); }); } } exports.Signer = Signer; //# sourceMappingURL=Signer.js.map