mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-06 19:38:05 +00:00
Generating eth_call based lazy watcher (#249)
* Add entity generation. * Add resolvers generation. * Add queries in resolvers generation. * Add indexer generation. * Extract helper code in utils. * Add server and artifacts generation. * Fix solidity-flattener issue. * Update readme and cleanup misc files. * Add queries to entity generation. * Add database generation. * Use snakecase in database. * Add readme generation. * Change template file names. * Add method descriptions. * Change mode to eth_call in readme. Co-authored-by: prathamesh <prathamesh.musale0@gmail.com>
This commit is contained in:
parent
4f8f1d8cd7
commit
92b7967895
@ -10,14 +10,15 @@
|
||||
|
||||
## Run
|
||||
|
||||
* Run the following command to generate schema from a contract file:
|
||||
* Run the following command to generate a watcher from a contract file:
|
||||
|
||||
```bash
|
||||
yarn codegen:gql --input-file <input-file-path> --output-file [output-file-path] --mode [eth_call | storage] --flatten [true | false]
|
||||
yarn codegen --input-file <input-file-path> --contract-name <contract-name> --output-folder [output-folder] --mode [eth_call | storage] --flatten [true | false]
|
||||
```
|
||||
|
||||
* `input-file`(alias: `i`): Input contract file path or an URL (required).
|
||||
* `output-file`(alias: `o`): Schema output file path (logs output using `stdout` if not provided).
|
||||
* `contract-name`(alias: `c`): Main contract name (required).
|
||||
* `output-folder`(alias: `o`): Output folder path. (logs output using `stdout` if not provided).
|
||||
* `mode`(alias: `m`): Code generation mode (default: `storage`).
|
||||
* `flatten`(alias: `f`): Flatten the input contract file (default: `true`).
|
||||
|
||||
@ -26,11 +27,11 @@
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
yarn codegen:gql --input-file ./test/examples/contracts/ERC20.sol --output-file ./ERC20-schema.gql --mode eth_call
|
||||
yarn codegen --input-file ./test/examples/contracts/ERC20.sol --contract-name ERC20 --output-folder ../my-erc20-watcher --mode eth_call
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn codegen:gql --input-file https://git.io/Jupci --output-file ./ERC721-schema.gql --mode storage
|
||||
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../my-erc721-watcher --mode eth_call
|
||||
```
|
||||
|
||||
## Demo
|
||||
@ -41,16 +42,18 @@
|
||||
yarn
|
||||
```
|
||||
|
||||
* Generate schema from a contract file:
|
||||
* Generate a watcher from a contract file:
|
||||
|
||||
```bash
|
||||
yarn codegen:gql --input-file ../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --output-file ./ERC20-schema.gql --mode storage
|
||||
yarn codegen --input-file ../../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --contract-name ERC20 --output-folder ../demo-erc20-watcher --mode eth_call
|
||||
```
|
||||
|
||||
* Generate schema from a flattened contract file from an URL:
|
||||
This will create a folder called `demo-erc20-watcher` containing the generated code at the specified path. Follow the steps in `demo-erc20-watcher/README.md` to setup and run the generated watcher.
|
||||
|
||||
* Generate a watcher from a flattened contract file from an URL:
|
||||
|
||||
```bash
|
||||
yarn codegen:gql --input-file https://git.io/Jupci --output-file ./ERC721-schema.gql --mode eth_call
|
||||
yarn codegen --input-file https://git.io/Jupci --contract-name ERC721 --output-folder ../demo-erc721-watcher --mode eth_call
|
||||
```
|
||||
|
||||
## References
|
||||
|
@ -6,7 +6,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"codegen:gql": "ts-node src/generate-schema.ts"
|
||||
"codegen": "ts-node src/generate-code.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -23,7 +23,9 @@
|
||||
"@solidity-parser/parser": "^0.13.2",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-compose": "^9.0.3",
|
||||
"handlebars": "^4.7.7",
|
||||
"node-fetch": "^2",
|
||||
"solc": "^0.8.7-fixed",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "^4.3.2",
|
||||
"yargs": "^17.1.1"
|
||||
|
35
packages/codegen/src/artifacts.ts
Normal file
35
packages/codegen/src/artifacts.ts
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import solc from 'solc';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
/**
|
||||
* Compiles the given contract using solc and writes the resultant artifacts to a file.
|
||||
* @param outStream A writable output stream to write the artifacts file to.
|
||||
* @param contractContent Contents of the contract file to be compiled.
|
||||
* @param contractFileName Input contract file name.
|
||||
* @param contractName Name of the main contract in the contract file.
|
||||
*/
|
||||
export function exportArtifacts (outStream: Writable, contractContent: string, contractFileName: string, contractName: string): void {
|
||||
const input: any = {
|
||||
language: 'Solidity',
|
||||
sources: {},
|
||||
settings: {
|
||||
outputSelection: {
|
||||
'*': {
|
||||
'*': ['abi', 'storageLayout']
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
input.sources[contractFileName] = {
|
||||
content: contractContent
|
||||
};
|
||||
|
||||
// Get artifacts for the required contract.
|
||||
const output = JSON.parse(solc.compile(JSON.stringify(input))).contracts[contractFileName][contractName];
|
||||
outStream.write(JSON.stringify(output, null, 2));
|
||||
}
|
24
packages/codegen/src/config.ts
Normal file
24
packages/codegen/src/config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Handlebars from 'handlebars';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
const TEMPLATE_FILE = './templates/config-template.handlebars';
|
||||
|
||||
/**
|
||||
* Writes the config file generated from a template to a stream.
|
||||
* @param folderName Watcher folder name to be passed to the template.
|
||||
* @param outStream A writable output stream to write the config file to.
|
||||
*/
|
||||
export function exportConfig (folderName: string, outStream: Writable): void {
|
||||
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
const template = Handlebars.compile(templateString);
|
||||
const config = template({
|
||||
folderName
|
||||
});
|
||||
outStream.write(config);
|
||||
}
|
73
packages/codegen/src/database.ts
Normal file
73
packages/codegen/src/database.ts
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import assert from 'assert';
|
||||
import Handlebars from 'handlebars';
|
||||
import { Writable } from 'stream';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getTsForSol } from './utils/type-mappings';
|
||||
import { Param } from './utils/types';
|
||||
import { capitalizeHelper } from './utils/handlebar-helpers';
|
||||
|
||||
const TEMPLATE_FILE = './templates/database-template.handlebars';
|
||||
|
||||
export class Database {
|
||||
_queries: Array<any>;
|
||||
_templateString: string;
|
||||
|
||||
constructor () {
|
||||
this._queries = [];
|
||||
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
|
||||
Handlebars.registerHelper('capitalize', capitalizeHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the query to be passed to the template.
|
||||
* @param name Name of the query.
|
||||
* @param params Parameters to the query.
|
||||
* @param returnType Return type for the query.
|
||||
*/
|
||||
addQuery (name: string, params: Array<Param>, returnType: string): void {
|
||||
// Check if the query is already added.
|
||||
if (this._queries.some(query => query.name === name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryObject = {
|
||||
name: name,
|
||||
params: _.cloneDeep(params),
|
||||
returnType: returnType
|
||||
};
|
||||
|
||||
queryObject.params = queryObject.params.map((param) => {
|
||||
const tsParamType = getTsForSol(param.type);
|
||||
assert(tsParamType);
|
||||
param.type = tsParamType;
|
||||
return param;
|
||||
});
|
||||
|
||||
const tsReturnType = getTsForSol(returnType);
|
||||
assert(tsReturnType);
|
||||
queryObject.returnType = tsReturnType;
|
||||
|
||||
this._queries.push(queryObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the database file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the database file to.
|
||||
*/
|
||||
exportDatabase (outStream: Writable): void {
|
||||
const template = Handlebars.compile(this._templateString);
|
||||
const obj = {
|
||||
queries: this._queries
|
||||
};
|
||||
const database = template(obj);
|
||||
outStream.write(database);
|
||||
}
|
||||
}
|
97
packages/codegen/src/entity.ts
Normal file
97
packages/codegen/src/entity.ts
Normal file
@ -0,0 +1,97 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import assert from 'assert';
|
||||
import Handlebars from 'handlebars';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
import { getTsForSol, getPgForTs } from './utils/type-mappings';
|
||||
import { Param } from './utils/types';
|
||||
|
||||
const TEMPLATE_FILE = './templates/entity-template.handlebars';
|
||||
|
||||
export class Entity {
|
||||
_entities: Array<any>;
|
||||
_templateString: string;
|
||||
|
||||
constructor () {
|
||||
this._entities = [];
|
||||
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity object from the query and stores to be passed to the template.
|
||||
* @param name Name of the query.
|
||||
* @param params Parameters to the query.
|
||||
* @param returnType Return type for the query.
|
||||
*/
|
||||
addQuery (name: string, params: Array<Param>, returnType: string): void {
|
||||
// Check if the query is already added.
|
||||
if (this._entities.some(entity => entity.className.toLowerCase() === name.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entityObject: any = {
|
||||
// Capitalize the first letter of name.
|
||||
className: `${name.charAt(0).toUpperCase()}${name.slice(1)}`,
|
||||
indexOn: {},
|
||||
columns: [{}],
|
||||
returnType: returnType
|
||||
};
|
||||
|
||||
entityObject.indexOn.columns = params.map((param) => {
|
||||
return param.name;
|
||||
});
|
||||
entityObject.indexOn.unique = true;
|
||||
|
||||
entityObject.columns = params.map((param) => {
|
||||
const length = param.type === 'address' ? 42 : null;
|
||||
const name = param.name;
|
||||
|
||||
const tsType = getTsForSol(param.type);
|
||||
assert(tsType);
|
||||
|
||||
const pgType = getPgForTs(tsType);
|
||||
assert(pgType);
|
||||
|
||||
return {
|
||||
name,
|
||||
pgType,
|
||||
tsType,
|
||||
length
|
||||
};
|
||||
});
|
||||
|
||||
const tsReturnType = getTsForSol(returnType);
|
||||
assert(tsReturnType);
|
||||
|
||||
const pgReturnType = getPgForTs(tsReturnType);
|
||||
assert(pgReturnType);
|
||||
|
||||
entityObject.columns.push({
|
||||
name: 'value',
|
||||
pgType: pgReturnType,
|
||||
tsType: tsReturnType
|
||||
});
|
||||
|
||||
this._entities.push(entityObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the generated entity files in the given directory.
|
||||
* @param entityDir Directory to write the entities to.
|
||||
*/
|
||||
exportEntities (entityDir: string): void {
|
||||
const template = Handlebars.compile(this._templateString);
|
||||
this._entities.forEach(entityObj => {
|
||||
const entity = template(entityObj);
|
||||
const outStream: Writable = entityDir
|
||||
? fs.createWriteStream(path.join(entityDir, `${entityObj.className}.ts`))
|
||||
: process.stdout;
|
||||
outStream.write(entity);
|
||||
});
|
||||
}
|
||||
}
|
179
packages/codegen/src/generate-code.ts
Normal file
179
packages/codegen/src/generate-code.ts
Normal file
@ -0,0 +1,179 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import fetch from 'node-fetch';
|
||||
import path from 'path';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import { flatten } from '@poanet/solidity-flattener';
|
||||
|
||||
import { parse, visit } from '@solidity-parser/parser';
|
||||
|
||||
import { Visitor } from './visitor';
|
||||
import { exportServer } from './server';
|
||||
import { exportConfig } from './config';
|
||||
import { exportArtifacts } from './artifacts';
|
||||
import { exportPackage } from './package';
|
||||
import { exportTSConfig } from './tsconfig';
|
||||
import { exportReadme } from './readme';
|
||||
|
||||
const MODE_ETH_CALL = 'eth_call';
|
||||
const MODE_STORAGE = 'storage';
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
const argv = await yargs(hideBin(process.argv))
|
||||
.option('input-file', {
|
||||
alias: 'i',
|
||||
demandOption: true,
|
||||
describe: 'Input contract file path or an url.',
|
||||
type: 'string'
|
||||
})
|
||||
.option('contract-name', {
|
||||
alias: 'c',
|
||||
demandOption: true,
|
||||
describe: 'Main contract name.',
|
||||
type: 'string'
|
||||
})
|
||||
.option('output-folder', {
|
||||
alias: 'o',
|
||||
describe: 'Output folder path.',
|
||||
type: 'string'
|
||||
})
|
||||
.option('mode', {
|
||||
alias: 'm',
|
||||
describe: 'Code generation mode.',
|
||||
type: 'string',
|
||||
default: MODE_STORAGE,
|
||||
choices: [MODE_ETH_CALL, MODE_STORAGE]
|
||||
})
|
||||
.option('flatten', {
|
||||
alias: 'f',
|
||||
describe: 'Flatten the input contract file.',
|
||||
type: 'boolean',
|
||||
default: true
|
||||
})
|
||||
.argv;
|
||||
|
||||
let data: string;
|
||||
if (argv['input-file'].startsWith('http')) {
|
||||
// Assume flattened file in case of URL.
|
||||
const response = await fetch(argv['input-file']);
|
||||
data = await response.text();
|
||||
} else {
|
||||
data = argv.flatten
|
||||
? await flatten(path.resolve(argv['input-file']))
|
||||
: fs.readFileSync(path.resolve(argv['input-file'])).toString();
|
||||
}
|
||||
|
||||
const visitor = new Visitor();
|
||||
|
||||
parseAndVisit(data, visitor, argv.mode);
|
||||
|
||||
generateWatcher(data, visitor, argv);
|
||||
};
|
||||
|
||||
function parseAndVisit (data: string, visitor: Visitor, mode: string) {
|
||||
// Get the abstract syntax tree for the flattened contract.
|
||||
const ast = parse(data);
|
||||
|
||||
// Filter out library nodes.
|
||||
ast.children = ast.children.filter(child => !(child.type === 'ContractDefinition' && child.kind === 'library'));
|
||||
|
||||
if (mode === MODE_ETH_CALL) {
|
||||
visit(ast, {
|
||||
FunctionDefinition: visitor.functionDefinitionVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
} else {
|
||||
visit(ast, {
|
||||
StateVariableDeclaration: visitor.stateVariableDeclarationVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function generateWatcher (data: string, visitor: Visitor, argv: any) {
|
||||
// Prepare directory structure for the watcher.
|
||||
let outputDir = '';
|
||||
if (argv['output-folder']) {
|
||||
outputDir = path.resolve(argv['output-folder']);
|
||||
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const environmentsFolder = path.join(outputDir, 'environments');
|
||||
if (!fs.existsSync(environmentsFolder)) fs.mkdirSync(environmentsFolder);
|
||||
|
||||
const artifactsFolder = path.join(outputDir, 'src/artifacts');
|
||||
if (!fs.existsSync(artifactsFolder)) fs.mkdirSync(artifactsFolder, { recursive: true });
|
||||
|
||||
const entitiesFolder = path.join(outputDir, 'src/entity');
|
||||
if (!fs.existsSync(entitiesFolder)) fs.mkdirSync(entitiesFolder, { recursive: true });
|
||||
}
|
||||
|
||||
const inputFileName = path.basename(argv['input-file'], '.sol');
|
||||
|
||||
let outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/schema.gql'))
|
||||
: process.stdout;
|
||||
visitor.exportSchema(outStream);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/resolvers.ts'))
|
||||
: process.stdout;
|
||||
visitor.exportResolvers(outStream);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/indexer.ts'))
|
||||
: process.stdout;
|
||||
visitor.exportIndexer(outStream, inputFileName);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/server.ts'))
|
||||
: process.stdout;
|
||||
exportServer(outStream);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'environments/local.toml'))
|
||||
: process.stdout;
|
||||
exportConfig(path.basename(outputDir), outStream);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/artifacts/', `${inputFileName}.json`))
|
||||
: process.stdout;
|
||||
exportArtifacts(
|
||||
outStream,
|
||||
data,
|
||||
`${inputFileName}.sol`,
|
||||
argv['contract-name']
|
||||
);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'src/database.ts'))
|
||||
: process.stdout;
|
||||
visitor.exportDatabase(outStream);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'package.json'))
|
||||
: process.stdout;
|
||||
exportPackage(path.basename(outputDir), outStream);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'tsconfig.json'))
|
||||
: process.stdout;
|
||||
exportTSConfig(outStream);
|
||||
|
||||
const entityDir = outputDir
|
||||
? path.join(outputDir, 'src/entity')
|
||||
: '';
|
||||
visitor.exportEntities(entityDir);
|
||||
|
||||
outStream = outputDir
|
||||
? fs.createWriteStream(path.join(outputDir, 'README.md'))
|
||||
: process.stdout;
|
||||
exportReadme(path.basename(outputDir), argv['contract-name'], outStream);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
});
|
@ -1,86 +0,0 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import { readFileSync, createWriteStream } from 'fs';
|
||||
import fetch from 'node-fetch';
|
||||
import path from 'path';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import { flatten } from '@poanet/solidity-flattener';
|
||||
|
||||
import { parse, visit } from '@solidity-parser/parser';
|
||||
|
||||
import { Visitor } from './visitor';
|
||||
|
||||
const MODE_ETH_CALL = 'eth_call';
|
||||
const MODE_STORAGE = 'storage';
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
const argv = await yargs(hideBin(process.argv))
|
||||
.option('input-file', {
|
||||
alias: 'i',
|
||||
demandOption: true,
|
||||
describe: 'Input contract file path or an url.',
|
||||
type: 'string'
|
||||
})
|
||||
.option('output-file', {
|
||||
alias: 'o',
|
||||
describe: 'Schema output file path.',
|
||||
type: 'string'
|
||||
})
|
||||
.option('mode', {
|
||||
alias: 'm',
|
||||
describe: 'Code generation mode.',
|
||||
type: 'string',
|
||||
default: MODE_STORAGE,
|
||||
choices: [MODE_ETH_CALL, MODE_STORAGE]
|
||||
})
|
||||
.option('flatten', {
|
||||
alias: 'f',
|
||||
describe: 'Flatten the input contract file.',
|
||||
type: 'boolean',
|
||||
default: true
|
||||
})
|
||||
.argv;
|
||||
|
||||
let data: string;
|
||||
if (argv['input-file'].startsWith('http')) {
|
||||
// Assume flattened file in case of URL.
|
||||
const response = await fetch(argv['input-file']);
|
||||
data = await response.text();
|
||||
} else {
|
||||
data = argv.flatten
|
||||
? await flatten(path.resolve(argv['input-file']))
|
||||
: readFileSync(path.resolve(argv['input-file'])).toString();
|
||||
}
|
||||
|
||||
// Get the abstract syntax tree for the flattened contract.
|
||||
const ast = parse(data);
|
||||
|
||||
// Filter out library nodes.
|
||||
ast.children = ast.children.filter(child => !(child.type === 'ContractDefinition' && child.kind === 'library'));
|
||||
|
||||
const visitor = new Visitor();
|
||||
|
||||
if (argv.mode === MODE_ETH_CALL) {
|
||||
visit(ast, {
|
||||
FunctionDefinition: visitor.functionDefinitionVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
} else {
|
||||
visit(ast, {
|
||||
StateVariableDeclaration: visitor.stateVariableDeclarationVisitor.bind(visitor),
|
||||
EventDefinition: visitor.eventDefinitionVisitor.bind(visitor)
|
||||
});
|
||||
}
|
||||
|
||||
const outStream = argv['output-file']
|
||||
? createWriteStream(path.resolve(argv['output-file']))
|
||||
: process.stdout;
|
||||
visitor.exportSchema(outStream);
|
||||
};
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
});
|
76
packages/codegen/src/indexer.ts
Normal file
76
packages/codegen/src/indexer.ts
Normal file
@ -0,0 +1,76 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import assert from 'assert';
|
||||
import Handlebars from 'handlebars';
|
||||
import { Writable } from 'stream';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getTsForSol } from './utils/type-mappings';
|
||||
import { Param } from './utils/types';
|
||||
import { compareHelper, capitalizeHelper } from './utils/handlebar-helpers';
|
||||
|
||||
const TEMPLATE_FILE = './templates/indexer-template.handlebars';
|
||||
|
||||
export class Indexer {
|
||||
_queries: Array<any>;
|
||||
_templateString: string;
|
||||
|
||||
constructor () {
|
||||
this._queries = [];
|
||||
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
|
||||
Handlebars.registerHelper('compare', compareHelper);
|
||||
Handlebars.registerHelper('capitalize', capitalizeHelper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the query to be passed to the template.
|
||||
* @param name Name of the query.
|
||||
* @param params Parameters to the query.
|
||||
* @param returnType Return type for the query.
|
||||
*/
|
||||
addQuery (name: string, params: Array<Param>, returnType: string): void {
|
||||
// Check if the query is already added.
|
||||
if (this._queries.some(query => query.name === name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryObject = {
|
||||
name: name,
|
||||
params: _.cloneDeep(params),
|
||||
returnType: returnType
|
||||
};
|
||||
|
||||
queryObject.params = queryObject.params.map((param) => {
|
||||
const tsParamType = getTsForSol(param.type);
|
||||
assert(tsParamType);
|
||||
param.type = tsParamType;
|
||||
return param;
|
||||
});
|
||||
|
||||
const tsReturnType = getTsForSol(returnType);
|
||||
assert(tsReturnType);
|
||||
queryObject.returnType = tsReturnType;
|
||||
|
||||
this._queries.push(queryObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the indexer file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the indexer file to.
|
||||
* @param inputFileName Input contract file name to be passed to the template.
|
||||
*/
|
||||
exportIndexer (outStream: Writable, inputFileName: string): void {
|
||||
const template = Handlebars.compile(this._templateString);
|
||||
const obj = {
|
||||
inputFileName,
|
||||
queries: this._queries
|
||||
};
|
||||
const indexer = template(obj);
|
||||
outStream.write(indexer);
|
||||
}
|
||||
}
|
24
packages/codegen/src/package.ts
Normal file
24
packages/codegen/src/package.ts
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Handlebars from 'handlebars';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
const TEMPLATE_FILE = './templates/package-template.handlebars';
|
||||
|
||||
/**
|
||||
* Writes the package.json file generated from a template to a stream.
|
||||
* @param folderName Watcher folder name to be passed to the template.
|
||||
* @param outStream A writable output stream to write the package.json file to.
|
||||
*/
|
||||
export function exportPackage (folderName: string, outStream: Writable): void {
|
||||
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
const template = Handlebars.compile(templateString);
|
||||
const packageString = template({
|
||||
folderName
|
||||
});
|
||||
outStream.write(packageString);
|
||||
}
|
26
packages/codegen/src/readme.ts
Normal file
26
packages/codegen/src/readme.ts
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Handlebars from 'handlebars';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
const TEMPLATE_FILE = './templates/readme-template.handlebars';
|
||||
|
||||
/**
|
||||
* Writes the README.md file generated from a template to a stream.
|
||||
* @param folderName Watcher folder name to be passed to the template.
|
||||
* @param contractName Input contract name given as title of the README.
|
||||
* @param outStream A writable output stream to write the README.md file to.
|
||||
*/
|
||||
export function exportReadme (folderName: string, contractName: string, outStream: Writable): void {
|
||||
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
const template = Handlebars.compile(templateString);
|
||||
const readmeString = template({
|
||||
folderName,
|
||||
contractName
|
||||
});
|
||||
outStream.write(readmeString);
|
||||
}
|
66
packages/codegen/src/resolvers.ts
Normal file
66
packages/codegen/src/resolvers.ts
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { Writable } from 'stream';
|
||||
import Handlebars from 'handlebars';
|
||||
import assert from 'assert';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getTsForSol } from './utils/type-mappings';
|
||||
import { Param } from './utils/types';
|
||||
|
||||
const TEMPLATE_FILE = './templates/resolvers-template.handlebars';
|
||||
|
||||
export class Resolvers {
|
||||
_queries: Array<any>;
|
||||
_templateString: string;
|
||||
|
||||
constructor () {
|
||||
this._queries = [];
|
||||
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the query to be passed to the template.
|
||||
* @param name Name of the query.
|
||||
* @param params Parameters to the query.
|
||||
* @param returnType Return type for the query.
|
||||
*/
|
||||
addQuery (name: string, params: Array<Param>, returnType: string): void {
|
||||
// Check if the query is already added.
|
||||
if (this._queries.some(query => query.name === name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryObject = {
|
||||
name: name,
|
||||
params: _.cloneDeep(params),
|
||||
returnType: returnType
|
||||
};
|
||||
|
||||
queryObject.params = queryObject.params.map((param) => {
|
||||
const tsParamType = getTsForSol(param.type);
|
||||
assert(tsParamType);
|
||||
param.type = tsParamType;
|
||||
return param;
|
||||
});
|
||||
|
||||
this._queries.push(queryObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the resolvers file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the resolvers file to.
|
||||
*/
|
||||
exportResolvers (outStream: Writable): void {
|
||||
const template = Handlebars.compile(this._templateString);
|
||||
const obj = {
|
||||
queries: this._queries
|
||||
};
|
||||
const resolvers = template(obj);
|
||||
outStream.write(resolvers);
|
||||
}
|
||||
}
|
@ -2,23 +2,20 @@
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import assert from 'assert';
|
||||
import { GraphQLSchema, printSchema } from 'graphql';
|
||||
import { SchemaComposer } from 'graphql-compose';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
export interface Param {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
import { getTsForSol, getGqlForTs } from './utils/type-mappings';
|
||||
import { Param } from './utils/types';
|
||||
|
||||
export class Schema {
|
||||
_composer: SchemaComposer;
|
||||
_typeMapping: Map<string, string>;
|
||||
_events: Array<string>;
|
||||
|
||||
constructor () {
|
||||
this._composer = new SchemaComposer();
|
||||
this._typeMapping = new Map();
|
||||
this._events = [];
|
||||
|
||||
this._addBasicTypes();
|
||||
@ -32,10 +29,13 @@ export class Schema {
|
||||
*/
|
||||
addQuery (name: string, params: Array<Param>, returnType: string): void {
|
||||
// TODO: Handle cases where returnType/params type is an array.
|
||||
const tsReturnType = getTsForSol(returnType);
|
||||
assert(tsReturnType);
|
||||
|
||||
const queryObject: { [key: string]: any; } = {};
|
||||
queryObject[name] = {
|
||||
// Get type composer object for return type from the schema composer.
|
||||
type: this._composer.getOTC(`Result${this._typeMapping.get(returnType)}`).NonNull,
|
||||
type: this._composer.getOTC(`Result${getGqlForTs(tsReturnType)}`).NonNull,
|
||||
args: {
|
||||
blockHash: 'String!',
|
||||
contractAddress: 'String!'
|
||||
@ -44,7 +44,9 @@ export class Schema {
|
||||
|
||||
if (params.length > 0) {
|
||||
queryObject[name].args = params.reduce((acc, curr) => {
|
||||
acc[curr.name] = this._typeMapping.get(curr.type) + '!';
|
||||
const tsCurrType = getTsForSol(curr.type);
|
||||
assert(tsCurrType);
|
||||
acc[curr.name] = `${getGqlForTs(tsCurrType)}!`;
|
||||
return acc;
|
||||
}, queryObject[name].args);
|
||||
}
|
||||
@ -67,7 +69,9 @@ export class Schema {
|
||||
|
||||
if (params.length > 0) {
|
||||
typeObject.fields = params.reduce((acc, curr) => {
|
||||
acc[curr.name] = this._typeMapping.get(curr.type) + '!';
|
||||
const tsCurrType = getTsForSol(curr.type);
|
||||
assert(tsCurrType);
|
||||
acc[curr.name] = `${getGqlForTs(tsCurrType)}!`;
|
||||
return acc;
|
||||
}, typeObject.fields);
|
||||
}
|
||||
@ -99,8 +103,8 @@ export class Schema {
|
||||
*/
|
||||
exportSchema (outStream: Writable): void {
|
||||
// Get schema as a string from GraphQLSchema.
|
||||
const schema = printSchema(this.buildSchema());
|
||||
outStream.write(schema);
|
||||
const schemaString = printSchema(this.buildSchema());
|
||||
outStream.write(schemaString);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,14 +156,6 @@ export class Schema {
|
||||
proof: () => this._composer.getOTC('Proof')
|
||||
}
|
||||
});
|
||||
|
||||
// TODO Get typemapping from ethersjs.
|
||||
this._typeMapping.set('string', 'String');
|
||||
this._typeMapping.set('uint8', 'Int');
|
||||
this._typeMapping.set('uint256', 'BigInt');
|
||||
this._typeMapping.set('address', 'String');
|
||||
this._typeMapping.set('bool', 'Boolean');
|
||||
this._typeMapping.set('bytes4', 'String');
|
||||
}
|
||||
|
||||
/**
|
||||
|
21
packages/codegen/src/server.ts
Normal file
21
packages/codegen/src/server.ts
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Handlebars from 'handlebars';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
const TEMPLATE_FILE = './templates/server-template.handlebars';
|
||||
|
||||
/**
|
||||
* Writes the server file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the server file to.
|
||||
*/
|
||||
export function exportServer (outStream: Writable): void {
|
||||
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
const template = Handlebars.compile(templateString);
|
||||
const server = template({});
|
||||
outStream.write(server);
|
||||
}
|
25
packages/codegen/src/templates/config-template.handlebars
Normal file
25
packages/codegen/src/templates/config-template.handlebars
Normal file
@ -0,0 +1,25 @@
|
||||
[server]
|
||||
host = "127.0.0.1"
|
||||
port = 3008
|
||||
mode = "eth_call"
|
||||
|
||||
[database]
|
||||
type = "postgres"
|
||||
host = "localhost"
|
||||
port = 5432
|
||||
database = "{{folderName}}"
|
||||
username = "postgres"
|
||||
password = "postgres"
|
||||
synchronize = true
|
||||
logging = false
|
||||
|
||||
[upstream]
|
||||
[upstream.ethServer]
|
||||
gqlApiEndpoint = "http://127.0.0.1:8082/graphql"
|
||||
gqlPostgraphileEndpoint = "http://127.0.0.1:5000/graphql"
|
||||
rpcProviderEndpoint = "http://127.0.0.1:8081"
|
||||
|
||||
[upstream.cache]
|
||||
name = "requests"
|
||||
enabled = false
|
||||
deleteOnStart = false
|
72
packages/codegen/src/templates/database-template.handlebars
Normal file
72
packages/codegen/src/templates/database-template.handlebars
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import assert from 'assert';
|
||||
import { Connection, ConnectionOptions, DeepPartial } from 'typeorm';
|
||||
import path from 'path';
|
||||
|
||||
import { Database as BaseDatabase } from '@vulcanize/util';
|
||||
|
||||
{{#each queries as | query |}}
|
||||
import { {{capitalize query.name tillIndex=1}} } from './entity/{{capitalize query.name tillIndex=1}}';
|
||||
{{/each}}
|
||||
|
||||
export class Database {
|
||||
_config: ConnectionOptions
|
||||
_conn!: Connection
|
||||
_baseDatabase: BaseDatabase;
|
||||
|
||||
constructor (config: ConnectionOptions) {
|
||||
assert(config);
|
||||
|
||||
this._config = {
|
||||
...config,
|
||||
entities: [path.join(__dirname, 'entity/*')]
|
||||
};
|
||||
|
||||
this._baseDatabase = new BaseDatabase(this._config);
|
||||
}
|
||||
|
||||
async init (): Promise<void> {
|
||||
this._conn = await this._baseDatabase.init();
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
return this._baseDatabase.close();
|
||||
}
|
||||
|
||||
{{#each queries as | query |}}
|
||||
async get{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
|
||||
{{~#each query.params}}, {{this.name~}} {{/each}} }: { blockHash: string, contractAddress: string
|
||||
{{~#each query.params}}, {{this.name~}}: {{this.type~}} {{/each}} }): Promise<{{capitalize query.name tillIndex=1}} | undefined> {
|
||||
return this._conn.getRepository({{capitalize query.name tillIndex=1}})
|
||||
.createQueryBuilder('{{query.name}}')
|
||||
.where(`${this._getColumn('{{capitalize query.name tillIndex=1}}', 'blockHash')} = :blockHash AND ${this._getColumn('{{capitalize query.name tillIndex=1}}', 'contractAddress')} = :contractAddress
|
||||
{{~#each query.params}} AND ${this._getColumn('{{capitalize query.name tillIndex=1}}', '{{this.name}}')} = :{{this.name~}} {{/each}}`, {
|
||||
blockHash,
|
||||
contractAddress
|
||||
{{~#each query.params}},
|
||||
{{this.name}}
|
||||
{{~/each}}
|
||||
|
||||
})
|
||||
.getOne();
|
||||
}
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{~#each queries as | query |}}
|
||||
async save{{capitalize query.name tillIndex=1}} ({ blockHash, contractAddress
|
||||
{{~#each query.params}}, {{this.name~}} {{/each}}, value, proof}: DeepPartial<{{capitalize query.name tillIndex=1}}>): Promise<{{capitalize query.name tillIndex=1}}> {
|
||||
const repo = this._conn.getRepository({{capitalize query.name tillIndex=1}});
|
||||
const entity = repo.create({ blockHash, contractAddress
|
||||
{{~#each query.params}}, {{this.name~}} {{/each}}, value, proof });
|
||||
return repo.save(entity);
|
||||
}
|
||||
|
||||
{{/each}}
|
||||
_getColumn (entityName: string, propertyName: string) {
|
||||
return this._conn.getMetadata(entityName).findColumnWithPropertyName(propertyName)?.databaseName
|
||||
}
|
||||
}
|
30
packages/codegen/src/templates/entity-template.handlebars
Normal file
30
packages/codegen/src/templates/entity-template.handlebars
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
{{#if indexOn.columns}}
|
||||
@Index(['blockHash', 'contractAddress'
|
||||
{{~#each indexOn.columns}}, '{{this}}'
|
||||
{{~/each}}], { unique: {{indexOn.unique}} })
|
||||
{{/if}}
|
||||
export class {{className}} {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
||||
@Column('varchar', { length: 66 })
|
||||
blockHash!: string;
|
||||
|
||||
@Column('varchar', { length: 42 })
|
||||
contractAddress!: string;
|
||||
|
||||
{{#each columns as | column |}}
|
||||
@Column('{{column.pgType}}' {{~#if column.length}}, { length: {{column.length}} } {{~/if}})
|
||||
{{column.name}}!: {{column.tsType}};
|
||||
|
||||
{{/each}}
|
||||
@Column('text', { nullable: true })
|
||||
proof!: string;
|
||||
}
|
98
packages/codegen/src/templates/indexer-template.handlebars
Normal file
98
packages/codegen/src/templates/indexer-template.handlebars
Normal file
@ -0,0 +1,98 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import assert from 'assert';
|
||||
import debug from 'debug';
|
||||
import { JsonFragment } from '@ethersproject/abi';
|
||||
import JSONbig from 'json-bigint';
|
||||
import { BigNumber, ethers, Contract } from 'ethers';
|
||||
import { BaseProvider } from '@ethersproject/providers';
|
||||
|
||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||
import { StorageLayout } from '@vulcanize/solidity-mapper';
|
||||
import { ValueResult } from '@vulcanize/util';
|
||||
|
||||
import { Database } from './database';
|
||||
import artifacts from './artifacts/{{inputFileName}}.json';
|
||||
|
||||
const log = debug('vulcanize:indexer');
|
||||
|
||||
export class Indexer {
|
||||
_db: Database
|
||||
_ethClient: EthClient
|
||||
_ethProvider: BaseProvider
|
||||
|
||||
_abi: JsonFragment[]
|
||||
_storageLayout: StorageLayout
|
||||
_contract: ethers.utils.Interface
|
||||
_serverMode: string
|
||||
|
||||
constructor (db: Database, ethClient: EthClient, ethProvider: BaseProvider, serverMode: string) {
|
||||
assert(db);
|
||||
assert(ethClient);
|
||||
|
||||
this._db = db;
|
||||
this._ethClient = ethClient;
|
||||
this._ethProvider = ethProvider;
|
||||
this._serverMode = serverMode;
|
||||
|
||||
const { abi, storageLayout } = artifacts;
|
||||
|
||||
assert(abi);
|
||||
assert(storageLayout);
|
||||
|
||||
this._abi = abi;
|
||||
this._storageLayout = storageLayout;
|
||||
|
||||
this._contract = new ethers.utils.Interface(this._abi);
|
||||
}
|
||||
|
||||
{{#each queries as | query |}}
|
||||
async {{query.name}} (blockHash: string, contractAddress: string
|
||||
{{~#each query.params}}, {{this.name~}}: {{this.type~}} {{/each}}): Promise<ValueResult> {
|
||||
const entity = await this._db.get{{capitalize query.name tillIndex=1}}({ blockHash, contractAddress
|
||||
{{~#each query.params}}, {{this.name~}} {{~/each}} });
|
||||
if (entity) {
|
||||
log('{{query.name}}: db hit.');
|
||||
|
||||
return {
|
||||
value: entity.value,
|
||||
proof: JSON.parse(entity.proof)
|
||||
};
|
||||
}
|
||||
|
||||
log('{{query.name}}: db miss, fetching from upstream server');
|
||||
|
||||
const contract = new Contract(contractAddress, this._abi, this._ethProvider);
|
||||
let value = null;
|
||||
|
||||
{{~#if query.params}}
|
||||
|
||||
const { block: { number } } = await this._ethClient.getBlockByHash(blockHash);
|
||||
const blockNumber = BigNumber.from(number).toNumber();
|
||||
value = await contract.{{query.name}}(
|
||||
{{~#each query.params}}{{this.name}}, {{/each}}{ blockTag: blockNumber });
|
||||
{{else}}
|
||||
|
||||
value = await contract.{{query.name}}();
|
||||
{{/if}}
|
||||
|
||||
{{~#if (compare query.returnType 'bigint')}}
|
||||
|
||||
value = value.toString();
|
||||
value = BigInt(value);
|
||||
{{/if}}
|
||||
|
||||
const result: ValueResult = { value };
|
||||
const { proof } = result;
|
||||
await this._db.save{{capitalize query.name tillIndex=1}}({ blockHash, contractAddress
|
||||
{{~#each query.params}}, {{this.name~}} {{/each}}, value, proof: JSONbig.stringify(proof) });
|
||||
|
||||
return result;
|
||||
}
|
||||
{{#unless @last}}
|
||||
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
}
|
44
packages/codegen/src/templates/package-template.handlebars
Normal file
44
packages/codegen/src/templates/package-template.handlebars
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@vulcanize/{{folderName}}",
|
||||
"version": "0.1.0",
|
||||
"description": "{{folderName}}",
|
||||
"private": true,
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"server": "DEBUG=vulcanize:* ts-node src/server.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vulcanize/watcher-ts.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "AGPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vulcanize/watcher-ts/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vulcanize/watcher-ts#readme",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.3.19",
|
||||
"@vulcanize/cache": "^0.1.0",
|
||||
"@vulcanize/ipld-eth-client": "^0.1.0",
|
||||
"@vulcanize/solidity-mapper": "^0.1.0",
|
||||
"@vulcanize/util": "^0.1.0",
|
||||
"apollo-server-express": "^2.25.0",
|
||||
"apollo-type-bigint": "^0.1.3",
|
||||
"debug": "^4.3.1",
|
||||
"ethers": "^5.2.0",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-import-node": "^0.0.4",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ethersproject/abi": "^5.3.0",
|
||||
"@types/express": "^4.17.11",
|
||||
"@types/yargs": "^17.0.0",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^4.3.2"
|
||||
}
|
||||
}
|
51
packages/codegen/src/templates/readme-template.handlebars
Normal file
51
packages/codegen/src/templates/readme-template.handlebars
Normal file
@ -0,0 +1,51 @@
|
||||
# {{contractName}} Watcher
|
||||
|
||||
## Setup
|
||||
|
||||
* Run the following command to install required packages:
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
* Create a postgres12 database for the watcher:
|
||||
|
||||
```bash
|
||||
sudo su - postgres
|
||||
createdb {{folderName}}
|
||||
```
|
||||
|
||||
* Update `environments/local.toml` with database connection settings.
|
||||
|
||||
* Update the `upstream` config in `environments/local.toml` and provide the `ipld-eth-server` GQL API and the `indexer-db` postgraphile endpoints.
|
||||
|
||||
## Run
|
||||
|
||||
* Run the watcher:
|
||||
|
||||
```bash
|
||||
yarn server
|
||||
```
|
||||
|
||||
GQL console: http://localhost:3008/graphql
|
||||
|
||||
## Demo
|
||||
|
||||
* Install required packages:
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
* Create the database:
|
||||
|
||||
```bash
|
||||
sudo su - postgres
|
||||
createdb {{folderName}}
|
||||
```
|
||||
|
||||
* Run the watcher:
|
||||
|
||||
```bash
|
||||
yarn server
|
||||
```
|
46
packages/codegen/src/templates/resolvers-template.handlebars
Normal file
46
packages/codegen/src/templates/resolvers-template.handlebars
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import assert from 'assert';
|
||||
import BigInt from 'apollo-type-bigint';
|
||||
import debug from 'debug';
|
||||
|
||||
import { ValueResult } from '@vulcanize/util';
|
||||
|
||||
import { Indexer } from './indexer';
|
||||
|
||||
const log = debug('vulcanize:resolver');
|
||||
|
||||
export const createResolvers = async (indexer: Indexer): Promise<any> => {
|
||||
assert(indexer);
|
||||
|
||||
return {
|
||||
BigInt: new BigInt('bigInt'),
|
||||
|
||||
Event: {
|
||||
__resolveType: (obj: any) => {
|
||||
assert(obj.__typename);
|
||||
|
||||
return obj.__typename;
|
||||
}
|
||||
},
|
||||
|
||||
Query: {
|
||||
{{#each queries}}
|
||||
{{this.name}}: (_: any, { blockHash, contractAddress
|
||||
{{~#each this.params}}, {{this.name~}} {{/each}} }: { blockHash: string, contractAddress: string
|
||||
{{~#each this.params}}, {{this.name}}: {{this.type~}} {{/each}} }): Promise<ValueResult> => {
|
||||
log('{{this.name}}', blockHash, contractAddress
|
||||
{{~#each this.params}}, {{this.name~}} {{/each}});
|
||||
return indexer.{{this.name}}(blockHash, contractAddress
|
||||
{{~#each this.params}}, {{this.name~}} {{/each}});
|
||||
}
|
||||
{{~#unless @last}},
|
||||
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
|
||||
}
|
||||
};
|
||||
};
|
97
packages/codegen/src/templates/server-template.handlebars
Normal file
97
packages/codegen/src/templates/server-template.handlebars
Normal file
@ -0,0 +1,97 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import assert from 'assert';
|
||||
import 'reflect-metadata';
|
||||
import express, { Application } from 'express';
|
||||
import { ApolloServer, PubSub } from 'apollo-server-express';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import debug from 'debug';
|
||||
import 'graphql-import-node';
|
||||
import { createServer } from 'http';
|
||||
import { getDefaultProvider } from 'ethers';
|
||||
|
||||
import { getCache } from '@vulcanize/cache';
|
||||
import { EthClient } from '@vulcanize/ipld-eth-client';
|
||||
import { DEFAULT_CONFIG_PATH, getConfig } from '@vulcanize/util';
|
||||
|
||||
import { createResolvers } from './resolvers';
|
||||
import { Indexer } from './indexer';
|
||||
import { Database } from './database';
|
||||
|
||||
const log = debug('vulcanize:server');
|
||||
|
||||
export const main = async (): Promise<any> => {
|
||||
const argv = await yargs(hideBin(process.argv))
|
||||
.option('f', {
|
||||
alias: 'config-file',
|
||||
demandOption: true,
|
||||
describe: 'configuration file path (toml)',
|
||||
type: 'string',
|
||||
default: DEFAULT_CONFIG_PATH
|
||||
})
|
||||
.argv;
|
||||
|
||||
const config = await getConfig(argv.f);
|
||||
|
||||
assert(config.server, 'Missing server config');
|
||||
|
||||
const { host, port, mode } = config.server;
|
||||
|
||||
const { upstream, database: dbConfig } = config;
|
||||
|
||||
assert(dbConfig, 'Missing database config');
|
||||
|
||||
const db = new Database(dbConfig);
|
||||
await db.init();
|
||||
|
||||
assert(upstream, 'Missing upstream config');
|
||||
const { ethServer: { gqlApiEndpoint, gqlPostgraphileEndpoint, rpcProviderEndpoint }, cache: cacheConfig } = upstream;
|
||||
assert(gqlApiEndpoint, 'Missing upstream ethServer.gqlApiEndpoint');
|
||||
assert(gqlPostgraphileEndpoint, 'Missing upstream ethServer.gqlPostgraphileEndpoint');
|
||||
|
||||
const cache = await getCache(cacheConfig);
|
||||
const ethClient = new EthClient({
|
||||
gqlEndpoint: gqlApiEndpoint,
|
||||
gqlSubscriptionEndpoint: gqlPostgraphileEndpoint,
|
||||
cache
|
||||
});
|
||||
|
||||
const ethProvider = getDefaultProvider(rpcProviderEndpoint);
|
||||
|
||||
// Note: In-memory pubsub works fine for now, as each watcher is a single process anyway.
|
||||
// Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
|
||||
const pubsub = new PubSub();
|
||||
const indexer = new Indexer(db, ethClient, ethProvider, mode);
|
||||
|
||||
const resolvers = await createResolvers(indexer);
|
||||
|
||||
const app: Application = express();
|
||||
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString();
|
||||
const server = new ApolloServer({
|
||||
typeDefs,
|
||||
resolvers
|
||||
});
|
||||
|
||||
await server.start();
|
||||
server.applyMiddleware({ app });
|
||||
|
||||
const httpServer = createServer(app);
|
||||
server.installSubscriptionHandlers(httpServer);
|
||||
|
||||
httpServer.listen(port, host, () => {
|
||||
log(`Server is listening on host ${host} port ${port}`);
|
||||
});
|
||||
|
||||
return { app, server };
|
||||
};
|
||||
|
||||
main().then(() => {
|
||||
log('Starting server...');
|
||||
}).catch(err => {
|
||||
log(err);
|
||||
});
|
74
packages/codegen/src/templates/tsconfig-template.handlebars
Normal file
74
packages/codegen/src/templates/tsconfig-template.handlebars
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||
"resolveJsonModule": true /* Enabling the option allows importing JSON, and validating the types in that JSON file. */
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
21
packages/codegen/src/tsconfig.ts
Normal file
21
packages/codegen/src/tsconfig.ts
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Handlebars from 'handlebars';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
const TEMPLATE_FILE = './templates/tsconfig-template.handlebars';
|
||||
|
||||
/**
|
||||
* Writes the tsconfig.json file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the tsconfig.json file to.
|
||||
*/
|
||||
export function exportTSConfig (outStream: Writable): void {
|
||||
const templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
|
||||
const template = Handlebars.compile(templateString);
|
||||
const tsconfig = template({});
|
||||
outStream.write(tsconfig);
|
||||
}
|
1
packages/codegen/src/types/common/main.d.ts
vendored
1
packages/codegen/src/types/common/main.d.ts
vendored
@ -3,3 +3,4 @@
|
||||
//
|
||||
|
||||
declare module '@poanet/solidity-flattener';
|
||||
declare module 'solc';
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "common",
|
||||
"version": "0.1.0",
|
||||
"license": "AGPL-3.0",
|
||||
"typings": "main.d.ts"
|
||||
"name": "common",
|
||||
"version": "0.1.0",
|
||||
"license": "AGPL-3.0",
|
||||
"typings": "main.d.ts"
|
||||
}
|
||||
|
46
packages/codegen/src/utils/handlebar-helpers.ts
Normal file
46
packages/codegen/src/utils/handlebar-helpers.ts
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
/**
|
||||
* Helper function to compare two values using the given operator.
|
||||
* @param lvalue Left hand side value.
|
||||
* @param rvalue Right hasd side value.
|
||||
* @param options Handlebars options parameter. `options.hash.operator`: operator to be used for comparison.
|
||||
* @returns Result of the comparison.
|
||||
*/
|
||||
export function compareHelper (lvalue: string, rvalue: string, options: any): boolean {
|
||||
assert(lvalue && rvalue, "Handlerbars Helper 'compare' needs at least 2 parameters");
|
||||
|
||||
const operator = options.hash.operator || '===';
|
||||
|
||||
const operators: Map<string, (l:any, r:any) => boolean> = new Map();
|
||||
|
||||
operators.set('===', function (l: any, r: any) { return l === r; });
|
||||
operators.set('!==', function (l: any, r: any) { return l !== r; });
|
||||
operators.set('<', function (l: any, r: any) { return l < r; });
|
||||
operators.set('>', function (l: any, r: any) { return l > r; });
|
||||
operators.set('<=', function (l: any, r: any) { return l <= r; });
|
||||
operators.set('>=', function (l: any, r: any) { return l >= r; });
|
||||
|
||||
const operatorFunction = operators.get(operator);
|
||||
assert(operatorFunction, "Handlerbars Helper 'compare' doesn't know the operator " + operator);
|
||||
const result = operatorFunction(lvalue, rvalue);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that capitalized string till given index.
|
||||
* @param value String of which content is to be capitalized.
|
||||
* @param options Handlebars options parameter. `options.hash.tillIndex`: index till which to capitalize the string.
|
||||
* @returns The modified string.
|
||||
*/
|
||||
export function capitalizeHelper (value: string, options: any): string {
|
||||
const tillIndex = options.hash.tillIndex || value.length;
|
||||
const result = `${value.slice(0, tillIndex).toUpperCase()}${value.slice(tillIndex, value.length)}`;
|
||||
|
||||
return result;
|
||||
}
|
42
packages/codegen/src/utils/type-mappings.ts
Normal file
42
packages/codegen/src/utils/type-mappings.ts
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
const _solToTs: Map<string, string> = new Map();
|
||||
const _tsToGql: Map<string, string> = new Map();
|
||||
const _tsToPg: Map<string, string> = new Map();
|
||||
|
||||
// TODO Get typemapping from ethersjs.
|
||||
// Solidity to Typescript type-mapping.
|
||||
_solToTs.set('string', 'string');
|
||||
_solToTs.set('uint8', 'number');
|
||||
_solToTs.set('uint256', 'bigint');
|
||||
_solToTs.set('address', 'string');
|
||||
_solToTs.set('bool', 'boolean');
|
||||
_solToTs.set('bytes4', 'string');
|
||||
|
||||
// Typescript to Graphql type-mapping.
|
||||
_tsToGql.set('string', 'String');
|
||||
_tsToGql.set('number', 'Int');
|
||||
_tsToGql.set('bigint', 'BigInt');
|
||||
_tsToGql.set('boolean', 'Boolean');
|
||||
|
||||
// Typescript to Postgres type-mapping.
|
||||
_tsToPg.set('string', 'varchar');
|
||||
_tsToPg.set('number', 'numeric');
|
||||
_tsToPg.set('bigint', 'numeric');
|
||||
_tsToPg.set('boolean', 'boolean');
|
||||
|
||||
function getTsForSol (solType: string): string | undefined {
|
||||
return _solToTs.get(solType);
|
||||
}
|
||||
|
||||
function getGqlForTs (tsType: string): string | undefined {
|
||||
return _tsToGql.get(tsType);
|
||||
}
|
||||
|
||||
function getPgForTs (tsType: string): string | undefined {
|
||||
return _tsToPg.get(tsType);
|
||||
}
|
||||
|
||||
export { getTsForSol, getGqlForTs, getPgForTs };
|
8
packages/codegen/src/utils/types.ts
Normal file
8
packages/codegen/src/utils/types.ts
Normal file
@ -0,0 +1,8 @@
|
||||
//
|
||||
// Copyright 2021 Vulcanize, Inc.
|
||||
//
|
||||
|
||||
export interface Param {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
@ -3,13 +3,27 @@
|
||||
//
|
||||
|
||||
import { Writable } from 'stream';
|
||||
import { Schema, Param } from './schema';
|
||||
|
||||
import { Database } from './database';
|
||||
import { Entity } from './entity';
|
||||
import { Indexer } from './indexer';
|
||||
import { Resolvers } from './resolvers';
|
||||
import { Schema } from './schema';
|
||||
import { Param } from './utils/types';
|
||||
|
||||
export class Visitor {
|
||||
_schema: Schema;
|
||||
_resolvers: Resolvers;
|
||||
_indexer: Indexer;
|
||||
_entity: Entity;
|
||||
_database: Database;
|
||||
|
||||
constructor () {
|
||||
this._schema = new Schema();
|
||||
this._resolvers = new Resolvers();
|
||||
this._indexer = new Indexer();
|
||||
this._entity = new Entity();
|
||||
this._database = new Database();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,6 +41,10 @@ export class Visitor {
|
||||
const returnType = node.returnParameters[0].typeName.name;
|
||||
|
||||
this._schema.addQuery(name, params, returnType);
|
||||
this._resolvers.addQuery(name, params, returnType);
|
||||
this._indexer.addQuery(name, params, returnType);
|
||||
this._entity.addQuery(name, params, returnType);
|
||||
this._database.addQuery(name, params, returnType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +74,10 @@ export class Visitor {
|
||||
const returnType = typeName.name;
|
||||
|
||||
this._schema.addQuery(name, params, returnType);
|
||||
this._resolvers.addQuery(name, params, returnType);
|
||||
this._indexer.addQuery(name, params, returnType);
|
||||
this._entity.addQuery(name, params, returnType);
|
||||
this._database.addQuery(name, params, returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,4 +100,37 @@ export class Visitor {
|
||||
exportSchema (outStream: Writable): void {
|
||||
this._schema.exportSchema(outStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the resolvers file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the resolvers file to.
|
||||
*/
|
||||
exportResolvers (outStream: Writable): void {
|
||||
this._resolvers.exportResolvers(outStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the indexer file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the indexer file to.
|
||||
* @param inputFileName Input contract file name to be passed to the template.
|
||||
*/
|
||||
exportIndexer (outStream: Writable, inputFileName: string): void {
|
||||
this._indexer.exportIndexer(outStream, inputFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the generated entity files in the given directory.
|
||||
* @param entityDir Directory to write the entities to.
|
||||
*/
|
||||
exportEntities (entityDir: string): void {
|
||||
this._entity.exportEntities(entityDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the database file generated from a template to a stream.
|
||||
* @param outStream A writable output stream to write the database file to.
|
||||
*/
|
||||
exportDatabase (outStream: Writable): void {
|
||||
this._database.exportDatabase(outStream);
|
||||
}
|
||||
}
|
||||
|
@ -44,12 +44,13 @@
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
"typeRoots": [
|
||||
"./src/types"
|
||||
"./src/types",
|
||||
"node_modules/@types"
|
||||
], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
@ -69,7 +70,8 @@
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||
"resolveJsonModule": true /* Enabling the option allows importing JSON, and validating the types in that JSON file. */
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
19
yarn.lock
19
yarn.lock
@ -1991,7 +1991,7 @@
|
||||
|
||||
"@poanet/solidity-flattener@https://github.com/vulcanize/solidity-flattener.git":
|
||||
version "3.0.6"
|
||||
resolved "https://github.com/vulcanize/solidity-flattener.git#397f7295acff78eb039e479a85e8c3a9d78a96dd"
|
||||
resolved "https://github.com/vulcanize/solidity-flattener.git#9c8a22b9c43515be306646a177a43636fd95aaae"
|
||||
dependencies:
|
||||
bunyan "^1.8.12"
|
||||
decomment "^0.9.1"
|
||||
@ -7578,7 +7578,7 @@ growl@1.10.5:
|
||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
|
||||
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
|
||||
|
||||
handlebars@^4.7.6:
|
||||
handlebars@^4.7.6, handlebars@^4.7.7:
|
||||
version "4.7.7"
|
||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
|
||||
integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
|
||||
@ -12421,6 +12421,21 @@ solc@^0.6.3:
|
||||
semver "^5.5.0"
|
||||
tmp "0.0.33"
|
||||
|
||||
solc@^0.8.7-fixed:
|
||||
version "0.8.7-fixed"
|
||||
resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.7-fixed.tgz#76eb37d33637ad278ad858e2633e9da597d877ac"
|
||||
integrity sha512-nWZRkdPwfBpimAelO30Bz7/hxoj+mylb30gEpBL8hhEWR4xqu2ezQAxWK1Hz5xx1NqesbgGjSgnGul49tRHWgQ==
|
||||
dependencies:
|
||||
command-exists "^1.2.8"
|
||||
commander "3.0.2"
|
||||
follow-redirects "^1.12.1"
|
||||
fs-extra "^0.30.0"
|
||||
js-sha3 "0.8.0"
|
||||
memorystream "^0.3.1"
|
||||
require-from-string "^2.0.0"
|
||||
semver "^5.5.0"
|
||||
tmp "0.0.33"
|
||||
|
||||
sort-keys@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
|
||||
|
Loading…
Reference in New Issue
Block a user