text:
import CSSKeywordValue from './CSSKeywordValue';
import CSSMathInvert from './CSSMathInvert';
import CSSMathMax from './CSSMathMax';
import CSSMathMin from './CSSMathMin';
import CSSMathProduct from './CSSMathProduct';
import CSSMathSum from './CSSMathSum';
import CSSStyleValue from './CSSStyleValue';
import CSSUnitValue from './CSSUnitValue';
import StylePropertyMap from './StylePropertyMap';
import units from './units';

export default function polyfill(window) {
	if (!window.CSS) window.CSS = class CSS {}

	Object.keys(units).forEach(
		unit => {
			if (!(unit in window.CSS)) {
				window.CSS[unit] = value => new CSSUnitValue(value, unit);
			}
		}
	);

	defineProperty(
		window.CSSRule.prototype,
		'styleMap',
		context => context.style
	);

	defineProperty(
		window.Element.prototype,
		'attributeStyleMap',
		context => context.style
	);

	defineProperty(
		window.Element.prototype,
		'computedStyleMap',
		context => getComputedStyle(context)
	);

	if (!window.CSSKeywordValue) window.CSSKeywordValue = CSSKeywordValue;
	if (!window.CSSMathInvert) window.CSSMathInvert = CSSMathInvert;
	if (!window.CSSMathMax) window.CSSMathMax = CSSMathMax;
	if (!window.CSSMathMin) window.CSSMathMin = CSSMathMin;
	if (!window.CSSMathProduct) window.CSSMathProduct = CSSMathProduct;
	if (!window.CSSMathSum) window.CSSMathSum = CSSMathSum;
	if (!window.CSSStyleValue) window.CSSStyleValue = CSSStyleValue;
	if (!window.CSSUnitValue) window.CSSUnitValue = CSSUnitValue;
	if (!window.StylePropertyMap) window.StylePropertyMap = StylePropertyMap;

	function defineProperty(prototype, property, getStyle) {
		if (!(property in prototype)) {
			Object.defineProperty(prototype, property, {
				configurable: true,
				enumerable: true,
				get() {
					const computedStyleMap = Object.create(StylePropertyMap.prototype);

					computedStyleMap.style = getStyle(this);

					return computedStyleMap;
				}
			});
		}
	}
}


text:
import parseAsValue from './parse-as-value';

export default class StylePropertyMap {
	get(...args) {
		if (args.length < 1) {
			throw new TypeError(`Failed to execute 'get' on 'StylePropertyMapReadOnly': 1 argument required, but only ${args.length} present.`);
		}

		const [ property ] = args;
		const value = this.style[property];

		if (value) {
			return parseAsValue(value);
		}

		return null;
	}

	set(...args) {
		if (args.length < 2) {
			throw new TypeError(`Failed to execute 'set' on 'StylePropertyMap': 2 arguments required, but only ${args.length} present.`);
		}

		const [ property, value ] = args;

		this.style[property] = String(value);
	}

	constructor() {
		throw new TypeError('Illegal constructor');
	}
}

repo_name: csstools/css-typed-om

repo_language: JavaScript

mime_type: text/x-java


text:
import units from './units';
import CSSUnitValue from './CSSUnitValue';
import CSSKeywordValue from './CSSKeywordValue';

export default string => {
	const unitParsingMatch = String(string).match(unitParsingMatcher);

	if (unitParsingMatch) {
		const [, value, unit] = unitParsingMatch;

		return new CSSUnitValue(value, unitKeys[unitValues.indexOf(unit || '')]);
	}

	return new CSSKeywordValue(string);
}

const unitKeys = Object.keys(units);
const unitValues = Object.values(units);
const unitParsingMatcher = new RegExp(`^([-+]?[0-9]*\.?[0-9]+)(${unitValues.join('|')})?$`);


text:
const _values = new WeakMap();

export default class CSSMathMax {
	get operator() {
		return 'max';
	}

	get values() {
		return _values.get(this);
	}

	toString() {
		return `max(${_values.get(this).join(', ')})`
	}

	constructor(...values) {
		_values.set(this, values);
	}
}


text:
export default class CSSStyleValue {
	constructor() {
		throw new TypeError('Illegal constructor');
	}
}


text:
import units from './units';
import CSSNumericValue from './CSSNumericValue'

const _value = new WeakMap();
const _unit = new WeakMap();

export default class CSSUnitValue extends CSSNumericValue {
	get value() {
		return _value.get(this);
	}

	set value(newValue) {
		_value.set(this, getFiniteNumber(newValue));
	}

	get unit() {
		return _unit.get(this);
	}

	toString() {
		return `${this.value}${units[this.unit]}`;
	}

	constructor(...args) {
		super()

		if (args.length < 2) {
			throw new TypeError(`Failed to construct 'CSSUnitValue': 2 arguments required, but only ${args.length} present.`);
		}

		_value.set(this, getFiniteNumber(args[0]));
		_unit.set(this, getUnit(args[1]));
	}
}

Object.defineProperties(CSSUnitValue.prototype, {
	value: { enumerable: true },
	unit: { enumerable: true }
});

function getFiniteNumber(value) {
	if (isNaN(value) || Math.abs(value) === Infinity) {
		throw new TypeError(`Failed to set the 'value' property on 'CSSUnitValue': The provided double value is non-finite.`);
	}

	return Number(value);
}

function getUnit(unit) {
	if (!Object.keys(units).includes(unit)) {
		throw new TypeError(`Failed to construct 'CSSUnitValue': Invalid unit: ${unit}`);
	}

	return unit
}


text:
const _value = new WeakMap();

export default class CSSMathInvert {
	get operator() {
		return 'invert';
	}

	get value() {
		return _value.get(this);
	}

	toString() {
		return `calc(1 / ${_value.get(this)})`
	}

	constructor(value) {
		_value.set(this, value)
	}
}


text:
import CSSMathInvert from './CSSMathInvert'

const _values = new WeakMap();

export default class CSSMathProduct {
	get operator() {
		return 'product';
	}

	get values() {
		return _values.get(this);
	}

	toString() {
		return `calc(${_values.get(this).reduce(
			(contents, value) => `${value instanceof CSSMathInvert
				? `${contents
						? `${contents} / `
					: '1 / '
				}${value.value}`
			: `${contents
					? `${contents} * `
				: ''}${value}`
			}`,
			''
		)})`
	}

	constructor(...values) {
		_values.set(this, values);
	}
}


text:
import CSSMathMax from './CSSMathMax'
import CSSMathMin from './CSSMathMin'
import CSSUnitValue from './CSSUnitValue';
import CSSMathProduct from './CSSMathProduct'
import CSSMathInvert from './CSSMathInvert'
import CSSMathSum from './CSSMathSum'

export default class CSSNumericValue {
	add(...args) {
		const Constructor = this.constructor
		const result = new Constructor(this.value, this.unit)
		const values = []

		for (let arg of args) {
			if (arg instanceof Constructor) {
				if (values.length || result.unit !== arg.unit) {
					values.push(arg)
				} else {
					result.value += arg.value
				}
			} else if (
				arg instanceof CSSMathProduct ||
				arg instanceof CSSMathMax ||
				arg instanceof CSSMathMin ||
				arg instanceof CSSMathInvert
			 ) {
				values.push(arg)
			} else {
				return null
			}
		}

		return values.length ? new CSSMathSum(result, ...values) : result
	}

	div(...args) {
		const Constructor = this.constructor
		const result = new Constructor(this.value, this.unit)
		const values = []

		for (let arg of args) {
			if (typeof arg === 'number') {
				arg = new CSSUnitValue(arg, 'number')
			}

			if (arg instanceof Constructor) {
				if (values.length || result.unit !== arg.unit && arg.unit !== 'number') {
					values.push(arg)
				} else {
					result.value /= arg.value
				}
			} else {
				return null
			}
		}

		return values.length ? new CSSMathProduct(result, ...values.map(
			value => new CSSMathInvert(value)
		)) : result
	}

	max(...args) {
		const result = new CSSUnitValue(this.value, this.unit)
		const values = [result]

		for (let arg of args) {
			if (arg instanceof CSSUnitValue) {
				if (values.length > 1 || result.unit !== arg.unit) {
					values.push(arg)
				} else {
					result.value = Math.max(result.value, arg.value)
				}
			} else {
				return null
			}
		}

		return values.length > 1 ? new CSSMathMax(...values) : result
	}

	min(...args) {
		const result = new CSSUnitValue(this.value, this.unit)
		const values = [result]

		for (let arg of args) {
			if (arg instanceof CSSUnitValue) {
				if (values.length > 1 || result.unit !== arg.unit) {
					values.push(arg)
				} else {
					result.value = Math.min(result.value, arg.value)
				}
			} else {
				return null
			}
		}

		return values.length > 1 ? new CSSMathMin(...values) : result
	}

	mul(...args) {
		const Constructor = this.constructor
		const result = new Constructor(this.value, this.unit)
		const values = []

		for (let arg of args) {
			if (typeof arg === 'number') {
				arg = new CSSUnitValue(arg, 'number')
			}

			if (arg instanceof Constructor) {
				if (values.length || result.unit !== arg.unit && arg.unit !== 'number') {
					values.push(arg)
				} else {
					result.value *= arg.value
				}
			} else {
				return null
			}
		}

		return values.length ? new CSSMathProduct(result, ...values) : result
	}

	sub(...args) {
		const Constructor = this.constructor
		const result = new Constructor(this.value, this.unit)
		const values = []

		for (let arg of args) {
			if (arg instanceof Constructor) {
				if (values.length || result.unit !== arg.unit) {
					values.push(new Constructor(arg.value * -1, arg.unit))
				} else {
					result.value -= arg.value
				}
			} else {
				return null
			}
		}

		return values.length ? new CSSMathSum(result, ...values) : result
	}
}


text:
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 2022-01-14

- Data migration on updating relayer:

```bash
$ ./util/update/update.sh
```

*This replaces all previous update steps!*

## 2021-12-14

- Run `node lib/data_migrations/2021-12-14-empty-blocks.js` to reindex all historical empty blocks.

```bash
$ cd migrations
$ goose postgres "user=<USERNAME> password=<PASSWORD> dbname=aurora sslmode=disable" up
```

## 2021-12-08

