PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo/node_modules/micro-eth-signer/src

Просмотр файла: tx-validator.ts

import { Address, RawTxMap, add0x } from './index.js';
import { parseDecimal } from './formatters.js';

export type Unit = 'eth' | 'wei' | 'gwei';
type SNB = string | number | bigint;
export type HumanizedTx = {
  from?: string;
  to: string;
  value: SNB;
  maxFeePerGas: SNB;
  maxPriorityFeePerGas: SNB;
  nonce: SNB;
  data?: string;
  gasLimit?: SNB;
  amountUnit?: Unit;
  maxFeePerGasUnit?: Unit;
  maxPriorityFeePerGasUnit?: Unit;
  chainId?: number;
};

const GWEI_PRECISION = 9;
const ETHER_PRECISION = 18;
const GWEI = 10n ** BigInt(GWEI_PRECISION);
const ETHER = 10n ** BigInt(ETHER_PRECISION);
// const MICROETH = 10n ** 12n;

const MAX_AMOUNT = ETHER * 100000000n; // 100m ether
const MAX_GAS_PRICE = Number(GWEI * 10000n); // 10,000 gwei. Arbitrage HFT bots can use more.
// etherscan.io/chart/gasprice
const MIN_GAS_LIMIT = 21000;
const MAX_GAS_LIMIT = 20000000; // 20m wei. It's dynamic; a block limit in 2021 is 12m.
const MAX_NONCE = 10000000; // 10M
const MAX_DATA_SIZE = 10000000;

function minmax(val: bigint, min: bigint, max: bigint, err?: string): true | string;
function minmax(val: number, min: number, max: number, err?: string): true | string;
function minmax(
  val: number | bigint,
  min: number | bigint,
  max: number | bigint,
  err?: string
): true | string {
  if (!err) err = `>= ${min} and <= ${max}`;
  if (Number.isNaN(val) || val < min || val > max) throw new Error(`Must be ${err}`);
  return true;
}

function ensureNot16x(val: SNB, isBig = false) {
  if (typeof val === 'string' && val.startsWith('0x')) {
    return isBig ? BigInt(val) : Number.parseInt(val, 16);
  }
  return val;
}

const checks = {
  nonce(num: number) {
    return minmax(num, 0, MAX_NONCE);
  },
  maxFeePerGas(num: number) {
    return minmax(num, 1, MAX_GAS_PRICE, '>= 1 wei and < 10000 gwei');
  },
  maxPriorityFeePerGas(num: number) {
    return minmax(num, 0, MAX_GAS_PRICE, '>= 1 wei and < 10000 gwei');
  },
  gasLimit(num: number) {
    return minmax(num, MIN_GAS_LIMIT, MAX_GAS_LIMIT);
  },
  to(addr: string) {
    if (addr.length !== 40 && addr.length !== 42)
      throw new Error('Address length must be 40 or 42 symbols');
    addr = add0x(addr);
    if (!/^0x[0-9a-f]+$/i.test(addr)) throw new Error('Address must be hex');
    if (!Address.verifyChecksum(addr)) throw new Error('Address checksum does not match');
    return true;
  },
  value(num: bigint) {
    return minmax(num, 0n, MAX_AMOUNT, '>= 0 and < 100M eth');
  },
  data(val?: string) {
    if (typeof val === 'string' && val.length > MAX_DATA_SIZE) throw new Error('Data is too big');
    return true;
  },
  chainId(num?: number) {
    if (!num) return true;
    return minmax(num, 1, 2 ** 32 - 1, '>= 1 and <= 2**32-1');
  },
};

function parseHex(val: string) {
  if (val === '0x') val = '0x00';
  return Number.parseInt(val, 16);
}

export function parseUnit(val: SNB, unit: Unit) {
  const str = ensureNot16x(val, true).toString();
  if (unit === 'wei') return BigInt(str);
  let precision: number;
  if (unit === 'gwei') precision = GWEI_PRECISION;
  else if (unit === 'eth') precision = ETHER_PRECISION;
  else throw new Error(`Wrong unit name: ${unit}`);
  return parseDecimal(str, precision);
}

// Raw transaction to humanized
const r2h = {
  nonce: parseHex,
  maxFeePerGas: parseHex,
  gasLimit: parseHex,
  to: (val: string): string => Address.checksum(val),
  value: (val: string): bigint => BigInt(val),
  data: (val: string): string => val,
  chainId: (val: string): number => (val ? parseHex(val) : 1),
};

