@reef-defi/evm-provider
Version:
316 lines • 13.6 kB
JavaScript
"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