A new column `from` was added into the `event` table. Check out this PR [#120](https://github.com/aurora-is-near/aurora-relayer/pull/120) for more info.

To update the `event` table, execute:

- Create `from` column:

```bash
$ cd migrations
$ goose postgres "user=<USERNAME> password=<PASSWORD> dbname=aurora sslmode=disable" up-to 20211208103001
```

- Run `node lib/data_migrations/2021-12-02-event.js` to reindex all historical events.
- Set `from` column to `NOT NULL`:

```bash
$ cd migrations
$ goose postgres "user=<USERNAME> password=<PASSWORD> dbname=aurora sslmode=disable" up-to 20211208110338
```
- Finally check the status of the migration:
```bash
goose postgres "user=<USERNAME> password=<PASSWORD> dbname=aurora sslmode=disable" status
```
## 2021-12-02

New table to handle subscriptions.
To update your existing schema, execute:

```sql
CREATE TABLE subscription (
	sec_websocket_key varchar COLLATE "default",
	id                varchar COLLATE "default",
	type              varchar COLLATE "default",
	ip                varchar COLLATE "default",
	filter            jsonb NULL
);

CREATE UNIQUE INDEX subscription_sec_websocket_key_type_filter_index_idx ON subscription (sec_websocket_key, type, filter);
```
## 2021-12-01

Stored procedure needs to be reloaded [Issue #133](https://github.com/aurora-is-near/aurora-relayer/issues/133)

- `etc/schema/functions/eth_getTransactionReceipt.sql`

## 2021-10-19

Some schema changes were necessary in order to be able to index
[prehistory]. To update your existing schema, execute:

```sql
ALTER TABLE block ALTER COLUMN near_hash DROP NOT NULL;
ALTER TABLE block ALTER COLUMN timestamp DROP NOT NULL;
ALTER DOMAIN instant DROP CONSTRAINT instant_check;
ALTER DOMAIN instant ADD CONSTRAINT instant_check CHECK (value = timestamptz '1970-01-01T00:00:00Z' OR value > timestamptz '2015-07-30T00:00:00Z');
```

In addition, reload the following stored procedures:

- `etc/schema/functions/eth_getBlockByNumber.sql`

[prehistory]: https://github.com/aurora-is-near/aurora-relayer-dumps

repo_name: aurora-is-near/aurora-relayer

repo_language: TypeScript

mime_type: text/plain


text:
{
  "files": [
    "src/index.ts",
    "src/indexer_backend.ts",
    "src/indexer_worker.ts",
    "src/prehistory.ts",
    "src/decs.d.ts",
    "src/data_migrations/2021-12-02-event.ts",
    "src/data_migrations/2021-12-14-empty-blocks.ts",
  ],
  "extends": "@tsconfig/node16/tsconfig.json",
  "compilerOptions": {
    "outDir": "./lib",
    "target": "es2021",
    "module": "es2020",
    "moduleResolution": "node",
    "declaration": true,
    "isolatedModules": true,
    "strict": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": true,
    "noUnusedParameters": false,
    "resolveJsonModule": true,
    "allowJs": true
  },
  "typedocOptions": {
    "entryPoints": ["index.ts"],
    "includeVersion": false,
    "hideGenerator": true
  }
}


text:
export default {
  testEnvironment: 'node',
  testTimeout: 90000,
  preset: 'ts-jest/presets/default-esm',
  globals: {
    'ts-jest': {
      useESM: true,
    },
  },
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
  transform: {
    "\\.[jt]sx?$": "ts-jest"
  },
  transformIgnorePatterns: [
    "node_modules/(?!(@aurora-is-near))"
  ],
  globalSetup: '<rootDir>/test/setup.js',
};


text:
* Ahmed Ali <ahmed@aurora.dev>
* Anton Paisov <anton@aurora.dev>
* Arto Bendiken <arto@aurora.dev>
* Illia Polosukhin <ilblackdragon@gmail.com>


text:
version: '3.8'
services:
  database:
    container_name: "${NEAR_ENV:-testnet}_database"
    image: auroraisnear/relayer-database
    restart: unless-stopped
    ports:
      - '127.0.0.1:5432:5432'
    volumes:
      - database:/var/lib/postgresql/data
  indexer:
    container_name: "${NEAR_ENV:-testnet}_indexer"
    image: auroraisnear/relayer-endpoint
    restart: unless-stopped
    init: true
    depends_on:
      - database
    environment:
      - WAIT_HOSTS=database:5432
      - WAIT_BEFORE=1
      - NEAR_ENV=${NEAR_ENV:-testnet}
      - NODE_ENV=${NEAR_ENV:-testnet}
    volumes:
      - ./config:/srv/aurora/relayer/config
    extra_hosts:
      - host.docker.internal:host-gateway # See: https://stackoverflow.com/a/43541732
    entrypoint: ["sh", "-c", "util/indexer/indexer | node lib/indexer_backend.js"]
  endpoint:
    container_name: "${NEAR_ENV:-testnet}_endpoint"
    image: auroraisnear/relayer-endpoint
    restart: unless-stopped
    init: true
    depends_on:
      - database
    environment:
      - WAIT_HOSTS=database:5432
      - WAIT_BEFORE=1
      - NEAR_ENV=${NEAR_ENV:-testnet}
      - NODE_ENV=${NEAR_ENV:-testnet}
    ports:
      - '127.0.0.1:8545:8545'
    volumes:
      - ./config:/srv/aurora/relayer/config
    entrypoint: ["node", "lib/index.js"]
volumes:
  database:


text:
TSC = npx tsc
TYPEDOC = npx typedoc

# The default target:

all: lib/index.js

.PHONY: all

# Rules for development:

lib/%.js: src/%.ts tsconfig.json package.json package-lock.json
	$(TSC)

lib/%.d.ts: lib/%.js

.docker/docker-entrypoint-initdb.d/init.txt: etc/schema.sql
	cat $< | cut -d' ' -f2 | xargs cat > $@

docs:
	$(TYPEDOC) --out $@

clean:
	@rm -Rf docs lib/*.js lib/*.d.ts *~

distclean: clean

mostlyclean: clean

maintainer-clean: clean

.PHONY: clean distclean mostlyclean maintainer-clean

.SECONDARY:
.SUFFIXES:


text:
> **⚠ WARNING: This repository is deprecated. Please visit [Aurora Relayer V2](https://github.com/aurora-is-near/relayer2-public).

# Aurora Relayer

[![Project license](https://img.shields.io/badge/License-Public%20Domain-blue.svg)](https://creativecommons.org/publicdomain/zero/1.0/)
[![Discord](https://img.shields.io/discord/490367152054992913?label=Discord)](https://discord.gg/jNjHYUF8vw)
[![Lints](https://github.com/aurora-is-near/aurora-relayer/actions/workflows/lints.yml/badge.svg)](https://github.com/aurora-is-near/aurora-relayer/actions/workflows/lints.yml)

Implements a JSON-RPC server compatible with Ethereum's
[Web3 API](https://eth.wiki/json-rpc/API) for
[Aurora Engine](https://github.com/aurora-is-near/aurora-engine)
instances deployed on the NEAR Protocol.

## Prerequisites

- [Docker] and [Docker Compose]

[Docker]: https://docs.docker.com/engine/install/
[Docker Compose]: https://docs.docker.com/compose/install/

## Usage

### Usage for Testnet

To run the relayer locally, execute:

```bash
NEAR_ENV=testnet docker-compose up
```

You can customize the configuration by copying [`config/testnet.yaml`] to
`config/local.yaml` and editing that file. (The configuration settings in
`config/local.yaml` override the defaults from `config/testnet.yaml`.)

[`config/testnet.yaml`]: https://github.com/aurora-is-near/aurora-relayer/blob/master/config/testnet.yaml

### Usage for Testnet without Docker

1. `npm i`
2. Install postgresql and create database, [example](https://github.com/aurora-is-near/aurora-relayer/blob/master/.docker/docker-entrypoint-initdb.d/init.sh) of how to create database
3. Go to indexer directory `cd util/indexer`
4. Compile `indexer` binary, `go build`
5. Return to root directory of the project
6. Run Node.js server `NEAR_ENV=testnet node lib/index.js`
7. Run indexer `sh -c util/indexer/indexer | NEAR_ENV=testnet node lib/indexer_backend.js`

### Usage for Testnet without Docker and with live changes

To see local changes without restarting the Node.js server, instead of step 6, execute:
```
NEAR_ENV=testnet npm run start
npm run build:watch
```

### Run local specs

1. Apply mocks to local database `sh test/fixtures/mocks_init.sh`
2. Run indexer `sh -c util/indexer/indexer | NEAR_ENV=testnet node lib/indexer_backend.js`
3. Run local specs `npm run test`

### Usage for LocalNet

To run the relayer locally, first start [nearcore] and then execute:

```bash
NEAR_ENV=localnet docker-compose up
```

You can customize the configuration by copying [`config/localnet.yaml`] to
`config/local.yaml` and editing that file. (The configuration settings in
`config/local.yaml` override the defaults from `config/localnet.yaml`.)

[nearcore]: https://docs.near.org/docs/community/contribute/contribute-nearcore
[`config/localnet.yaml`]: https://github.com/aurora-is-near/aurora-relayer/blob/master/config/localnet.yaml

### Endpoint URL

The relayer's HTTP endpoint is served up on the TCP port 8545 by default.

For example, you can send a Web3 JSON-RPC request to the endpoint using
[HTTPie], as follows:

```bash
http post http://localhost:8545 jsonrpc=2.0 id=1 method=eth_blockNumber params:='[]'
```

[HTTPie]: https://httpie.io

## Configuration

To customize configuration settings, create a `config/local.yaml` file.

Configuration settings are loaded firstly from `config/default.yaml`, then from
`config/$NEAR_ENV.yaml`, and lastly from `config/local.yaml`. Settings in
files loaded later override the same settings from files loaded earlier.

### Configuring a signing key

To be able to call `eth_sendRawTransaction`, you must have a NEAR account and
signing key on the network you are relaying to, and said NEAR account must have
a sufficient Ⓝ balance to be able to send transactions.

To configure the signing account and private key, place the JSON key file
into the `config/` directory and edit `config/local.yaml` as follows:

```yaml
signer: you.testnet
signerKey: config/you.testnet.json
```

If you're using the [NEAR CLI], you will find your signing keys stored as JSON
key files under your `$HOME/.near-credentials/` directory.

[NEAR CLI]: https://docs.near.org/docs/tools/near-cli

## Status

Method | Status | Notes
------ | ------ | -----
[`web3_clientVersion`] | ✅ |
[`web3_sha3`] | ✅ |
[`net_listening`] | ✅ |
[`net_peerCount`] | ✅ |
[`net_version`] | ✅ |
[`eth_accounts`] | ✅ |
[`eth_blockNumber`] | ✅ |
[`eth_call`] | ✅ |
[`eth_chainId`] | ✅ |
[`eth_coinbase`] | ✅ |
[`eth_compileLLL`] | ❌ | Unsupported
[`eth_compileSerpent`] | ❌ | Unsupported
[`eth_compileSolidity`] | ❌ | Unsupported
[`eth_estimateGas`] | ✅ |
[`eth_gasPrice`] | ✅ |
[`eth_getBalance`] | ✅ |
[`eth_getBlockByHash`] | ✅ |
[`eth_getBlockByNumber`] | ✅ |
[`eth_getBlockTransactionCountByHash`] | ✅ |
[`eth_getBlockTransactionCountByNumber`] | ✅ |
[`eth_getCode`] | ✅ |
[`eth_getCompilers`] | ✅ |
[`eth_getFilterChanges`] | ✅ |
[`eth_getFilterLogs`] | ✅ |
[`eth_getLogs`] | ✅ |
[`eth_getProof`] | ❌ | EIP-1186
[`eth_getStorageAt`] | ✅ |
[`eth_getTransactionByBlockHashAndIndex`] | ✅ |
[`eth_getTransactionByBlockNumberAndIndex`] | ✅ |
[`eth_getTransactionByHash`] | ✅ |
[`eth_getTransactionCount`] | ✅ |
[`eth_getTransactionReceipt`] | ✅ |
[`eth_getUncleByBlockHashAndIndex`] | ✅ |
[`eth_getUncleByBlockNumberAndIndex`] | ✅ |
[`eth_getUncleCountByBlockHash`] | ✅ |
[`eth_getUncleCountByBlockNumber`] | ✅ |
[`eth_getWork`] | ❌ | Unsupported
[`eth_hashrate`] | ✅ |
[`eth_mining`] | ✅ |
[`eth_newBlockFilter`] | ✅ |
[`eth_newFilter`] | ✅ |
[`eth_newPendingTransactionFilter`] | ✅ |
[`eth_pendingTransactions`] | ✅ | [Undocumented](https://github.com/ethereum/go-ethereum/issues/1648#issuecomment-130591933)
[`eth_protocolVersion`] | ✅ |
[`eth_sendRawTransaction`] | ✅ |
[`eth_sendTransaction`] | ❌ | Unsupported
[`eth_sign`] | ❌ | Unsupported
[`eth_signTransaction`] | ❌ | Unsupported
[`eth_signTypedData`] | ❌ | Unsupported
[`eth_submitHashrate`] | ❌ | Unsupported
[`eth_submitWork`] | ❌ | Unsupported
[`eth_syncing`] | ✅ |
[`eth_uninstallFilter`] | ✅ |
[`db_getHex`] | ❌ | Deprecated
[`db_getString`] | ❌ | Deprecated
[`db_putHex`] | ❌ | Deprecated
[`db_putString`] | ❌ | Deprecated
[`shh_addToGroup`] | ❌ | Discontinued
[`shh_getFilterChanges`] | ❌ | Discontinued
[`shh_getMessages`] | ❌ | Discontinued
[`shh_hasIdentity`] | ❌ | Discontinued
[`shh_newFilter`] | ❌ | Discontinued
[`shh_newGroup`] | ❌ | Discontinued
[`shh_newIdentity`] | ❌ | Discontinued
[`shh_post`] | ❌ | Discontinued
[`shh_uninstallFilter`] | ❌ | Discontinued
[`shh_version`] | ❌ | Discontinued
[`txpool_content`] | ✅ | Geth extension
[`txpool_inspect`] | ✅ | Geth extension
[`txpool_status`] | ✅ | Geth extension
[`parity_pendingTransactions`] | ✅ | Parity extension

**Legend**: ❌ = not supported. 🚧 = work in progress. ✅ = supported.

[`web3_clientVersion`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#web3_clientversion
[`web3_sha3`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#web3_sha3
[`net_listening`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#net_listening
[`net_peerCount`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#net_peercount
[`net_version`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#net_version
[`eth_accounts`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_accounts
[`eth_blockNumber`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber
[`eth_call`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call
[`eth_chainId`]: https://eips.ethereum.org/EIPS/eip-695
[`eth_coinbase`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_coinbase
[`eth_compileLLL`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_compilelll
[`eth_compileSerpent`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_compileserpent
[`eth_compileSolidity`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_compile_solidity
[`eth_estimateGas`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_estimategas
[`eth_gasPrice`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gasprice
[`eth_getBalance`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getbalance
[`eth_getBlockByHash`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash
[`eth_getBlockByNumber`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber
[`eth_getBlockTransactionCountByHash`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblocktransactioncountbyhash
[`eth_getBlockTransactionCountByNumber`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblocktransactioncountbynumber
[`eth_getCode`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getcode
[`eth_getCompilers`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getcompilers
[`eth_getFilterChanges`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getfilterchanges
[`eth_getFilterLogs`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getfilterlogs
[`eth_getLogs`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs
[`eth_getProof`]: https://eips.ethereum.org/EIPS/eip-1186
[`eth_getStorageAt`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getstorageat
[`eth_getTransactionByBlockHashAndIndex`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyblockhashandindex
[`eth_getTransactionByBlockNumberAndIndex`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyblocknumberandindex
[`eth_getTransactionByHash`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash
[`eth_getTransactionCount`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactioncount
[`eth_getTransactionReceipt`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt
[`eth_getUncleByBlockHashAndIndex`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getunclebyblockhashandindex
[`eth_getUncleByBlockNumberAndIndex`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getunclebyblocknumberandindex
[`eth_getUncleCountByBlockHash`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getunclecountbyblockhash
[`eth_getUncleCountByBlockNumber`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getunclecountbyblocknumber
[`eth_getWork`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getwork
[`eth_hashrate`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_hashrate
[`eth_mining`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_mining
[`eth_newBlockFilter`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_newblockfilter
[`eth_newFilter`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_newfilter
[`eth_newPendingTransactionFilter`]: https://openethereum.github.io/JSONRPC-eth-module.html#eth_newpendingtransactionfilter
[`eth_pendingTransactions`]: https://github.com/ethereum/wiki/issues/685
[`eth_protocolVersion`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_protocolversion
[`eth_sendRawTransaction`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction
[`eth_sendTransaction`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction
[`eth_sign`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign
[`eth_signTransaction`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_signtransaction
[`eth_signTypedData`]: https://eips.ethereum.org/EIPS/eip-712
[`eth_submitHashrate`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_submithashrate
[`eth_submitWork`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_submitwork
[`eth_syncing`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_syncing
[`eth_uninstallFilter`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_uninstallfilter
[`db_getHex`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#db_gethex
[`db_getString`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#db_getstring
[`db_putHex`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#db_puthex
[`db_putString`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#db_putstring
[`shh_addToGroup`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_addtogroup
[`shh_getFilterChanges`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_getfilterchanges
[`shh_getMessages`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_getmessages
[`shh_hasIdentity`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_hasidentity
[`shh_newFilter`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_newfilter
[`shh_newGroup`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_newgroup
[`shh_newIdentity`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_newidentity
[`shh_post`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_post
[`shh_uninstallFilter`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_uninstallfilter
[`shh_version`]: https://ethereum.org/en/developers/docs/apis/json-rpc/#shh_post
[`txpool_content`]: https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content
[`txpool_inspect`]: https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect
[`txpool_status`]: https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status
[`parity_pendingTransactions`]: https://openethereum.github.io/JSONRPC-parity-module#parity_pendingtransactions

## Contributing

1. Clone the repo
2. Create your branch
3. Add your changes
4. Run tests (`npm run test`, the local server must be running for the tests to pass). Fix if you broke something. Add your own tests
5. Format your code (`npm run format`)
6. Run linter (`npm run lint`)
7. Build changes (`npm run build`)
8. Commit your Changes (`git commit -m 'Resolved an issue'`)
9. Push to the Branch (`git push origin new_feature`)
10. Open a Pull Request


text:
* @artob
* @spilin


text:
---
name: ENGINE bug report
about: Create a report to help us improve
title: "[BUG]"
labels: ''
assignees: ''

---

**Describe the bug**
Please provide a short description of the bug.

**To Reproduce**
Steps to reproduce the behavior, e.g.:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Technical Details (please complete relevant fields):**
- NEARcore commit/branch
- Rust version (if local)
- Docker (if using docker)
- Mainnet/Testnet/Betanet/local
- Name of the dApp used
- Wallet name and version
- Browser name and version
- Tokens used
- RPC Public/Private
- Technology used (JS, TS, etc.)
- Gas related issue — Yes/No

**Additional context**
Add any other context about the problem here.


text:
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


text:
---
on:
  push:
    branches:
      - master
      - mainnet
      - testnet

name: Buildchain
jobs:

  buildchain:
    name: Buildchain
    runs-on: [self-hosted, light]
    steps:

      - name: Clone the repository
        uses: actions/checkout@v3

      - name: Clone buildchain repository
        uses: actions/checkout@v3
        with:
          repository: aurora-is-near/buildchain
          ssh-key: ${{ secrets.BUILDCHAIN_REPO_SSH_KEY }}
          path: buildchain

      - name: Install remaphore
        run: go get -u -v github.com/aurora-is-near/remaphore/cmd/remaphore

      - name: Install uuidgen
        run: apt-get update && apt-get install uuid-runtime

      - name: Configure
        run: |
          mkdir -p /etc/buildchain-client
          echo "$NATS_CREDS" > /etc/buildchain-client/nats.creds
          echo "$REMAPHORE_CONFIG" > /etc/buildchain-client/remaphore.conf
        env:
          NATS_CREDS: ${{secrets.BUILDCHAIN_REMAPHORE_NATS_CREDS}}
          REMAPHORE_CONFIG: ${{secrets.BUILDCHAIN_CLIENT_CONFIG}}

      - name: Run build
        run: |
          ./buildchain/client/run.sh buildchain.builder \
            build_relayer "$GITHUB_REF_NAME $GITHUB_SHA"
        env:
          BUILDCHAIN_CLIENT_REMAPHORE_CONFIG: /etc/buildchain-client/remaphore.conf
        timeout-minutes: 15

      - name: Run testing & beta deploy
        run: |
          ./buildchain/client/run.sh buildchain.relayer_tester \
            test_relayer "$GITHUB_REF_NAME $GITHUB_SHA"
        env:
          BUILDCHAIN_CLIENT_REMAPHORE_CONFIG: /etc/buildchain-client/remaphore.conf
        timeout-minutes: 70

      - name: Cleanup
        if: ${{ always() }}
        run: rm -rf /etc/buildchain-client || true


text:
---
on:
  push:
    branches:
      - master
  pull_request:

name: Lints
jobs:
  eslint:
    name: ESLint & Prettier
    runs-on: [self-hosted, light]
    steps:
      - name: Clone the repository
        uses: actions/checkout@v3
      - name: Cache node modules
        id: cache-dep
        uses: actions/cache@v3
        env:
          cache-name: cache-node-modules
        with:
          path: node_modules
          key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
      - name: Install JS dependencies
        if: steps.cache-dep.outputs.cache-hit != 'true'
        run: npm install
      - name: Run ESLint
        run: npm run lint
      - name: Run prettier
        run: npx prettier --check src


text:
---
on:
  push:
    branches:
      - master
  pull_request:

name: Tests for testnet
jobs:
  prebuild:
    name: Prebuild
    runs-on: [self-hosted, heavy]
    if: ${{ contains(github.event.pull_request.labels.*.name, 'safe to test') || github.ref == 'refs/heads/master' }}
    steps:
      - name: Clone the repository
        uses: actions/checkout@v3
      - name: Install upx
        run: apt-get install -y upx
      - name: Prebuild
        run: ./.ci/e2e/runner/prebuild.sh
        env:
          TEST_NET_KEY: ${{ secrets.TEST_NET_KEY }}
      - name: Teardown
        if: ${{ always() }}
        run: ./.ci/e2e/runner/teardown.sh
  tests:
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: Jest specs
            run: |
              npm test
    name: ${{ matrix.name }}
    runs-on: [self-hosted, heavy]
    needs: prebuild
    steps:
      - name: Clone the repository
        uses: actions/checkout@v3
      - name: Setup
        run: ./.ci/e2e/runner/setup.sh
        env:
          TEST_NET_KEY: ${{ secrets.TEST_NET_KEY }}
      - name: Cache node modules
        id: cache-dep
        uses: actions/cache@v3
        env:
          cache-name: cache-node-modules
        with:
          path: node_modules
          key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
      - name: Install JS dependencies
        if: steps.cache-dep.outputs.cache-hit != 'true'
        run: npm install
      - name: Build app
        run: npm run build
      - name: Run Test
        run: ${{ matrix.run }}
      - name: Print database logs
        if: ${{ always() }}
        run: ./.ci/e2e/show_logs.sh database
      - name: Print indexer logs
        if: ${{ always() }}
        run: ./.ci/e2e/show_logs.sh indexer
      - name: Print endpoint logs
        if: ${{ always() }}
        run: ./.ci/e2e/show_logs.sh endpoint
      - name: Teardown
        if: ${{ always() }}
        run: ./.ci/e2e/runner/teardown.sh
  cleanup:
    name: Cleanup
    runs-on: [self-hosted, heavy]
    needs: tests
    if: ${{ always() }}
    steps:
      - name: Clone the repository
        uses: actions/checkout@v3
      - name: Cleanup
        run: ./.ci/e2e/runner/cleanup.sh


text:
---
on:
  push:
    branches:
      - master
  pull_request:

name: Tests
jobs:
  prebuild:
    name: Prebuild
    runs-on: [self-hosted, heavy]
    steps:
      - name: Clone the repository
        uses: actions/checkout@v3
      - name: Prebuild
        run: ./.ci/runner/prebuild.sh
      - name: Teardown
        if: ${{ always() }}
        run: ./.ci/runner/teardown.sh
  tests:
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: Test 1
            run: |
              ENDPOINT_HOST=$(cat .ci/workdir/endpoint.txt)
              curl -X POST \
                -H 'Content-Type: application/json' \
                -d '{"jsonrpc":"2.0","id":"1","method":"eth_blockNumber","params":[]}' \
                http://${ENDPOINT_HOST}:8545
          - name: Test 2
            run: |
              ENDPOINT_HOST=$(cat .ci/workdir/endpoint.txt)
              curl -X POST \
                -H 'Content-Type: application/json' \
                -d '{"jsonrpc":"2.0","id":"1","method":"eth_blockNumber","params":[]}' \
                http://${ENDPOINT_HOST}:8545
    name: ${{ matrix.name }}
    runs-on: [self-hosted, heavy]
    needs: prebuild
    steps:
      - name: Clone the repository
        uses: actions/checkout@v3
      - name: Setup
        run: ./.ci/runner/setup.sh
      - name: Run Test
        run: ${{ matrix.run }}
      - name: Print nearcore logs
        if: ${{ always() }}
        run: ./.ci/show_logs.sh nearcore
      - name: Print database logs
        if: ${{ always() }}
        run: ./.ci/show_logs.sh database
      - name: Print indexer logs
        if: ${{ always() }}
        run: ./.ci/show_logs.sh indexer
      - name: Print endpoint logs
        if: ${{ always() }}
        run: ./.ci/show_logs.sh endpoint
      - name: Teardown
        if: ${{ always() }}
        run: ./.ci/runner/teardown.sh
  cleanup:
    name: Cleanup
    runs-on: [self-hosted, heavy]
    needs: tests
    if: ${{ always() }}
    steps:
      - name: Clone the repository
        uses: actions/checkout@v3
      - name: Cleanup
        run: ./.ci/runner/cleanup.sh


text:
FROM node:16-alpine
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.9.0/wait /wait
RUN apk add --no-cache $(apk search --no-cache --no-progress -q "postgresql[[:digit:]]*-client" | sort | tail -n1)
RUN chmod +x /wait
RUN mkdir -p /srv/aurora
RUN mkdir -p /srv/aurora/.near-credentials/mainnet
RUN mkdir -p /srv/aurora/.near-credentials/testnet
RUN mkdir -p /srv/aurora/.near-credentials/betanet
RUN ln -s testnet /srv/aurora/.near-credentials/default
RUN mkdir -p /srv/aurora/relayer
RUN chown -R node:node /srv/aurora
WORKDIR /srv/aurora/relayer
COPY --chown=node:node package*.json ./
USER node
RUN npm ci --only=production
COPY --chown=node:node . .
CMD /wait && node lib/index.js
EXPOSE 8545


text:
FROM postgres:13.4-alpine
ENV POSTGRES_PASSWORD postgres
COPY .docker/docker-entrypoint-initdb.d/init.* /docker-entrypoint-initdb.d/


text:
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOF
  CREATE USER aurora PASSWORD 'aurora';
  CREATE DATABASE aurora;
  GRANT ALL PRIVILEGES ON DATABASE aurora TO aurora;
EOF
psql -v ON_ERROR_STOP=1 --username aurora --dbname aurora < /docker-entrypoint-initdb.d/init.txt


text:
DROP DOMAIN IF EXISTS address CASCADE;
CREATE DOMAIN address AS bytea
  CHECK (length(value) = 20);

DROP DOMAIN IF EXISTS blockno CASCADE;
CREATE DOMAIN blockno AS int8
  CHECK (value >= 0);

DROP DOMAIN IF EXISTS chainid CASCADE;
CREATE DOMAIN chainid AS int4
  CHECK (value >= 1);

DROP DOMAIN IF EXISTS hash CASCADE;
CREATE DOMAIN hash AS bytea
  CHECK (length(value) = 32);

DROP DOMAIN IF EXISTS instant CASCADE;
CREATE DOMAIN instant AS timestamptz
  CHECK (value = timestamptz '1970-01-01T00:00:00Z' OR value > timestamptz '2015-07-30T00:00:00Z');

DROP DOMAIN IF EXISTS u64 CASCADE;
CREATE DOMAIN u64 AS numeric(20, 0)
  CHECK (value >= 0 AND value <= 18446744073709551615);

DROP DOMAIN IF EXISTS u256 CASCADE;
CREATE DOMAIN u256 AS numeric(78, 0)
  CHECK (value >= 0 AND value <= 115792089237316195423570985008687907853269984665640564039457584007913129639935);
DROP TYPE IF EXISTS block_result CASCADE;

CREATE TYPE block_result AS (
    "number" blockno,
    "hash" hash,
    "parentHash" hash,
    "nonce" bytea,           -- 8 bytes
    "sha3Uncles" hash,
    "logsBloom" bytea,       -- 256 bytes
    "transactionsRoot" hash,
    "stateRoot" hash,
    "receiptsRoot" hash,
    "miner" address,
    "difficulty" int2,
    "totalDifficulty" int2,
    "extraData" bytea,       -- 0 bytes
    "size" int4,
    "gasLimit" int8,
    "gasUsed" int8,
    "timestamp" int4,
    "mixHash" hash
);
DROP TYPE IF EXISTS filter_result CASCADE;

CREATE TYPE filter_result AS (
    "blockNumber" blockno,
    "blockHash" hash,
    "transactionIndex" int,
    "transactionHash" hash,
    "logIndex" int,
    "address" address,
    "topics" hash[4],
    "data" bytea,
    "removed" boolean
);
DROP TYPE IF EXISTS filter_type CASCADE;

CREATE TYPE filter_type AS ENUM ('block', 'event', 'transaction');
DROP TYPE IF EXISTS transaction_receipt CASCADE;

CREATE TYPE transaction_receipt AS (
    "blockNumber" blockno,
    "blockHash" hash,
    "transactionIndex" int,
    "transactionHash" hash,
    "from" address,
    "to" address,
    "gasUsed" u256,
    "cumulativeGasUsed" u256,
    "contractAddress" address,
    "logs" bigint[],
    "logsBloom" bytea, -- 256 bytes
    "status" smallint,
    "nearTransactionHash" hash,
    "nearReceiptHash" hash
);
DROP TYPE IF EXISTS transaction_result CASCADE;

CREATE TYPE transaction_result AS (
    "blockNumber" blockno,
    "blockHash" hash,
    "transactionIndex" int,
    "hash" hash,
    "from" address,
    "to" address,
    "gas" u256,
    "gasPrice" u256,
    "nonce" u256,
    "value" u256,
    "input" bytea,
    "v" u64,
    "r" u256,
    "s" u256
);
DROP TABLE IF EXISTS block CASCADE;

CREATE TABLE block (
  chain             chainid NOT NULL,
  id                blockno NOT NULL PRIMARY KEY,
  hash              hash NOT NULL UNIQUE,
  near_hash         hash NULL UNIQUE,
  timestamp         instant NULL, -- NULL for unknown (pre-HF)
  size              int4 NOT NULL,
  gas_limit         u256 NOT NULL,
  gas_used          u256 NOT NULL CHECK (gas_used <= gas_limit),
  parent_hash       hash NOT NULL UNIQUE,
  transactions_root hash NOT NULL,
  state_root        hash NOT NULL,
  receipts_root     hash NOT NULL
);

CREATE UNIQUE INDEX block_chain_id_idx ON block (chain, id);

CREATE INDEX block_timestamp_idx ON block USING btree (timestamp);
DROP TABLE IF EXISTS filter CASCADE;

CREATE TABLE filter (
  id                uuid NOT NULL PRIMARY KEY,
  type              filter_type NOT NULL,
  created_at        instant NOT NULL,
  created_by        inet NOT NULL,
  polled_at         instant NULL,
  poll_block        blockno NULL,
  from_block        blockno NULL,
  to_block          blockno NULL,
  addresses         address[] NULL CHECK (array_length(addresses, 1) > 0),
  topics            jsonb NULL
);
DROP TABLE IF EXISTS transaction CASCADE;

CREATE TABLE transaction (
  block             blockno NOT NULL REFERENCES block ON DELETE CASCADE,
  index             int NOT NULL CHECK (index >= 0),
  id                bigserial NOT NULL PRIMARY KEY,
  hash              hash NOT NULL UNIQUE,
  near_hash         hash NOT NULL,
  near_receipt_hash hash NULL,
  "from"            address NOT NULL,
  "to"              address NULL,
  nonce             u256 NOT NULL,
  gas_price         u256 NOT NULL,
  gas_limit         u256 NOT NULL,
  gas_used          u256 NOT NULL, -- FIXME: CHECK (gas_used <= gas_limit),
  value             u256 NOT NULL,
  input             bytea NULL CHECK (length(input) > 0),
  v                 u64 NULL,
  r                 u256 NULL,
  s                 u256 NULL,
  status            boolean NOT NULL,
  output            bytea NULL CHECK (length(output) > 0)
);

CREATE UNIQUE INDEX transaction_block_index_idx ON transaction (block, index);

CREATE INDEX transaction_block_idx ON transaction USING btree (block);

CREATE INDEX transaction_from_idx ON transaction USING btree ("from");

CREATE INDEX transaction_to_idx ON transaction USING btree ("to");
DROP TABLE IF EXISTS event CASCADE;

CREATE TABLE event (
  transaction       bigint NOT NULL REFERENCES transaction ON DELETE CASCADE,
  index             int NOT NULL CHECK (index >= 0),
  id                bigserial NOT NULL PRIMARY KEY,
  data              bytea NULL CHECK (length(data) > 0),
  "from"            address NULL,
  topics            hash[] NULL CHECK (array_length(topics, 1) > 0 AND array_length(topics, 1) <= 4)
);

CREATE UNIQUE INDEX event_transaction_index_idx ON event (transaction, index);
DROP FUNCTION IF EXISTS eth_blockNumber() RESTRICT;

DROP TABLE IF EXISTS subscription CASCADE;

CREATE TABLE subscription (
	sec_websocket_key varchar COLLATE "default",
	id                varchar COLLATE "default",
	type              varchar COLLATE "default",
	ip                varchar COLLATE "default",
	filter            jsonb NULL
);

CREATE UNIQUE INDEX subscription_sec_websocket_key_type_filter_index_idx ON subscription (sec_websocket_key, type, filter);

CREATE FUNCTION eth_blockNumber() RETURNS blockno AS $$
DECLARE
  block_id blockno;
BEGIN
  SELECT COALESCE(MAX(id), 0) FROM block INTO STRICT block_id;
  RETURN block_id;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getBlockByHash(hash) RESTRICT;

CREATE FUNCTION eth_getBlockByHash(block_hash hash) RETURNS block_result AS $$
DECLARE
  result block_result;
  block_id blockno;
BEGIN
  SELECT id FROM block WHERE hash = block_hash INTO STRICT block_id;
  SELECT * FROM eth_getBlockByNumber(block_id) INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getBlockByNumber(blockno) RESTRICT;

CREATE FUNCTION eth_getBlockByNumber(block_id blockno) RETURNS block_result AS $$
DECLARE
  result block_result;
BEGIN
  SELECT
      id,                           -- number
      hash,                         -- hash
      parent_hash,                  -- parentHash
      repeat('\000', 8)::bytea,     -- nonce
      '\x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347',    -- sha3Uncles keccak256(rlp.encode([]))
      repeat('\000', 256)::bytea,   -- logsBloom
      transactions_root,            -- transactionsRoot
      state_root,                   -- stateRoot
      receipts_root,                -- receiptsRoot
      repeat('\000', 20)::bytea,    -- miner
      0,                            -- difficulty
      0,                            -- totalDifficulty
      ''::bytea,                    -- extraData
      size,                         -- size
      gas_limit,                    -- gasLimit
      gas_used,                     -- gasUsed
      COALESCE(EXTRACT(EPOCH FROM timestamp), 0)::int4, -- timestamp
      repeat('\000', 32)::hash      -- mixHash
    FROM block
    WHERE id = block_id
    LIMIT 1
    INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getBlockTransactionCountByHash(hash) RESTRICT;

CREATE FUNCTION eth_getBlockTransactionCountByHash(block_hash hash) RETURNS bigint AS $$
DECLARE
  result bigint;
BEGIN
  -- FIXME: return NULL for unknown blocks
  SELECT COUNT(t.id)
    FROM transaction t
      LEFT JOIN block b ON t.block = b.id
    WHERE b.hash = block_hash
    INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getBlockTransactionCountByNumber(blockno) RESTRICT;

CREATE FUNCTION eth_getBlockTransactionCountByNumber(block_id blockno) RETURNS bigint AS $$
DECLARE
  result bigint;
BEGIN
  -- FIXME: return NULL for unknown blocks
  SELECT COUNT(id)
    FROM transaction
    WHERE block = block_id
    INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getFilterChanges_block(bytea) RESTRICT;
DROP FUNCTION IF EXISTS eth_getFilterChanges_event(bytea) RESTRICT;
DROP FUNCTION IF EXISTS eth_getFilterChanges_transaction(bytea) RESTRICT;

CREATE FUNCTION eth_getFilterChanges_block(filter_id bytea) RETURNS SETOF hash AS $$
DECLARE
  block_id blockno;
BEGIN
  SELECT coalesce(max(id), 0) FROM block INTO STRICT block_id;
  RETURN QUERY EXECUTE format('
    SELECT b.hash FROM block b
      WHERE b.id >= (SELECT coalesce(f.poll_block, f.from_block, 0) FROM filter f WHERE uuid_send(f.id) = %L)
        AND b.id <= (SELECT least(f.to_block, %L) FROM filter f WHERE uuid_send(f.id) = %L)
      ORDER BY b.id ASC;
  ', filter_id, block_id, filter_id);
  UPDATE filter SET poll_block = block_id + 1 WHERE uuid_send(id) = filter_id;
  RETURN;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;

CREATE FUNCTION eth_getFilterChanges_transaction(filter_id bytea) RETURNS SETOF hash AS $$
BEGIN
  RETURN; -- no pending transactions
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getFilterLogs_block(bytea) RESTRICT;
DROP FUNCTION IF EXISTS eth_getFilterLogs_event(bytea) RESTRICT;
DROP FUNCTION IF EXISTS eth_getFilterLogs_transaction(bytea) RESTRICT;

CREATE FUNCTION eth_getFilterLogs_block(filter_id bytea) RETURNS SETOF hash AS $$
DECLARE
  block_id blockno;
BEGIN
  SELECT coalesce(max(id), 0) FROM block INTO STRICT block_id;
  RETURN QUERY EXECUTE format('
    SELECT b.hash FROM block b
      WHERE b.id >= (SELECT coalesce(f.from_block, 0) FROM filter f WHERE uuid_send(f.id) = %L)
        AND b.id <= (SELECT least(f.to_block, %L) FROM filter f WHERE uuid_send(f.id) = %L)
      ORDER BY b.id ASC;
  ', filter_id, block_id, filter_id);
  RETURN;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;

CREATE FUNCTION eth_getFilterLogs_transaction(filter_id bytea) RETURNS SETOF hash AS $$
BEGIN
  RETURN; -- no pending transactions
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getTransactionByBlockHashAndIndex(hash, int) RESTRICT;

CREATE FUNCTION eth_getTransactionByBlockHashAndIndex(block_hash hash, transaction_index int) RETURNS transaction_result AS $$
DECLARE
  result transaction_result;
BEGIN
  SELECT
      b.id AS "blockNumber",
      b.hash AS "blockHash",
      t.index AS "transactionIndex",
      t.hash AS "hash",
      t.from AS "from",
      t.to AS "to",
      t.gas_limit AS "gas",
      t.gas_price AS "gasPrice",
      t.nonce AS "nonce",
      t.value AS "value",
      coalesce(t.input, '\x'::bytea) AS "input",
      t.v AS "v",
      t.r AS "r",
      t.s AS "s"
    FROM transaction t
      LEFT JOIN block b ON t.block = b.id
    WHERE b.hash = block_hash AND t.index = transaction_index
    LIMIT 1
    INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getTransactionByBlockNumberAndIndex(blockno, int) RESTRICT;

CREATE FUNCTION eth_getTransactionByBlockNumberAndIndex(block_id blockno, transaction_index int) RETURNS transaction_result AS $$
DECLARE
  result transaction_result;
BEGIN
  SELECT
      b.id AS "blockNumber",
      b.hash AS "blockHash",
      t.index AS "transactionIndex",
      t.hash AS "hash",
      t.from AS "from",
      t.to AS "to",
      t.gas_limit AS "gas",
      t.gas_price AS "gasPrice",
      t.nonce AS "nonce",
      t.value AS "value",
      coalesce(t.input, '\x'::bytea) AS "input",
      t.v AS "v",
      t.r AS "r",
      t.s AS "s"
    FROM transaction t
      LEFT JOIN block b ON t.block = b.id
    WHERE b.id = block_id AND t.index = transaction_index
    LIMIT 1
    INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getTransactionByHash(hash) RESTRICT;

CREATE FUNCTION eth_getTransactionByHash(transaction_hash hash) RETURNS transaction_result AS $$
DECLARE
  result transaction_result;
BEGIN
  SELECT
      b.id AS "blockNumber",
      b.hash AS "blockHash",
      t.index AS "transactionIndex",
      t.hash AS "hash",
      t.from AS "from",
      t.to AS "to",
      t.gas_limit AS "gas",
      t.gas_price AS "gasPrice",
      t.nonce AS "nonce",
      t.value AS "value",
      coalesce(t.input, '\x'::bytea) AS "input",
      t.v AS "v",
      t.r AS "r",
      t.s AS "s"
    FROM transaction t
      LEFT JOIN block b ON t.block = b.id
    WHERE t.hash = transaction_hash
    LIMIT 1
    INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getTransactionCount(address, blockno) RESTRICT;

CREATE FUNCTION eth_getTransactionCount(address address, block_id blockno) RETURNS bigint AS $$
DECLARE
  result bigint;
BEGIN -- TODO: use nonce column?
  SELECT COUNT(id) FROM transaction
    WHERE "from" = address AND block <= block_id
    INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getTransactionReceipt(hash) RESTRICT;

CREATE FUNCTION eth_getTransactionReceipt(transaction_hash hash) RETURNS transaction_receipt AS $$
DECLARE
  result transaction_receipt;
BEGIN
  SELECT
      b.id AS "blockNumber",
      b.hash AS "blockHash",
      t.index AS "transactionIndex",
      t.hash AS "transactionHash",
      t.from AS "from",
      t.to AS "to",
      t.gas_used AS "gasUsed",
      0::u256 AS "cumulativeGasUsed", -- TODO: tally?
      CASE WHEN (t.to IS NULL OR t.to = '\x0000000000000000000000000000000000000000') AND length(t.output) = 20 THEN t.output
           ELSE NULL
      END AS "contractAddress",
      NULL AS "logs",                 -- TODO: fetch event.id[]
      repeat('\000', 256)::bytea AS "logsBloom",
      CASE WHEN t.status THEN 1 ELSE 0 END AS "status",
      t.near_hash AS "nearTransactionHash",
      t.near_receipt_hash AS "nearReceiptHash"
    FROM transaction t
      LEFT JOIN block b ON t.block = b.id
    WHERE t.hash = transaction_hash
    LIMIT 1
    INTO STRICT result;
  RETURN result;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getUncleByBlockHashAndIndex(hash, int) RESTRICT;

CREATE FUNCTION eth_getUncleByBlockHashAndIndex(block_hash hash, uncle_index int) RETURNS block_result AS $$
BEGIN
  RETURN NULL; -- no uncle blocks
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getUncleByBlockNumberAndIndex(blockno, int) RESTRICT;

CREATE FUNCTION eth_getUncleByBlockNumberAndIndex(block_id blockno, uncle_index int) RETURNS block_result AS $$
BEGIN
  RETURN NULL; -- no uncle blocks
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getUncleCountByBlockHash(hash) RESTRICT;

CREATE FUNCTION eth_getUncleCountByBlockHash(block_hash hash) RETURNS bigint AS $$
BEGIN
  PERFORM id FROM block WHERE hash = block_hash LIMIT 1;
  IF FOUND THEN RETURN 0; ELSE RETURN NULL; END IF;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_getUncleCountByBlockNumber(blockno) RESTRICT;

CREATE FUNCTION eth_getUncleCountByBlockNumber(block_id blockno) RETURNS bigint AS $$
BEGIN
  PERFORM id FROM block WHERE id = block_id LIMIT 1;
  IF FOUND THEN RETURN 0; ELSE RETURN NULL; END IF;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_newBlockFilter(inet) RESTRICT;

CREATE FUNCTION eth_newBlockFilter(client_ip inet) RETURNS bytea AS $$
DECLARE
  block_id blockno;
  filter_uuid uuid;
BEGIN
  SELECT max(id) FROM block INTO STRICT block_id;
  INSERT INTO filter
      (id, type, created_at, created_by, poll_block)
    VALUES
      (gen_random_uuid(), 'block', now(), client_ip, block_id + 1)
    RETURNING id INTO STRICT filter_uuid;
  RETURN uuid_send(filter_uuid);
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_newFilter(inet, blockno, blockno, address[], jsonb) RESTRICT;

CREATE FUNCTION eth_newFilter(client_ip inet, from_block blockno, to_block blockno, addresses address[], topics jsonb) RETURNS bytea AS $$
DECLARE
  block_id blockno;
  filter_uuid uuid;
BEGIN
  SELECT max(id) FROM block INTO STRICT block_id;
  INSERT INTO filter
      (id, type, created_at, created_by, poll_block, from_block, to_block, addresses, topics)
    VALUES
      (gen_random_uuid(), 'event', now(), client_ip, block_id + 1, from_block, to_block, addresses, topics)
    RETURNING id INTO STRICT filter_uuid;
  RETURN uuid_send(filter_uuid);
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_newPendingTransactionFilter() RESTRICT;

CREATE FUNCTION eth_newPendingTransactionFilter() RETURNS bytea AS $$
BEGIN
  RETURN repeat('\000', 16)::bytea; -- the empty filter
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;
DROP FUNCTION IF EXISTS eth_uninstallFilter(inet, bigint) RESTRICT;

CREATE FUNCTION eth_uninstallFilter(client_ip inet, filter_id bytea) RETURNS boolean AS $$
BEGIN
  DELETE FROM filter
    WHERE uuid_send(id) = filter_id AND created_by = client_ip;
  RETURN FOUND;
END;
$$ LANGUAGE plpgsql VOLATILE PARALLEL UNSAFE;


text:
/* This is free and unencumbered software released into the public domain. */

import pg from 'pg';

import sql from 'sql-bricks-postgres';

const sqlConvert = (sql as any).convert;
(sql as any).convert = (val: unknown) => {
  if (val instanceof Uint8Array) {
    return `'\\x${Buffer.from(val).toString('hex')}'`;
  }
  return sqlConvert(val);
};

export { pg, sql };


text:
/* This is free and unencumbered software released into the public domain. */

import { Config, parseConfig } from './config.js';

import { StaticPool } from 'node-worker-threads-pool';

import { ConnectEnv } from '@aurora-is-near/engine';
import { program } from 'commander';
import externalConfig from 'config';
import FastPriorityQueue from 'fastpriorityqueue';
import * as readline from 'readline';

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace NodeJS {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface ProcessEnv extends ConnectEnv {}
  }
}

main(process.argv, process.env).catch((error: Error) => {
  const errorMessage = error.message.startsWith('<')
    ? error.name
    : error.message;
  console.error(errorMessage);
  process.exit(70); // EX_SOFTWARE
});

async function main(argv: string[], env: NodeJS.ProcessEnv) {
  program
    .option('-d, --debug', 'enable debug output')
    .option('-v, --verbose', 'enable verbose output')
    .option('-f, --force', 'reindex already indexed blocks')
    .option(
      '--database <url>',
      `specify PostgreSQL database URL (default: none)`
    )
    .option(
      '--network <network>',
      `specify NEAR network ID (default: "${env.NEAR_ENV || 'local'}")`
    )
    .option(
      '--endpoint <url>',
      `specify NEAR RPC endpoint URL (default: "${env.NEAR_URL || ''}")`
    )
    .option(
      '--engine <account>',
      `specify Aurora Engine account ID (default: "${
        env.AURORA_ENGINE || 'aurora.test.near'
      }")`
    )
    .parse(argv);

  const opts = program.opts() as Config;
  const [network, config] = parseConfig(
    opts,
    (externalConfig as unknown) as Config,
    env
  );

  if (config.debug) {
    for (const source of externalConfig.util.getConfigSources()) {
      console.error(`Loaded configuration file ${source.name}.`);
    }
    console.error('Configuration:', config);
  }

  const workers = new StaticPool({
    size: 4, // TODO: make this configurable
    task: './lib/indexer_worker.js',
    workerData: {
      config,
      network,
      env: {
        NEAR_ENV: env.NEAR_ENV,
        NEAR_URL: env.NEAR_URL,
      },
    },
  });

  const queue = new FastPriorityQueue((a: number, b: number) => {
    return a > b;
  });

  let inputOpen = true;
  const input = readline.createInterface({
    input: process.stdin,
    terminal: false,
  });
  input
    .on('line', (line: string) => {
      queue.add(JSON.parse(line));
    })
    .on('close', () => {
      inputOpen = false;
    });

  while (!queue.isEmpty() || inputOpen) {
    const blockData = queue.poll();
    if (blockData === undefined) {
      // The queue is empty, wait for more input:
      await new Promise((resolve) => setTimeout(resolve, 10));
      continue;
    }
    await workers.exec(blockData);
  }
  process.exit(0);
}


text:
/* This is free and unencumbered software released into the public domain. */

import { Config } from './config.js';

import * as nats from 'nats';

const codec = nats.JSONCodec();

export class Bus {
  protected broker?: nats.NatsConnection;

  constructor(public readonly config: Config) {
    this._init();
  }

  protected async _init(): Promise<void> {
    this.broker = await nats.connect({ servers: this.config.broker });
  }

  async publishError(method: string, ip: string, code?: string): Promise<void> {
    if (!ip) return;
    this.broker?.publish(`error.${method}`, codec.encode({ method, ip, code }));
  }
}


text:
#!/usr/bin/env node
/* This is free and unencumbered software released into the public domain. */

import { createApp } from './app.js';
import { Config, parseConfig } from './config.js';
import { pg } from './database.js';

import { ConnectEnv, Engine } from '@aurora-is-near/engine';
import { program } from 'commander';
import externalConfig from 'config';
import pino from 'pino';

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace NodeJS {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface ProcessEnv extends ConnectEnv {
      CF_API_TOKEN?: string; // Cloudflare API token
      CF_ACCOUNT_ID?: string; // Cloudflare account ID
      CF_LIST_ID?: string; // Cloudflare IP blacklist ID
    }
  }
}