// Humanized to raw.
const h2r = {
  nonce(val: SNB): number {
    return Number.parseInt(ensureNot16x(val).toString());
  },
  maxFeePerGas(val: SNB, opts?: Partial<HumanizedTx>): bigint {
    return parseUnit(val, (opts && opts.maxFeePerGasUnit) || 'gwei');
  },
  maxPriorityFeePerGas(val: SNB, opts?: Partial<HumanizedTx>): bigint {
    return parseUnit(val, (opts && opts.maxPriorityFeePerGasUnit) || 'gwei');
  },
  gasLimit(val: SNB): number {
    return Number.parseInt(ensureNot16x(val).toString()) || MIN_GAS_LIMIT;
  },
  to(val: string, opts?: Partial<HumanizedTx>): string {
    if (opts && opts.from && opts.from === val) throw new Error('Must differ from sender address');
    return val;
  },
  value(val: SNB, opts?: Partial<HumanizedTx>): bigint {
    return parseUnit(val, (opts && opts.amountUnit) || 'eth');
  },
  data(val?: string): string {
    return val || '';
  },
  chainId(val: string) {
    return Number.parseInt(val) || 1;
  },
};
type h2rf = keyof typeof h2r;

function hasOwnProperty<X extends {}, Y extends PropertyKey>(
  obj: X,
  prop: Y
): obj is X & Record<Y, unknown> {
  return obj.hasOwnProperty(prop);
}

function numberToHexUnpadded(num: number | bigint): string {
  let hex = num.toString(16);
  hex = hex.length & 1 ? `0${hex}` : hex;
  return hex;
}

function dataToString(snb: SNB) {
  if (snb == null) return '';
  if (typeof snb === 'string') return snb;
  if (typeof snb === 'number' || typeof snb === 'bigint') return numberToHexUnpadded(snb);
  throw new Error('Invalid type');
}

class TransactionFieldError extends Error {
  constructor(
    message: string,
    readonly errors: Record<string, string>
  ) {
    super(message + '. ' + JSON.stringify(errors));
  }
}

const requiredFields = ['maxFeePerGas', 'maxPriorityFeePerGas', 'to', 'value', 'nonce'];
const optionFields = {
  value: ['amountUnit'],
  to: ['from'],
  maxFeePerGas: ['maxFeePerGasUnit'],
  maxPriorityFeePerGas: ['maxPriorityFeePerGasUnit'],
  chainId: [],
};
const allOptionFields = Object.values(optionFields).flat();

export function createTxMapFromFields(fields: HumanizedTx): RawTxMap {
  // prettier-ignore
  const normalized = {} as RawTxMap;
  const errors: Record<string, string> = {};
  requiredFields.forEach((f) => {
    if (fields[f as h2rf] == null) errors[f] = 'Cannot be empty';
  });
  Object.keys(fields).forEach((f) => {
    if (allOptionFields.includes(f)) return;
    const field = f as h2rf;
    const opts: Record<string, SNB> = {};
    if (hasOwnProperty(optionFields, field)) {
      const list = optionFields[field] as (keyof HumanizedTx)[];
      for (const optionalField of list) {
        const ofVal = fields[optionalField];
        if (ofVal != null) opts[optionalField] = ofVal;
      }
    }
    const val = fields[field];
    try {
      const normVal = h2r[field](val as any, opts);
      // @ts-ignore
      checks[field](normVal);
      normalized[field] = dataToString(normVal);
    } catch (error: any) {
      errors[field] = error.messages || error.message;
    }
  });

  if (Object.keys(errors).length) throw new TransactionFieldError('Invalid transaction', errors);
  Object.keys(normalized).forEach((f) => {
    const field = f as keyof RawTxMap;
    if (field === 'accessList') return;
    normalized[field] = add0x(normalized[field]!);
  });
  const raw: RawTxMap = Object.assign(
    {
      nonce: '0x',
      to: '0x',
      value: '0x',
      gasLimit: '0x5208',
      maxFeePerGas: '0x',
      data: '0x',
      v: '0x',
      r: '0x',
      s: '0x',
      chainId: 1,
    },
    normalized
  );
  return raw;
}

export function validateField(field: h2rf, val: SNB, opts?: Partial<HumanizedTx>) {
  const normVal = h2r[field](val as any, opts);
  // @ts-ignore
  checks[field](normVal);
  return dataToString(normVal);
}
export function validateFields(raw: RawTxMap) {
  Object.keys(raw).forEach((f) => {
    const field = f as keyof RawTxMap;
    if (field === 'accessList') return;
    const fn = r2h[field as keyof typeof r2h];
    if (typeof fn === 'function') {
      const value = raw[field];
      const normVal = fn(value || '');
      checks[field as keyof typeof checks](normVal as never);
    }
  });
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!