├── .gitignore ├── common └── schema.js ├── config.json ├── index.ts ├── package.json ├── scripts └── graph-compiler │ ├── cli.js │ ├── config.js │ ├── schema │ ├── index.js │ ├── schema.js │ ├── schema_entry.js │ └── schema_entry_field.js │ ├── subgraph │ ├── index.js │ └── subgraph.js │ └── utils.js ├── src ├── account.gql.json ├── constants.ts ├── decimals.gql.json ├── decimals.ts ├── events.gql.json ├── events.ts ├── index.ts ├── integers.ts ├── persistent │ ├── index.ts │ ├── string.gql.json │ ├── string.ts │ ├── string2.ts │ ├── stringarray.gql.json │ ├── stringarray.ts │ └── stringarray2.ts ├── transactions.gql.json └── transactions.ts └── testing ├── GenericFactory.json ├── mapping.ts └── subgraph.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | generated 3 | node_modules 4 | 5 | package-lock.json 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /common/schema.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = [ 4 | '../src/account.gql.json', 5 | '../src/decimals.gql.json', 6 | '../src/events.gql.json', 7 | '../src/transactions.gql.json', 8 | '../src/persistent/string.gql.json', 9 | '../src/persistent/stringarray.gql.json', 10 | ].flatMap(name => require(path.resolve(__dirname, name))); 11 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "output": "./generated/", 3 | "datasources": [ 4 | { 5 | "module": [ 6 | "account", 7 | "decimals", 8 | "events", 9 | "transactions", 10 | "persistent/string", 11 | "persistent/stringarray" 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './src'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@amxx/graphprotocol-utils", 3 | "version": "1.2.0", 4 | "description": "Tooling for graphprotocol subgraph construction", 5 | "author": "Hadrien Croubois (@Amxx)", 6 | "license": "MIT", 7 | "main": "index.ts", 8 | "bin": { 9 | "graph-compiler": "scripts/graph-compiler/cli.js" 10 | }, 11 | "files": [ 12 | "generated/schema.ts", 13 | "generated/schema.graphql", 14 | "common", 15 | "scripts", 16 | "src", 17 | "index.ts" 18 | ], 19 | "scripts": { 20 | "prepublish": "rimraf generated", 21 | "prepare": "npm run prepare:schema && npm run prepare:codegen", 22 | "prepare:schema": "scripts/graph-compiler/cli.js --config config.json --include src --no-common-types --export-schema", 23 | "prepare:codegen": "graph codegen testing/subgraph.yaml", 24 | "test": "graph build testing/subgraph.yaml" 25 | }, 26 | "dependencies": { 27 | "@graphprotocol/graph-ts": "^0.35.1", 28 | "yargs": "^17.5.1" 29 | }, 30 | "devDependencies": { 31 | "@graphprotocol/graph-cli": "^0.81.0" 32 | }, 33 | "engines": { 34 | "node": ">=0.12" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scripts/graph-compiler/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const { Config } = require('./config'); 5 | const { Schema } = require('./schema'); 6 | const { Subgraph } = require('./subgraph'); 7 | const { writeFile } = require('./utils'); 8 | 9 | const argv = require('yargs') 10 | .env() 11 | .option('config', { type: 'string', required: true }) 12 | .option('common-types', { type: 'string', default: '@amxx/graphprotocol-utils/common/schema.js' }) 13 | .option('include', { type: 'array', default: [] }) 14 | .option('export-schema', { type: 'boolean', default: false }) 15 | .option('export-subgraph', { type: 'boolean', default: false }) 16 | .option('root', { type: 'string', default: '.' }) 17 | .argv; 18 | 19 | const config = new Config(argv); 20 | 21 | if (argv.exportSchema) { 22 | writeFile(config.schemaPath(), Schema.fromConfig(config).toString()); 23 | console.log(`- Schema exported to ${config.schemaPath()}`) 24 | } 25 | 26 | if (argv.exportSubgraph) { 27 | writeFile(config.subgraphPath(), Subgraph.fromConfig(config).toString()); 28 | console.log(`- Manifest exported to ${config.subgraphPath()}`) 29 | } 30 | -------------------------------------------------------------------------------- /scripts/graph-compiler/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | class Config { 4 | constructor(argv) { 5 | module.paths.unshift( 6 | path.resolve('.'), 7 | ...argv.include.map(folder => path.resolve(folder)) 8 | ); 9 | 10 | this.require = require; 11 | this._cfg = require(argv.config); 12 | this._argv = argv; 13 | } 14 | 15 | modules() { 16 | return this._cfg.datasources.flatMap(({ module, templates }) => [].concat(module, templates)).filter(Boolean).unique(); 17 | } 18 | 19 | datasources() { 20 | return this._cfg.datasources; 21 | } 22 | 23 | templates() { 24 | return this._cfg.datasources.flatMap(({ templates }) => templates).filter(Boolean).unique(); 25 | } 26 | 27 | root() { 28 | return this._argv.root; 29 | } 30 | 31 | schemaPath() { return `${this._cfg.output}schema.graphql`; } 32 | subgraphPath() { return `${this._cfg.output}subgraph.yaml`; } 33 | } 34 | 35 | module.exports = { 36 | Config, 37 | }; 38 | -------------------------------------------------------------------------------- /scripts/graph-compiler/schema/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Schema: require('./schema'), 3 | SchemaEntry: require('./schema_entry'), 4 | SchemaEntryField: require('./schema_entry_field'), 5 | }; 6 | -------------------------------------------------------------------------------- /scripts/graph-compiler/schema/schema.js: -------------------------------------------------------------------------------- 1 | const SchemaEntry = require('./schema_entry'); 2 | 3 | class Schema extends Array { 4 | static fromConfig(cfg) { 5 | const entries = cfg.modules() 6 | .flatMap(module => cfg.require(`${module}.gql.json`)) 7 | .map(SchemaEntry.from); 8 | 9 | const commons = (cfg._argv.commonTypes ? cfg.require(cfg._argv.commonTypes) : []) 10 | .filter(({ name }) => entries.some(({ parent, fields }) => parent == name || fields.map(({ type }) => type.replace(/[\[\]\!]/, '')).includes(name))) 11 | .map(SchemaEntry.from); 12 | 13 | return Schema.from([].concat(entries, commons)).sanitize(); 14 | } 15 | 16 | sanitize() { 17 | return Schema.from( 18 | this 19 | .map(({ name }) => name) 20 | .unique() 21 | .map(name => this.filter(entry => name === entry.name).reduce(SchemaEntry.merge, {})) 22 | .map(entry => SchemaEntry.ensureId(entry)), 23 | ); 24 | } 25 | 26 | toString() { 27 | return this.join(''); 28 | } 29 | } 30 | 31 | module.exports = Schema; 32 | -------------------------------------------------------------------------------- /scripts/graph-compiler/schema/schema_entry.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('../utils'); 2 | 3 | const SchemaEntryField = require('./schema_entry_field'); 4 | 5 | class SchemaEntry { 6 | constructor({ name, abstract = false, immutable = false, fields = [], enums = [], parent = null }) { 7 | assert( 8 | enums.length == 0 || fields.length == 0, 9 | `Error loading schema entry ${name}: Entry contains both enums and fields`, 10 | ); 11 | this.name = name; 12 | this.abstract = abstract; 13 | this.immutable = immutable; 14 | this.fields = fields.map(SchemaEntryField.from); 15 | this.enums = enums; 16 | this.parent = parent; 17 | } 18 | 19 | toString() { 20 | return [].concat( 21 | // entity header 22 | this.enums.length > 0 23 | ? `enum ${this.name} {` 24 | : this.abstract 25 | ? `interface ${this.name} {` 26 | : !this.parent 27 | ? !this.immutable 28 | ? `type ${this.name} @entity {` 29 | : `type ${this.name} @entity(immutable: true) {` 30 | : !this.immutable 31 | ? `type ${this.name} implements ${this.parent} @entity {` 32 | : `type ${this.name} implements ${this.parent} @entity(immutable: true) {`, 33 | // entities 34 | ( 35 | this.enums.length == 0 36 | ? this.fields 37 | : this.enums 38 | ).map(e => `\t${e}`), 39 | `}`, 40 | '', 41 | ).join('\n') 42 | } 43 | 44 | static ensureId(e) { 45 | if (e.enums.length == 0 && !e.fields.some(field => field.name === 'id')) { 46 | e.fields.unshift(new SchemaEntryField()); 47 | } 48 | return e; 49 | } 50 | 51 | static from(obj) { 52 | return new SchemaEntry(obj); 53 | } 54 | 55 | static merge(e1, e2) { 56 | if (Object.isEmpty(e1)) { 57 | return e2; 58 | } else if (Object.isEmpty(e2)) { 59 | return e1; 60 | } else { 61 | assert( 62 | e1.name === e2.name, 63 | `Error merging schema entries: name do not match (${e1.name} / ${e2.name})`, 64 | ); 65 | assert( 66 | !e1.parent || !e2.parent || e1.parent == e2.parent, 67 | `Error merging schema entries: inheritance do not match for ${e1.name}`, 68 | ); 69 | assert( 70 | !!e1.enums === !!e2.enums, 71 | `Error merging schema entries: enum/type clash for ${e1.name}`, 72 | ); 73 | assert( 74 | e1.fields.every(f1 => e2.fields.every(f2 => !SchemaEntryField.conflict(f1, f2))), 75 | `Error merging schema entries: incompatible fields found for ${e1.name}`, 76 | ); 77 | 78 | return SchemaEntry.from({ 79 | name: e1.name, 80 | abstract: e1.abstract && e2.abstract, 81 | immutable: e1.immutable && e2.immutable, 82 | fields: [].concat(e1.fields, e2.fields).unique(({ name }) => name), 83 | enums: [].concat(e1.enums, e2.enums).unique(), 84 | parent: e1.parent || e2.parent, 85 | }); 86 | } 87 | } 88 | } 89 | 90 | module.exports = SchemaEntry; 91 | -------------------------------------------------------------------------------- /scripts/graph-compiler/schema/schema_entry_field.js: -------------------------------------------------------------------------------- 1 | class SchemaEntryField { 2 | constructor({ name = 'id', type = 'ID!', derived = null } = {}) { 3 | this.name = name; 4 | this.type = type; 5 | this.derived = derived; 6 | } 7 | 8 | toString() { 9 | return this.derived 10 | ? `${this.name}: [${this.type}]! @derivedFrom(field: "${this.derived}")` 11 | : `${this.name}: ${this.type}`; 12 | } 13 | 14 | static from(obj) { 15 | return new SchemaEntryField(obj); 16 | } 17 | 18 | static conflict(f1, f2) { 19 | return f1.name === f2.name && (f1.type !== f2.type || f1.derived !== f2.derived); 20 | } 21 | } 22 | 23 | module.exports = SchemaEntryField; 24 | -------------------------------------------------------------------------------- /scripts/graph-compiler/subgraph/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Subgraph: require('./subgraph'), 3 | }; 4 | -------------------------------------------------------------------------------- /scripts/graph-compiler/subgraph/subgraph.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { readFile } = require('../utils'); 4 | 5 | class Subgraph { 6 | static fromConfig(config) { 7 | const relative = (file) => path.relative(path.dirname(config.subgraphPath()), file) || '.'; 8 | const modules = Object.fromEntries( 9 | config.modules() 10 | .map(module => { 11 | try { 12 | return [ 13 | module, 14 | { 15 | yaml: config.require.resolve(`${module}.yaml`), 16 | ts: config.require.resolve(`${module}.ts`), 17 | } 18 | ]; 19 | } catch { 20 | return undefined 21 | } 22 | }) 23 | .filter(Boolean) 24 | ); 25 | 26 | Object.values(modules).forEach(item => Object.assign(item, { template: readFile(item.yaml) })); 27 | 28 | return [].concat( 29 | `specVersion: 0.0.2\n`, 30 | `schema:\n`, 31 | ` file: ${relative(config.schemaPath())}\n`, 32 | // datasources 33 | config.datasources().length && `dataSources:\n`, 34 | config.datasources() 35 | .flatMap(datasource => [].concat(datasource.module || []).map(module => Object.assign({}, datasource, { module }))) 36 | .map((datasource, i, array) => Object.assign( 37 | {}, 38 | config._argv, 39 | config._cfg, 40 | { 41 | id: array.findIndex(({ module }) => module === datasource.module) === i ? datasource.module : `${datasource.module}-${i}`, 42 | root: relative(config.root()), 43 | file: relative(modules[datasource.module].ts), 44 | }, 45 | datasource, 46 | )) 47 | .map(datasource => [].concat( 48 | modules[datasource.module].template 49 | .replace(/\{(\w+)\}/g, (_, varname) => datasource[varname]) 50 | .replace(/^.*undefined.*\n/mg, ''), 51 | modules[datasource.module].template.search(/^( {6}|\t{3})file:/gm) === -1 52 | && ` file: ${relative(modules[datasource.module].ts)}\n`, 53 | ).filter(Boolean).join('')), 54 | // templates 55 | config.templates().length && `templates:\n`, 56 | config.templates() 57 | .map(template => Object.assign( 58 | {}, 59 | config._argv, 60 | config._cfg, 61 | { 62 | id: template, 63 | root: relative(config.root()), 64 | }, 65 | )) 66 | .map(template => [].concat( 67 | modules[template.id].template 68 | .replace(/\{(\w+)\}/g, (_, varname) => template[varname]) 69 | .replace(/^.*undefined.*\n/mg, ''), 70 | ` file: ${relative(modules[template.id].ts)}\n`, 71 | ).join('')), 72 | ).filter(Boolean).join(''); 73 | } 74 | } 75 | 76 | module.exports = Subgraph; 77 | -------------------------------------------------------------------------------- /scripts/graph-compiler/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | Array.prototype.unique = function(op = x => x) { 5 | return this.filter((obj, i) => this.findIndex(entry => op(obj) === op(entry)) === i); 6 | } 7 | 8 | Object.prototype.isEmpty = function(obj) { 9 | return Object.keys(obj).length === 0; 10 | } 11 | 12 | function assert(condifiton, error = 'assertion failed') { 13 | if (!condifiton) { 14 | throw new Error(error); 15 | } 16 | } 17 | 18 | function readFile(file) { 19 | return fs.readFileSync(file, { encoding: 'utf8' }); 20 | } 21 | 22 | function writeFile(file, data) { 23 | fs.mkdirSync(path.dirname(file), { recursive: true }); 24 | fs.writeFileSync(file, data, { encoding: 'utf-8' }); 25 | } 26 | 27 | module.exports = { 28 | assert, 29 | readFile, 30 | writeFile, 31 | }; 32 | -------------------------------------------------------------------------------- /src/account.gql.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "Account", 3 | "abstract": true, 4 | "immutable": true, 5 | "fields": [ 6 | { "name": "id", "type": "Bytes!" }, 7 | { "name": "events", "type": "Event!", "derived": "emitter" } 8 | ] 9 | }] 10 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { Address, BigDecimal, BigInt, Bytes } from '@graphprotocol/graph-ts' 2 | 3 | export namespace constants { 4 | export let BIGINT_ZERO = BigInt.fromI32(0) 5 | export let BIGINT_ONE = BigInt.fromI32(1) 6 | export let BIGDECIMAL_ZERO = new BigDecimal(constants.BIGINT_ZERO) 7 | export let BIGDECIMAL_ONE = new BigDecimal(constants.BIGINT_ONE) 8 | export const ADDRESS_ZERO = Address.fromString('0x0000000000000000000000000000000000000000') 9 | export const BYTES32_ZERO = Bytes.fromHexString('0x0000000000000000000000000000000000000000000000000000000000000000') as Bytes 10 | } 11 | -------------------------------------------------------------------------------- /src/decimals.gql.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "DecimalValue", 3 | "fields": [ 4 | { "name": "value", "type": "BigDecimal!" }, 5 | { "name": "exact", "type": "BigInt!" }, 6 | { "name": "decimals", "type": "Int!" } 7 | ] 8 | }] 9 | -------------------------------------------------------------------------------- /src/decimals.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BigDecimal, 3 | BigInt 4 | } from '@graphprotocol/graph-ts' 5 | 6 | import { 7 | DecimalValue, 8 | } from '../generated/schema' 9 | 10 | import { 11 | constants 12 | } from './constants' 13 | 14 | import { 15 | integers 16 | } from './integers' 17 | 18 | export namespace decimals { 19 | export const DEFAULT_DECIMALS = 18 20 | export function toDecimals(value: BigInt, decimals: number = DEFAULT_DECIMALS): BigDecimal { 21 | let precision = BigInt.fromI32(10).pow(decimals).toBigDecimal() 22 | return value.divDecimal(precision) 23 | } 24 | 25 | export class Value { 26 | _entry!: DecimalValue; 27 | 28 | constructor(id: string, decimal: i32 = decimals.DEFAULT_DECIMALS) { 29 | let entry = DecimalValue.load(id) 30 | if (entry == null) { 31 | this._entry = new DecimalValue(id) 32 | this._entry.exact = constants.BIGINT_ZERO 33 | this._entry.decimals = decimal 34 | this._update() 35 | } else { 36 | this._entry = entry 37 | } 38 | } 39 | 40 | static fetch(id: string, decimal: i32 = decimals.DEFAULT_DECIMALS): Value { 41 | return new Value(id, decimal) 42 | } 43 | 44 | get id(): string { 45 | return this._entry.id; 46 | } 47 | 48 | get exact() : BigInt { 49 | return this._entry.exact; 50 | } 51 | 52 | get value() : BigDecimal { 53 | return this._entry.value; 54 | } 55 | 56 | get decimals() : i32 { 57 | return this._entry.decimals; 58 | } 59 | 60 | set(exact: BigInt): Value { 61 | this._entry.exact = exact 62 | this._update() 63 | return this 64 | } 65 | 66 | increment(delta: BigInt): Value { 67 | this._entry.exact = integers.increment(this._entry.exact, delta) 68 | this._update() 69 | return this 70 | } 71 | 72 | decrement(delta: BigInt): Value { 73 | this._entry.exact = integers.decrement(this._entry.exact, delta) 74 | this._update() 75 | return this 76 | } 77 | 78 | _update() : void { 79 | this._entry.value = decimals.toDecimals(this._entry.exact, this._entry.decimals) 80 | this._entry.save() 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/events.gql.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "Event", 3 | "abstract": true, 4 | "fields": [ 5 | { "name": "transaction", "type": "Transaction!" }, 6 | { "name": "emitter", "type": "Account!" }, 7 | { "name": "timestamp", "type": "BigInt!" } 8 | ] 9 | }] 10 | -------------------------------------------------------------------------------- /src/events.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ethereum, 3 | } from '@graphprotocol/graph-ts' 4 | 5 | export namespace events { 6 | export function id(event: ethereum.Event): string { 7 | return event.block.number.toString().concat('-').concat(event.logIndex.toString()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './persistent' 2 | export * from './constants' 3 | export * from './decimals' 4 | export * from './events' 5 | export * from './integers' 6 | export * from './transactions' 7 | -------------------------------------------------------------------------------- /src/integers.ts: -------------------------------------------------------------------------------- 1 | import { BigInt } from '@graphprotocol/graph-ts' 2 | import { constants } from './constants' 3 | 4 | export namespace integers { 5 | export function increment(num: BigInt, amount: BigInt = constants.BIGINT_ONE): BigInt { 6 | return num.plus(amount) 7 | } 8 | export function decrement(num: BigInt, amount: BigInt = constants.BIGINT_ONE): BigInt { 9 | return num.minus(amount) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/persistent/index.ts: -------------------------------------------------------------------------------- 1 | import { String } from './string'; 2 | import { String2 } from './string2'; 3 | import { StringArray } from './stringarray'; 4 | import { StringArray2 } from './stringarray2'; 5 | 6 | export namespace persistent { 7 | export class string extends String {} 8 | export class STRING extends String2 {} 9 | export class stringarray extends StringArray {} 10 | export class STRINGARRAY extends StringArray2 {} 11 | }; 12 | -------------------------------------------------------------------------------- /src/persistent/string.gql.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "PersistentString", 3 | "fields": [{ "name": "value", "type": "String!" }] 4 | }] 5 | -------------------------------------------------------------------------------- /src/persistent/string.ts: -------------------------------------------------------------------------------- 1 | import { 2 | store, 3 | } from '@graphprotocol/graph-ts' 4 | 5 | import { 6 | PersistentString, 7 | } from '../../generated/schema' 8 | 9 | export class String { 10 | static set(id: string, value: string): void { 11 | let buffer = new PersistentString(id) 12 | buffer.value = value 13 | buffer.save() 14 | } 15 | 16 | static get(id: string): string { 17 | let buffer = PersistentString.load(id) 18 | return buffer == null ? null : buffer.value 19 | } 20 | 21 | static del(id: string): void { 22 | store.remove('PersistentString', id) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/persistent/string2.ts: -------------------------------------------------------------------------------- 1 | import { 2 | store, 3 | } from '@graphprotocol/graph-ts' 4 | 5 | import { 6 | PersistentString, 7 | } from '../../generated/schema' 8 | 9 | export class String2 { 10 | _id: string; 11 | _buffer: PersistentString | null; 12 | 13 | constructor(id: string) { 14 | this._id = id; 15 | this._buffer = null; 16 | } 17 | 18 | load(): PersistentString | null { 19 | if (this._buffer) return this._buffer; 20 | this._buffer = PersistentString.load(this._id); 21 | if (this._buffer) return this._buffer; 22 | this._buffer = new PersistentString(this._id); 23 | return this._buffer; 24 | } 25 | 26 | get(): string { 27 | this.load(); 28 | return this._buffer.value; 29 | } 30 | 31 | set(value: string): void { 32 | this.load() 33 | this._buffer!.value = value; 34 | this._buffer!.save(); 35 | } 36 | 37 | del(): void { 38 | store.remove('PersistentString', id); 39 | this._buffer = null; 40 | } 41 | 42 | get id(): string { 43 | return this._id; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/persistent/stringarray.gql.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "PersistentStringArray", 3 | "fields": [{ "name": "values", "type": "[String!]!" }] 4 | }] 5 | -------------------------------------------------------------------------------- /src/persistent/stringarray.ts: -------------------------------------------------------------------------------- 1 | import { 2 | store, 3 | } from '@graphprotocol/graph-ts' 4 | 5 | import { 6 | PersistentStringArray, 7 | } from '../../generated/schema' 8 | 9 | export class StringArray { 10 | static pushBack(id: string, value: string) : void { 11 | let buffer = PersistentStringArray.load(id) 12 | if (buffer == null) { 13 | buffer = new PersistentStringArray(id) 14 | buffer.values = [] 15 | } 16 | let array = buffer.values 17 | array.push(value) 18 | buffer.values = array 19 | buffer.save() 20 | } 21 | 22 | static pushFront(id: string, value: string) : void { 23 | let buffer = PersistentStringArray.load(id) 24 | if (buffer == null) { 25 | buffer = new PersistentStringArray(id) 26 | buffer.values = [] 27 | } 28 | let array = buffer.values 29 | array.unshift(value) 30 | buffer.values = array 31 | buffer.save() 32 | } 33 | 34 | static popBack(id: string) : string { 35 | let buffer = PersistentStringArray.load(id) 36 | if (buffer == null) { 37 | return null 38 | } 39 | let array = buffer.values 40 | if (array.length == 0) { 41 | return null 42 | } 43 | let value = array.pop() 44 | buffer.values = array 45 | buffer.save() 46 | return value 47 | } 48 | 49 | static popFront(id: string) : string { 50 | let buffer = PersistentStringArray.load(id) 51 | if (buffer == null) 52 | { 53 | return null 54 | } 55 | let array = buffer.values 56 | if (array.length == 0) { 57 | return null 58 | } 59 | let value = array.shift() 60 | buffer.values = array 61 | buffer.save() 62 | return value 63 | } 64 | 65 | static length(id: string) : i32 { 66 | let buffer = ArrayBuffer.load(id) 67 | return buffer == null ? 0 : buffer.values.length 68 | } 69 | 70 | static del(id: string): void { 71 | store.remove('PersistentStringArray', id) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/persistent/stringarray2.ts: -------------------------------------------------------------------------------- 1 | import { 2 | store, 3 | } from '@graphprotocol/graph-ts' 4 | 5 | import { 6 | PersistentStringArray, 7 | } from '../../generated/schema' 8 | 9 | export class StringArray2 { 10 | _id: string; 11 | _buffer: PersistentStringArray | null; 12 | 13 | constructor(id: string) { 14 | this._id = id; 15 | this._buffer = null; 16 | } 17 | 18 | load(): PersistentStringArray | null { 19 | if (this._buffer) return this._buffer; 20 | this._buffer = PersistentStringArray.load(this._id); 21 | if (this._buffer) return this._buffer; 22 | this._buffer = new PersistentStringArray(this._id); 23 | this._buffer!.values = []; 24 | return this._buffer; 25 | } 26 | 27 | pushBack(value: string) : void { 28 | this.load() 29 | let array = this._buffer!.values 30 | array.push(value) 31 | this._buffer!.values = array 32 | this._buffer!.save() 33 | } 34 | 35 | pushFront(value: string) : void { 36 | this.load() 37 | let array = this._buffer!.values 38 | array.unshift(value) 39 | this._buffer!.values = array 40 | this._buffer!.save() 41 | } 42 | 43 | popBack() : string { 44 | this.load() 45 | if (this._buffer == null) { 46 | return null 47 | } 48 | let array = this._buffer.values 49 | if (array.length == 0) { 50 | return null 51 | } 52 | let value = array.pop() 53 | this._buffer.values = array 54 | this._buffer.save() 55 | return value 56 | } 57 | 58 | popFront() : string { 59 | this.load() 60 | if (this._buffer == null) { 61 | return null 62 | } 63 | let array = this._buffer.values 64 | if (array.length == 0) { 65 | return null 66 | } 67 | let value = array.shift() 68 | this._buffer.values = array 69 | this._buffer.save() 70 | return value 71 | } 72 | 73 | length() : i32 { 74 | this.load() 75 | return this._buffer.values.length 76 | } 77 | 78 | del(): void { 79 | store.remove('PersistentStringArray', id); 80 | this._buffer = null; 81 | } 82 | 83 | get id(): string { 84 | return this._id; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/transactions.gql.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "name": "Transaction", 3 | "immutable": true, 4 | "fields": [ 5 | { "name": "timestamp", "type": "BigInt!" }, 6 | { "name": "blockNumber", "type": "BigInt!" }, 7 | { "name": "events", "type": "Event!", "derived": "transaction" } 8 | ] 9 | }] 10 | -------------------------------------------------------------------------------- /src/transactions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ethereum, 3 | } from '@graphprotocol/graph-ts' 4 | 5 | import { 6 | Transaction, 7 | } from '../generated/schema' 8 | 9 | export namespace transactions { 10 | export function log(event: ethereum.Event): Transaction { 11 | let tx = new Transaction(event.transaction.hash.toHex()) 12 | tx.timestamp = event.block.timestamp 13 | tx.blockNumber = event.block.number 14 | tx.save() 15 | return tx as Transaction 16 | } 17 | export type Tx = Transaction 18 | } 19 | -------------------------------------------------------------------------------- /testing/GenericFactory.json: -------------------------------------------------------------------------------- 1 | {"abi":[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"}],"name":"NewContract","type":"event"},{"inputs":[{"internalType":"bytes","name":"code","type":"bytes"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"name":"createContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"code","type":"bytes"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"bytes","name":"call","type":"bytes"}],"name":"createContractAndCall","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"code","type":"bytes"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"name":"predictAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"code","type":"bytes"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"bytes","name":"call","type":"bytes"}],"name":"predictAddressWithCall","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]} 2 | -------------------------------------------------------------------------------- /testing/mapping.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | } from '@graphprotocol/graph-ts' 4 | 5 | import { 6 | NewContract as NewContractEvent 7 | } from '../generated/GenericFactory/GenericFactory' 8 | 9 | import { 10 | constants, 11 | decimals, 12 | events, 13 | integers, 14 | persistent, 15 | transactions, 16 | } from '../src' 17 | 18 | export function handleNewContract(ev: NewContractEvent): void { 19 | let tx = transactions.log(ev); 20 | let id = events.id(ev); 21 | let fees = decimals.toDecimals(ev.transaction.gasPrice.times(ev.transaction.gasLimit)) 22 | log.warning("tx: {}, id: {}, fees: {}", [ tx.id, id, fees.toString() ]) 23 | 24 | let i = constants.BIGINT_ONE 25 | i = integers.increment(i) 26 | i = integers.decrement(i) 27 | 28 | let d = new decimals.Value("mydecimalvalue", 9) 29 | d.increment(constants.BIGINT_ONE) 30 | let did = d.id 31 | 32 | persistent.string.set("Key", "Value") 33 | persistent.stringarray.pushBack("Key", "Value1") 34 | persistent.stringarray.pushFront("Key", "Value2") 35 | persistent.stringarray.pushBack("Key", "Value3") 36 | 37 | let s = new persistent.STRING("OtherKey") 38 | s.set("OtherValue") 39 | let sid = s.id 40 | 41 | let sa = new persistent.STRINGARRAY("OtherKey") 42 | sa.pushBack("Value1") 43 | sa.pushFront("Value2") 44 | sa.pushBack("Value3") 45 | let said = sa.id 46 | } 47 | -------------------------------------------------------------------------------- /testing/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.4 2 | description: graphprotocol-utils 3 | repository: https://github.com/Amxx/graphprotocol-utils 4 | schema: 5 | file: ../generated/schema.graphql 6 | dataSources: 7 | - name: GenericFactory 8 | kind: ethereum/contract 9 | network: mainnet 10 | source: 11 | address: '0xfac000a12da42b871c0aad5f25391aae62958db1' 12 | startBlock: 9665920 13 | abi: GenericFactory 14 | mapping: 15 | kind: ethereum/events 16 | apiVersion: 0.0.6 17 | language: wasm/assemblyscript 18 | entities: 19 | - Transaction 20 | abis: 21 | - name: GenericFactory 22 | file: ./GenericFactory.json 23 | eventHandlers: 24 | - event: NewContract(indexed address) 25 | handler: handleNewContract 26 | file: ./mapping.ts 27 | --------------------------------------------------------------------------------