async function main(argv: string[], env: NodeJS.ProcessEnv): Promise<void> {
  program
    .option('-d, --debug', 'enable debug output')
    .option('-v, --verbose', 'enable verbose output')
    .option(
      '--database <url>',
      `specify PostgreSQL database URL (default: none)`
    )
    .option('--port <port>', `specify port to listen to (default: ${8545})`)
    .option(
      '--network <network>',
      `specify NEAR network ID (default: "${env.NEAR_ENV || 'local'}")`
    )
    .option(
      '--endpoint <url>',
      `specify NEAR RPC endpoint URL (default: "${env.NEAR_URL || ''}")`
    )
    .option(
      '--engine <account>',
      `specify Aurora Engine account ID (default: "${
        env.AURORA_ENGINE || 'aurora.test.near'
      }")`
    )
    .option(
      '--signer <account>',
      `specify signer account ID (default: "${
        env.NEAR_MASTER_ACCOUNT || 'test.near'
      }")`
    )
    .option('--signer-key <path>', `specify path to signer key JSON file`)
    .parse(argv);

  const [network, config] = parseConfig(
    program.opts() as Config,
    (externalConfig as unknown) as Config,
    env
  );

  if (config.debug) {
    for (const source of externalConfig.util.getConfigSources()) {
      console.error(`Loaded configuration file ${source.name}.`);
    }
    console.error('Configuration:', config);
  }

  process.on('SIGHUP', () => {
    (global as any).blacklistConfig = null;
  });

  const logger = pino();
  logger.info('starting server');

  if (config.database) {
    try {
      const sql = new pg.Client(config.database);
      await sql.connect();
      await sql.query('SELECT 1'); // test connectivity
    } catch (error: any) {
      console.error(
        `aurora-relayer: Invalid database configuration: ${
          (error as Error).message
        }`
      );
      if (config.debug) console.error(error);
      process.exit(78); // EX_CONFIG
    }
  }

  const engine = await Engine.connect(
    {
      network: network.id,
      endpoint: config.endpoint,
      contract: config.engine,
      signer: config.signer,
    },
    env
  );

  if (Array.isArray(config.signerKeys) && config.signerKeys.length > 0) {
    engine.keyStore.loadKeyFiles(config.signerKeys);
    console.error(`Loaded signer key files ${config.signerKeys.join(', ')}.`);
  } else if (config.signerKey) {
    engine.keyStore.loadKeyFile(config.signerKey);
    console.error(`Loaded signer key file ${config.signerKey}.`);
  }

  const app = await createApp(config, logger, engine);
  app.listen(config.port, () => {
    if (config.verbose || config.debug) {
      console.error(
        `Relayer for the NEAR ${network.label} listening at http://localhost:${config.port}...`
      );
    }
  });
}

main(process.argv, process.env).catch((error: Error) => {
  console.error(`aurora-relayer: ${error.message}`);
  process.exit(78); // EX_CONFIG
});


text:
/* This is free and unencumbered software released into the public domain. */

import { Config, parseConfig } from './config.js';
import { pg, sql } from './database.js';
import { computeBlockHash } from './utils.js';

import {
  AccountID,
  base58ToBytes,
  ConnectEnv,
  NetworkConfig,
} from '@aurora-is-near/engine';
import { program } from 'commander';
import externalConfig from 'config';
import pino, { Logger } from 'pino';

interface BlockRow {
  chain: number;
  id: number;
  hash: Uint8Array;
  near_hash: Uint8Array | null;
  timestamp: Date | null;
  size: number;
  gas_limit: number;
  gas_used: number;
  parent_hash: Uint8Array;
  transactions_root: Uint8Array;
  state_root: Uint8Array;
  receipts_root: Uint8Array;
}

const logger = pino(pino.destination(2));

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace NodeJS {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface ProcessEnv extends ConnectEnv {}
  }
}

export class PrehistoryIndexer {
  protected readonly archive: pg.Client;
  protected blockID = 0;

  constructor(
    public readonly config: Config,
    public readonly network: NetworkConfig,
    public readonly logger: Logger
  ) {
    if (!network.archiveURL) {
      console.error(`No archive database for the network ${network.id}`);
    }
    this.archive = new pg.Client(network.archiveURL);
  }

  async run(startBlockID: number, mode?: string): Promise<void> {
    const contractID = AccountID.parse(this.config.engine).unwrap();
    await this.archive.connect();

    const step = Number(this.config.batchSize || 1000);
    for (
      let blockID = startBlockID;
      blockID < this.network.firstBlock;
      blockID += step
    ) {
      const query = sql
        .select()
        .from('blocks')
        .where(
          sql(`block_height >= ${blockID} AND block_height < ${blockID + step}`)
        )
        .limit(step);
      //console.debug(query.toString()); // DEBUG

      const result = await this.archive.query(query.toParams());
      const rows: any = {};
      for (const row of result.rows) {
        rows[Number(row.block_height)] = row;
      }
      for (let i = 0; i < step; i++) {
        const blockID_ = blockID + i;
        if (blockID_ >= this.network.firstBlock) break;
        const blockHash = computeBlockHash(
          blockID_ as number,
          contractID.toString(),
          this.network.chainID
        );
        const parentHash =
          blockID_ == 0
            ? Buffer.alloc(32)
            : computeBlockHash(
                (blockID_ as number) - 1,
                contractID.toString(),
                this.network.chainID
              );

        const row = rows[blockID_];
        const columns: BlockRow = {
          chain: this.network.chainID,
          id: blockID_,
          hash: blockHash,
          near_hash: row ? base58ToBytes(row.block_hash) : null,
          timestamp:
            blockID_ == 0
              ? new Date(this.network.genesisDate!)
              : row
              ? new Date(Number(row.block_timestamp) / 1_000_000.0)
              : null,
          size: 0,
          gas_limit: 0,
          gas_used: 0,
          parent_hash: parentHash,
          transactions_root: Buffer.alloc(32),
          state_root: Buffer.alloc(32),
          receipts_root: Buffer.alloc(32),
        };
        console.log(this.serialize(columns));
      }
    } // for
    process.exit(0); // EX_OK
  }

  serialize(query: BlockRow) {
    const items = Object.keys(query).map((key) => {
      const value = (query as any)[key];
      if (value === null) {
        return '\\N';
      }
      if (value instanceof Date) {
        return value.toISOString();
      }
      if (value instanceof Uint8Array) {
        return `\\\\x${Buffer.from(value).toString('hex')}`;
      }
      return value;
    });
    return items.join('\t');
  }
}

async function main(argv: string[], env: NodeJS.ProcessEnv) {
  program
    .option('-d, --debug', 'enable debug output')
    .option('-v, --verbose', 'enable verbose output')
    .option(
      '--network <network>',
      `specify NEAR network ID (default: "${env.NEAR_ENV || 'local'}")`
    )
    .option(
      '--engine <account>',
      `specify Aurora Engine account ID (default: "${
        env.AURORA_ENGINE || 'aurora.test.near'
      }")`
    )
    .option(
      '-B, --block <block>',
      `specify block height to begin indexing from (default: 0)`
    )
    .option(
      '--batch-size <batchSize>',
      `specify batch size for fetching block metadata (default: 1000)`
    )
    .parse(argv);

  const opts = program.opts() as Config;
  const [network, config] = parseConfig(
    opts,
    (externalConfig as unknown) as Config,
    env
  );
  const blockID = opts.block !== undefined ? parseInt(opts.block as string) : 0;

  if (config.debug) {
    for (const source of externalConfig.util.getConfigSources()) {
      console.error(`Loaded configuration file ${source.name}.`);
    }
    console.error('Configuration:', config);
  }

  logger.info('starting prehistory indexer');
  const indexer = new PrehistoryIndexer(config, network, logger);
  await indexer.run(blockID, 'follow');
}

main(process.argv, process.env).catch((error: Error) => {
  const errorMessage = error.message.startsWith('<')
    ? error.name
    : error.message;
  logger.error(errorMessage);
  process.exit(70); // EX_SOFTWARE
});


text:
/* This is free and unencumbered software released into the public domain. */

import { parseEVMRevertReason } from './utils.js';

export function unsupported(method: string): void {
  throw new UnsupportedMethod(method);
}

export function unimplemented(method: string): void {
  throw new UnimplementedMethod(method);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function expectArgs(
  args: any[] | undefined,
  min: number,
  max: number,
  minMsg?: string
): any[] {
  if (args && args.length < min) throw new MissingArgument(min - 1, minMsg);
  if (args && args.length > max) throw new TooManyArguments(max);
  //if (args.length > max) throw new InvalidArguments();
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return args as any[];
}

export abstract class CodedError extends Error {
  constructor(
    public readonly code: number,
    message: string,
    public readonly data?: any
  ) {
    super(message);
    Object.setPrototypeOf(this, CodedError.prototype);
  }
}

export class UnexpectedError extends CodedError {
  constructor(message: string) {
    super(-32603, message);
    Object.setPrototypeOf(this, UnexpectedError.prototype);
  }
}

export abstract class ExpectedError extends CodedError {
  constructor(code: number, message: string, data?: any) {
    super(code, message, data);
    Object.setPrototypeOf(this, ExpectedError.prototype);
  }
}

export class UnsupportedMethod extends ExpectedError {
  constructor(method: string) {
    super(-32601, `Unsupported method: ${method}`);
    Object.setPrototypeOf(this, UnsupportedMethod.prototype);
  }
}

export class UnimplementedMethod extends ExpectedError {
  constructor(method: string) {
    super(-32601, `Unimplemented method: ${method}`);
    Object.setPrototypeOf(this, UnimplementedMethod.prototype);
  }
}

export class MissingArgument extends ExpectedError {
  constructor(index: number, message?: string) {
    super(-32602, message || `missing value for required argument ${index}`);
    Object.setPrototypeOf(this, MissingArgument.prototype);
  }
}

export class TooManyArguments extends ExpectedError {
  constructor(maxCount: number) {
    super(-32602, `too many arguments, want at most ${maxCount}`);
    Object.setPrototypeOf(this, TooManyArguments.prototype);
  }
}

export class InvalidArguments extends ExpectedError {
  constructor(message?: string) {
    super(-32602, message || `Invalid method parameter(s).`);
    Object.setPrototypeOf(this, InvalidArguments.prototype);
  }
}

export class InvalidAddress extends ExpectedError {
  constructor(message?: string) {
    super(-32602, message || `Invalid address.`);
    Object.setPrototypeOf(this, InvalidAddress.prototype);
  }
}

export class UnknownFilter extends ExpectedError {
  constructor(_id: string) {
    super(-32000, `filter not found`);
    Object.setPrototypeOf(this, UnknownFilter.prototype);
  }
}

export class TransactionError extends ExpectedError {
  constructor(message: string) {
    super(-32000, message);
    Object.setPrototypeOf(this, TransactionError.prototype);
  }
}

export class RevertError extends ExpectedError {
  constructor(reason: Uint8Array) {
    const revertReason = parseEVMRevertReason(reason);
    if (typeof revertReason === 'string') {
      super(3, revertReason as string);
    } else {
      super(3, `execution reverted`, revertReason);
    }
    Object.setPrototypeOf(this, RevertError.prototype);
  }
}

export class GasPriceTooLow extends ExpectedError {
  constructor(message?: string) {
    super(-32000, message || `Gas price too low.`);
    Object.setPrototypeOf(this, InvalidAddress.prototype);
  }
}

export class LimitExceeded extends ExpectedError {
  constructor(limit: number, message?: string) {
    super(-32005, message || `query returned more than ${limit} results`);
    Object.setPrototypeOf(this, InvalidAddress.prototype);
  }
}


text:
/* This is free and unencumbered software released into the public domain. */

import { blacklist } from './blacklist.js';

import expressIpFilter from 'express-ipfilter';
const { IpDeniedError, IpFilter } = expressIpFilter;

import expressPinoLogger from 'express-pino-logger';

import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz', 16);

import { Logger } from 'pino';

export function setRequestID() {
  return (req: any, res: any, next: any): void => {
    const id =
      req.headers['x-request-id'] || req.headers['cf-request-id'] || nanoid();
    req['id'] = id;
    res.set('X-Request-ID', id);
    next();
  };
}

export function logger(logger: Logger): any {
  return expressPinoLogger({
    logger,
    genReqId: (req) => {
      return req.id;
    },
    reqCustomProps: (req, _res) => {
      const body = (req as any).body;
      if (!body || req?.method !== 'POST' || req?.url !== '/') return {};
      return {
        rpc: {
          method: body?.method,
          params: body?.params,
        },
      };
    },
  });
}

export function blacklistIPs(): any {
  const ipLookup = () => {
    return [...(blacklist('IPs') || [])];
  };
  return IpFilter(ipLookup, {
    mode: 'deny',
    log: false,
    trustProxy: true,
  });
}

export function handleErrors() {
  return (err: any, req: any, res: any, next: any): void => {
    if (err instanceof IpDeniedError) {
      // TODO: req.log.info(...);
      res.status(403).end();
    } else {
      next();
    }
  };
}

export default {
  setRequestID,
  logger,
  blacklistIPs,
  handleErrors,
};


text:
/* This is free and unencumbered software released into the public domain. */

import { Config } from './config.js';
import { matchTopics } from './topics.js';
import { createServer } from './server.js';
import pg from 'pg';
import expressWs from 'express-ws';
import { Engine, exportJSON } from '@aurora-is-near/engine';
import { Logger } from 'pino';

const syncInterval = 3000;
const newHeadsInterval = 200;
const retriesCount = 50;

export async function createWsServer(
  config: Config,
  logger: Logger,
  engine: Engine,
  app: any
): Promise<boolean> {
  // const pgPool = new pg.Pool({ "connectionString": config.database });
  const pgClient = new pg.Client(config.database);
  pgClient.connect();
  pgClient.query('DELETE FROM subscription');
  const jaysonWsServer = createServer(config, logger, engine);
  const expressWsApp = expressWs(app);
  expressWsApp.app.ws('/', function (ws: any, req) {
    ws.id = req.headers['sec-websocket-key'];
    ws.on('message', function (msg: string) {
      try {
        const parsedObject = JSON.parse(msg);
        jaysonWsServer.call(
          parsedObject,
          req,
          function (error: any, success: any) {
            ws.send(JSON.stringify(error || success));
          }
        );
      } catch (e) {
        ws.send('Invalid request');
      }
    });
    ws.on('close', function close() {
      pgClient.query('DELETE FROM subscription WHERE sec_websocket_key = $1', [
        ws.id,
      ]);
    });
    ws.on('error', () => {
      ws.send('Error connecting to WebSocket');
    });
  });

  setTimeout(sync, syncInterval, pgClient, expressWsApp);
  const blockHeight = ((await engine.getBlockHeight()).unwrap() as number) + 10;
  setTimeout(
    notifyNewHeads,
    newHeadsInterval,
    pgClient,
    expressWsApp,
    jaysonWsServer,
    blockHeight,
    0
  );
  return true;
}

async function sync(pgClient: any, expressWsApp: any, blockNumber: any) {
  const maxIdResult = await pgClient.query(
    'SELECT MAX(id)::int AS max_id FROM block'
  );
  const maxID = maxIdResult.rows[0].max_id || 0;
  forSubscriptions(pgClient, 'syncing', function (row: any) {
    const payload = {
      jsonrpc: '2.0',
      subscription: row.sub_id,
      result: { syncing: maxID > blockNumber },
    };
    sendPayload(expressWsApp, row.ws_key, row.sub_id, JSON.stringify(payload));
  });
  setTimeout(sync, syncInterval, pgClient, expressWsApp, maxID);
}

async function notifyNewHeads(
  pgClient: any,
  expressWsApp: any,
  jaysonWsServer: any,
  blockNumber: number,
  retries: number
) {
  const result = await pgClient.query(
    'SELECT 1 FROM block WHERE id = $1 LIMIT 1',
    [blockNumber]
  );
  if (result.rows.length > 0) {
    const blockBody = {
      jsonrpc: '2.0',
      id: 1,
      method: 'eth_getBlockByNumber',
      params: [blockNumber, true],
    };
    jaysonWsServer.call(blockBody, {}, function (error: any, success: any) {
      forSubscriptions(pgClient, 'newHeads', function (row: any) {
        sendPayload(expressWsApp, row.ws_key, row.sub_id, error || success);
      });
    });

    const logsBody = {
      jsonrpc: '2.0',
      id: 1,
      method: 'eth_getLogs',
      params: [
        {
          fromBlock: blockNumber,
          toBlock: blockNumber,
        },
      ],
    };
    jaysonWsServer.call(logsBody, {}, function (error: any, success: any) {
      forSubscriptions(pgClient, 'logs', function (row: any) {
        const address = parseAddresses(row.filter?.address);
        const topics = row.filter?.topics || [];
        let result = success.result;
        if (address.length > 0) {
          result = result.filter((result: any) => {
            return address.includes(result.address);
          });
        }
        if (topics.length > 0) {
          result = result.filter((result: any) => {
            return matchTopics(topics, result.topics);
          });
        }
        result.forEach(function (res: any) {
          sendPayload(expressWsApp, row.ws_key, row.sub_id, {
            ...success,
            ...{ result: res },
          });
        });
      });
    });

    blockNumber = blockNumber + 1;
    retries = 0;
  } else {
    if (retries > retriesCount) {
      blockNumber = blockNumber + 1;
      retries = 0;
    } else {
      retries = retries + 1;
    }
  }

  forSubscriptions(pgClient, 'newHeads_debug', function (row: any) {
    const payload = {
      jsonrpc: '2.0',
      subscription: row.sub_id,
      result: {
        blockNumber,
      },
    };
    sendPayload(expressWsApp, row.ws_key, row.sub_id, JSON.stringify(payload));
  });

  setTimeout(
    notifyNewHeads,
    newHeadsInterval,
    pgClient,
    expressWsApp,
    jaysonWsServer,
    blockNumber,
    retries
  );
}

function sendPayload(express: any, ws_key: any, subId: any, payload: any) {
  express.getWss().clients.forEach(function (client: any) {
    if (ws_key == client.id) {
      typeof payload === 'string'
        ? client.send(payload)
        : client.send(wsResponse(payload, subId));
    }
  });
}

function wsResponse(rpcResponse: any, clientId: any) {
  const response = {
    jsonrpc: '2.0',
    method: 'eth_subscription',
    params: {
      result: rpcResponse.result,
      subscription: clientId,
    },
  };
  return JSON.stringify(exportJSON(response));
}

function forSubscriptions(
  pgClient: any,
  subscriptionType: string,
  callback: any
) {
  pgClient
    .query(
      `SELECT sec_websocket_key AS ws_key, id AS sub_id, filter FROM subscription WHERE type = $1`,
      [subscriptionType]
    )
    .then(function (pgResult: any) {
      pgResult.rows.forEach(callback);
    });
}

function parseAddresses(inputs: any): any[] {
  const addresses = (Array.isArray(inputs) ? inputs : [inputs]).map(
    (input: any) => {
      return input?.id?.toLowerCase() || null;
    }
  );
  return addresses.filter((x): x is any => x !== null);
}


text:
/* This is free and unencumbered software released into the public domain. */

import * as web3 from './web3';

import sql from 'sql-bricks';

export function compileTopics(
  topics: web3.FilterTopic[]
): sql.WhereExpression | null {
  if (!topics || topics.length == 0) return null;
  if (Array.isArray(topics)) {
    const topicsDeepLevel = 15;
    const flattenedTopics = topics.flat(topicsDeepLevel);
    const isEmptyTopics = flattenedTopics.every((topic) => {
      return topic === null;
    });
    if (isEmptyTopics) return null;
  }
  const operands: sql.WhereExpression[] = topics
    .map((topic: web3.FilterTopic, i: number): sql.WhereExpression[] => {
      if (topic === null) return [];
      if (typeof topic === 'string') {
        return [sql.eq(`e.topics[${i + 1}]`, hexToBytes(topic))];
      }
      if (Array.isArray(topic)) {
        if (!topic.length) return [];
        const operands: sql.WhereExpression[] = topic
          .map((topic: web3.FilterTopic): sql.WhereExpression[] => {
            if (topic === null) return [];
            return [sql.eq(`e.topics[${i + 1}]`, hexToBytes(topic as string))];
          })
          .flat();
        return [sql.or(...operands)];
      }
      throw Error('unreachable');
    })
    .flat();
  return sql.and(...operands);
}

// Duplicated here because of https://github.com/kulshekhar/ts-jest/issues/970
function hexToBytes(input: string): Uint8Array {
  return Buffer.from(input.substring(2), 'hex');
}

export function matchTopics(
  topics: Array<string | null | Array<string>>,
  topicsInTransaction: string[] | null | any[]
): boolean {
  return topics.every((topic, i) => {
    if (topic === null || (Array.isArray(topic) && !topic.length)) {
      return true;
    }

    // can be removed after https://github.com/aurora-is-near/aurora-relayer/pull/293 merge
    if (!Array.isArray(topicsInTransaction)) {
      return false;
    }

    if (Array.isArray(topic)) {
      return topic.includes(topicsInTransaction[i]);
    }

    if (topic !== topicsInTransaction[i]) {
      return false;
    }

    return true;
  });
}


text:
/* This is free and unencumbered software released into the public domain. */

import { MessagePort, parentPort, workerData } from 'worker_threads';

import { Config } from './config.js';
import { pg, sql } from './database.js';
import {
  computeBlockHash,
  EmptyBlock,
  generateEmptyBlock,
  emptyTransactionsRoot,
} from './utils.js';
import {
  AccountID,
  BlockHeight,
  ConnectEnv,
  Engine,
  hexToBytes,
  LogEvent,
  LogEventWithAddress,
  NetworkConfig,
} from '@aurora-is-near/engine';

interface PendingBlocks {
  [key: number]: string;
}

interface WorkerData {
  config: Config;
  network: NetworkConfig;
  env: ConnectEnv;
}

export class Indexer {
  protected readonly contractID: AccountID;
  protected readonly pgClient: pg.Client;
  protected pendingHeadBlock: number;
  protected pendingBlocks: PendingBlocks = {};

  constructor(
    public readonly config: Config,
    public readonly network: NetworkConfig,
    public readonly engine: Engine
  ) {
    this.contractID = AccountID.parse(this.config.engine).unwrap();
    this.pgClient = new pg.Client(config.database);
    this.pendingHeadBlock = 0;
    this.pendingBlocks = {};
  }

  async start(): Promise<void> {
    await this.pgClient.connect();
  }

  async indexBlock(blockID: BlockHeight): Promise<any> {
    for (;;) {
      const proxy = await this.engine.getBlock(blockID, {
        transactions: 'full',
        contractID: this.contractID,
      });
      if (proxy.isErr()) {
        const error = proxy.unwrapErr();
        if (error.startsWith('[-32000] Server error: DB Not Found Error')) {
          // Handling empty blocks
          const emptyBlock: EmptyBlock = generateEmptyBlock(
            blockID as number,
            this.contractID.toString(),
            this.network.chainID
          );
          //if (this.config.debug) console.debug(emptyBlock); // DEBUG
          console.error(`Indexing skipped block #${blockID}...`);
          const skippedBlockDataJson = {
            chain: emptyBlock.chain,
            id: emptyBlock.id,
            hash: emptyBlock.hash,
            near_hash: emptyBlock.nearHash,
            timestamp: emptyBlock.timestamp,
            size: emptyBlock.size,
            gas_limit: emptyBlock.gasLimit,
            gas_used: emptyBlock.gasUsed,
            parent_hash: emptyBlock.parentHash,
            transactions_root: emptyBlock.transactionsRoot,
            state_root: emptyBlock.stateRoot,
            receipts_root: emptyBlock.receiptsRoot,
          };
          return skippedBlockDataJson;
        }
        if (this.config.debug) console.error(error); // DEBUG
        await this.delay(100);
        continue; // retry block
      }
      const block_ = proxy.unwrap();
      const block = block_.getMetadata();
      const blockHash = computeBlockHash(
        block.number as number,
        this.contractID.toString(),
        this.network.chainID
      );
      const parentHash = computeBlockHash(
        (block.number as number) - 1,
        this.contractID.toString(),
        this.network.chainID
      );

      const transactionsRoot =
        block.transactions.length == 0
          ? emptyTransactionsRoot()
          : block.transactionsRoot;
      // console.error(`Indexing block #${blockID}...`);
      const transactions = await (block.transactions as any[]).map(
        async (transaction, transactionIndex) => {
          return await this.indexTransaction(
            blockID,
            transactionIndex,
            transaction
          );
        }
      );

      const blockData = {
        chain: this.network.chainID,
        id: block.number,
        hash: blockHash,
        near_hash: block_.near?.hash,
        timestamp: new Date((block.timestamp as number) * 1000).toISOString(),
        size: block.size,
        gas_limit: 0, // FIXME: block.gasLimit,
        gas_used: 0, // FIXME: block.gasUsed,
        parent_hash: parentHash,
        transactions_root: transactionsRoot,
        state_root: block.stateRoot,
        receipts_root: block.receiptsRoot,
      };
      const jsonBlockData = {
        ...blockData,
        ...{ transactions: await Promise.all(transactions) },
      };
      return jsonBlockData; // finish block
    } // for (;;)
  }

  async indexTransaction(
    blockID: BlockHeight,
    transactionIndex: number,
    transaction: any
  ): Promise<any> {
    console.error(
      `Indexing transaction ${transaction.hash} at #${blockID}:${transactionIndex}...`
    );

    const to = transaction.to;
    const result = transaction.result!;

    let status;
    let output = null;
    if (typeof result?.result?.status === 'boolean') {
      status = result.result?.status;
      output = result.output;
    } else {
      status = result.result?.status.enum === 'success';
      if (result.result?.status.success?.output.length)
        output = result.result?.status.success?.output;
    }
    //if (this.config.debug) console.debug(query.toParams()); // DEBUG

    const logs = await ((transaction.result?.result?.logs || []) as any[]).map(
      async (event: any, eventIndex: any) => {
        return await this.indexEvent(
          blockID,
          transactionIndex,
          eventIndex,
          event
        );
      }
    );

    // Index all log events emitted by this transaction:
    return {
      block: blockID,
      index: transactionIndex,
      //id: null,
      hash: Buffer.from(hexToBytes(transaction.hash!)),
      near_hash: transaction.near?.hash,
      near_receipt_hash: transaction.near?.receiptHash,
      from: Buffer.from(transaction.from!.toBytes()),
      to: to?.isSome() ? Buffer.from(to.unwrap().toBytes()) : null,
      nonce: transaction.nonce,
      gas_price: transaction.gasPrice,
      gas_limit: transaction.gasLimit,
      gas_used: result.result?.gasUsed || '0',
      value: transaction.value,
      input: transaction.data?.length ? transaction.data : null,
      v: transaction.v,
      r: transaction.r,
      s: transaction.s,
      status: status,
      output: output,
      logs: await Promise.all(logs),
    };
  }

  async indexEvent(
    blockID: BlockHeight,
    transactionIndex: number,
    eventIndex: number,
    event: LogEventWithAddress | LogEvent
  ): Promise<any> {
    console.error(
      `Indexing log event at #${blockID}:${transactionIndex}:${eventIndex}...`
    );
    const event_ = event as LogEventWithAddress;

    return {
      //id: null,
      data: event_.data?.length ? event_.data : null,
      from: event_.address ? Buffer.from(event_.address) : Buffer.alloc(20),
      topics: event_.topics?.length
        ? event_.topics.map((topic) => topic.toBytes())
        : null,
    };
  }

  async insertNewHeads(): Promise<void> {
    for (;;) {
      if (this.pendingBlocks[this.pendingHeadBlock]) {
        await this.insert(this.pendingBlocks[this.pendingHeadBlock]);
        delete this.pendingBlocks[this.pendingHeadBlock];
        this.pendingHeadBlock += 1;
      }
      await this.delay(100);
    }
  }

  async notifyNewHeads(blockID: number, blockData: any): Promise<void> {
    if (this.pendingHeadBlock == 0) {
      this.pendingHeadBlock = blockID;
    }
    this.pendingBlocks[blockID] = blockData;
  }

  async insert(blockData: any) {
    try {
      const transactions = blockData.transactions || [];
      delete blockData.transactions;
      await this.pgClient.query('BEGIN');
      const query = sql.insert('block', blockData);
      await this.pgClient.query(query.toParams());
      for (const [transactionIndex, transaction] of transactions.entries()) {
        const logs = transaction.logs;
        delete transaction.logs;
        const transactionData = {
          ...transaction,
          ...{ block: blockData.id, index: transactionIndex },
        };
        const query = sql
          .insert('transaction', transactionData)
          .returning('id');
        const {
          rows: [{ id }],
        } = await this.pgClient.query(query.toParams());
        const transactionID = parseInt(id as string);
        for (const [eventIndex, event] of logs.entries()) {
          const eventData = {
            ...event,
            ...{ transaction: transactionID, index: eventIndex },
          };
          const query = sql.insert('event', eventData);
          await this.pgClient.query(query.toParams());
        }
      }
      await this.pgClient.query('COMMIT');
    } catch (error) {
      console.error('Error indexing. Performing Rollback', error);
      await this.pgClient.query('ROLLBACK');
      return; // abort block
    }
  }

  async delay(ms: number) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

async function main(parentPort: MessagePort, workerData: WorkerData) {
  const { config, network, env } = workerData;
  const engine = await Engine.connect(
    {
      network: network.id,
      endpoint: config.endpoint,
      contract: config.engine,
    },
    env
  );
  const indexer = new Indexer(config, network, engine);
  await indexer.start();

  setTimeout(function () {
    indexer.insertNewHeads();
  });

  parentPort
    .on('message', async (block: any) => {
      parentPort.postMessage(true); // ack the request
      const blockData = await indexer.indexBlock(block.id);
      if (block.is_head) {
        indexer.notifyNewHeads(block.id, blockData);
      } else {
        indexer.insert(blockData);
      }
    })
    .on('close', () => {
      return; // TODO?
    });
}

main(parentPort!, workerData as WorkerData);


text:
/* This is free and unencumbered software released into the public domain. */

import { sha256 } from 'ethereum-cryptography/sha256.js';
import * as ethers from 'ethers';
import { keccak256 } from 'ethereumjs-util';
import * as rlp from 'rlp';

export type EmptyBlock = {
  chain: number;
  id: number;
  hash: Buffer;
  nearHash: Buffer | null;
  timestamp: string | null;
  size: number;
  gasLimit: number;
  gasUsed: number;
  parentHash: Buffer;
  transactionsRoot: Buffer;
  stateRoot: Buffer;
  receiptsRoot: Buffer;
};

export function computeBlockHash(
  blockHeight: number,
  accountId: string,
  chainId: number
): Buffer {
  return sha256(generateBlockPreImage(blockHeight, accountId, chainId));
}

function generateBlockPreImage(
  blockHeight: number,
  accountId: string,
  chainId: number
): Buffer {
  const blockHeightBuf = Buffer.alloc(4);
  blockHeightBuf.writeInt32BE(blockHeight, 0);
  const chainIdBuf = Buffer.alloc(4);
  chainIdBuf.writeInt32BE(chainId, 0);
  return Buffer.concat([
    Buffer.alloc(29),
    chainIdBuf,
    Buffer.from(accountId, 'utf8'),
    Buffer.alloc(4),
    blockHeightBuf,
  ]);
}

export function generateEmptyBlock(
  blockHeight: number,
  accountId: string,
  chainId: number
): EmptyBlock {
  const hash = computeBlockHash(blockHeight, accountId, chainId);

  const parentHash = computeBlockHash(blockHeight - 1, accountId, chainId);

  return {
    chain: chainId,
    id: blockHeight,
    hash: hash,
    nearHash: null,
    timestamp: null,
    size: 0,
    gasLimit: 0,
    gasUsed: 0,
    parentHash: parentHash,
    transactionsRoot: emptyTransactionsRoot(),
    stateRoot: Buffer.alloc(32),
    receiptsRoot: Buffer.alloc(32),
  };
}

export function emptyTransactionsRoot(): Buffer {
  return keccak256(rlp.encode(''));
}

export function parseEVMRevertReason(reason: Uint8Array): string | Uint8Array {
  if (reason.length > 0) {
    // only for valid decoded revert reason
    const coder = new ethers.utils.AbiCoder();
    try {
      return coder.decode(['string'], reason.slice(4)).toString();
    } catch {
      return reason;
    }
  }
  return reason;
}


text:
/* This is free and unencumbered software released into the public domain. */

/** Encapsulates the Express.js `Request` object. */
export class Request {
  constructor(public readonly req: any) {}

  hasAuthorization(): boolean {
    return 'authorization' in this.req.headers;
  }

  ip(): string {
    return this.req.headers['cf-connecting-ip'];
  }

  country(): string {
    return this.req.headers['cf-ipcountry'];
  }

  websocketKey(): string {
    return this.req.headers['sec-websocket-key'];
  }
}


text:
/* This is free and unencumbered software released into the public domain. */

import { ConnectEnv, NetworkConfig, NETWORKS } from '@aurora-is-near/engine';

export interface Config {
  debug: boolean;
  verbose: boolean;
  force: boolean;
  database?: string;
  broker?: string;
  port: number | string;
  network: string;
  endpoint?: string;
  engine: string;
  signer: string;
  signerKey?: string;
  signerKeys?: string[];
  block?: number | string;
  batchSize?: number | string;
  writable?: boolean;
  errorLog?: string;
  minGasPrice?: number;
  getLogsLimit: number;
}

export const localConfig: Config = {
  debug: false,
  verbose: false,
  force: false,
  database: undefined,
  broker: undefined,
  port: 8545,
  network: 'local',
  endpoint: 'http://127.0.0.1:3030',
  engine: 'aurora.test.near',
  signer: 'test.near',
  signerKey: undefined,
  signerKeys: [],
  writable: true,
  errorLog: undefined,
  minGasPrice: undefined,
  getLogsLimit: 10000,
};

export function parseConfig(
  options: Config,
  config: Config,
  env: ConnectEnv
): [NetworkConfig, Config] {
  const networkID = options.network || env.NEAR_ENV || config.network;
  if (!networkID) {
    throw new Error(`Missing network ID. Use: --network <network>`);
  }
  const network = NETWORKS.get(networkID);
  if (!network) {
    throw new Error(`Unknown network ID: '${networkID}'`);
  }
  const debug = options.debug || config.debug;
  return [
    network,
    {
      debug: debug,
      verbose: debug || options.verbose || config.verbose,
      force: options.force || config.force,
      database: options.database || config.database,
      broker: options.broker || config.broker,
      port: parseInt((options.port as string) || (config.port as string)),
      network: networkID,
      endpoint:
        options.endpoint ||
        env.NEAR_URL ||
        config.endpoint ||
        network.nearEndpoint,
      engine:
        options.engine ||
        env.AURORA_ENGINE ||
        config.engine ||
        network.contractID,
      signer: options.signer || env.NEAR_MASTER_ACCOUNT || config.signer,
      signerKey: options.signerKey || config.signerKey,
      signerKeys: config.signerKeys || [],
      batchSize: parseInt(
        (options.batchSize as string) || (config.batchSize as string) || '1000'
      ),
      writable: config.writable !== undefined ? config.writable : true,
      errorLog: config.errorLog,
      minGasPrice: config.minGasPrice !== undefined ? config.minGasPrice : 0,
      getLogsLimit: options.getLogsLimit || config.getLogsLimit || 10000,
    },
  ];
}


text:
/* This is free and unencumbered software released into the public domain. */

declare module 'sql-bricks-postgres';