Skip to content
Snippets Groups Projects
Commit 05f90fcb authored by Eldar Gabdullin's avatar Eldar Gabdullin
Browse files

first commit

parents
No related branches found
No related tags found
No related merge requests found
Showing with 520 additions and 0 deletions
.env 0 → 100644
DB_PORT=23798
GQL_PORT=4350
/node_modules
/lib
/chainVersions.json
# IDE files
/.idea
Makefile 0 → 100644
process:
@node -r dotenv/config lib/processor.js
serve:
@npx squid-graphql-server
migrate:
@npx sqd db:migrate
migration:
@npx sqd db:create-migration Data
build:
@npm run build
codegen:
@npx sqd codegen
typegen:
@npx squid-substrate-typegen typegen.json
explore:
@npx squid-substrate-metadata-explorer \
--chain wss://kusama-rpc.polkadot.io \
--archive https://kusama.indexer.gc.subsquid.io/v4/graphql \
--out kusamaVersions.json
up:
@docker-compose up -d
down:
@docker-compose down
.PHONY: process serve start codegen migration migrate up down
module.exports = class Data1641285739348 {
name = 'Data1641285739348'
async up(db) {
await db.query(`CREATE TABLE "historical_balance" ("id" character varying NOT NULL, "balance" numeric NOT NULL, "date" TIMESTAMP WITH TIME ZONE NOT NULL, "account_id" character varying NOT NULL, CONSTRAINT "PK_74ac29ad0bdffb6d1281a1e17e8" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_383ff006e4b59db91d32cb891e" ON "historical_balance" ("account_id") `)
await db.query(`CREATE TABLE "account" ("id" character varying NOT NULL, "balance" numeric NOT NULL, CONSTRAINT "PK_54115ee388cdb6d86bb4bf5b2ea" PRIMARY KEY ("id"))`)
await db.query(`ALTER TABLE "historical_balance" ADD CONSTRAINT "FK_383ff006e4b59db91d32cb891e9" FOREIGN KEY ("account_id") REFERENCES "account"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
}
async down(db) {
await db.query(`DROP TABLE "historical_balance"`)
await db.query(`DROP INDEX "public"."IDX_383ff006e4b59db91d32cb891e"`)
await db.query(`DROP TABLE "account"`)
await db.query(`ALTER TABLE "historical_balance" DROP CONSTRAINT "FK_383ff006e4b59db91d32cb891e9"`)
}
}
version: "3"
services:
db:
image: postgres:12
environment:
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
ports:
- "${DB_PORT}:5432"
# command: ["postgres", "-c", "log_statement=all"]
Source diff could not be displayed: it is too large. Options to address this: view the blob.
{
"name": "kusama-balances",
"private": true,
"scripts": {
"build": "rm -rf lib && tsc"
},
"dependencies": {
"@subsquid/graphql-server": "^0.0.2",
"@subsquid/substrate-metadata": "^0.0.1",
"@subsquid/substrate-processor": "^0.0.1",
"dotenv": "^10.0.0",
"pg": "^8.7.1",
"typeorm": "^0.2.41"
},
"devDependencies": {
"@subsquid/cli": "^0.0.2",
"@subsquid/substrate-metadata-explorer": "^0.0.1",
"@subsquid/substrate-typegen": "^0.0.1",
"@types/node": "^16.11.17",
"typescript": "~4.5.4"
}
}
type Account @entity {
"Account address"
id: ID!
balance: BigInt!
historicalBalances: [HistoricalBalance!] @derivedFrom(field: "account")
}
type HistoricalBalance @entity {
id: ID!
account: Account!
balance: BigInt!
date: DateTime!
}
import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, OneToMany as OneToMany_} from "typeorm"
import * as marshal from "./marshal"
import {HistoricalBalance} from "./historicalBalance.model"
@Entity_()
export class Account {
constructor(props?: Partial<Account>) {
Object.assign(this, props)
}
/**
* Account address
*/
@PrimaryColumn_()
id!: string
@Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false})
balance!: bigint
@OneToMany_(() => HistoricalBalance, e => e.account)
historicalBalances!: HistoricalBalance[]
}
import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_} from "typeorm"
import * as marshal from "./marshal"
import {Account} from "./account.model"
@Entity_()
export class HistoricalBalance {
constructor(props?: Partial<HistoricalBalance>) {
Object.assign(this, props)
}
@PrimaryColumn_()
id!: string
@Index_()
@ManyToOne_(() => Account, {nullable: false})
account!: Account
@Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false})
balance!: bigint
@Column_("timestamp with time zone", {nullable: false})
date!: Date
}
export * from "./account.model"
export * from "./historicalBalance.model"
import assert from 'assert'
export interface Marshal<T, S> {
fromJSON(value: unknown): T
toJSON(value: T): S
}
export const string: Marshal<string, string> = {
fromJSON(value: unknown): string {
assert(typeof value === 'string', 'invalid String')
return value
},
toJSON(value) {
return value
},
}
export const id = string
export const int: Marshal<number, number> = {
fromJSON(value: unknown): number {
assert(Number.isInteger(value), 'invalid Int')
return value as number
},
toJSON(value) {
return value
},
}
export const float: Marshal<number, number> = {
fromJSON(value: unknown): number {
assert(typeof value === 'number', 'invalid Float')
return value as number
},
toJSON(value) {
return value
},
}
export const boolean: Marshal<boolean, boolean> = {
fromJSON(value: unknown): boolean {
assert(typeof value === 'boolean', 'invalid Boolean')
return value
},
toJSON(value: boolean): boolean {
return value
},
}
export const bigint: Marshal<bigint, string> = {
fromJSON(value: unknown): bigint {
assert(typeof value === 'string', 'invalid BigInt')
return BigInt(value)
},
toJSON(value: bigint): string {
return value.toString()
},
}
// credit - https://github.com/Urigo/graphql-scalars/blob/91b4ea8df891be8af7904cf84751930cc0c6613d/src/scalars/iso-date/validator.ts#L122
const RFC_3339_REGEX =
/^(\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60))(\.\d{1,})?([Z])$/
function isIsoDateTimeString(s: string): boolean {
return RFC_3339_REGEX.test(s)
}
export const datetime: Marshal<Date, string> = {
fromJSON(value: unknown): Date {
assert(typeof value === 'string', 'invalid DateTime')
assert(isIsoDateTimeString(value), 'invalid DateTime')
return new Date(value)
},
toJSON(value: Date): string {
return value.toISOString()
},
}
export const bytes: Marshal<Uint8Array, string> = {
fromJSON(value: unknown): Buffer {
assert(typeof value === 'string', 'invalid Bytes')
assert(value.length % 2 === 0, 'invalid Bytes')
assert(/^0x[0-9a-f]+$/i.test(value), 'invalid Bytes')
return Buffer.from(value.slice(2), 'hex')
},
toJSON(value: Uint8Array): string {
if (Buffer.isBuffer(value)) {
return '0x' + value.toString('hex')
} else {
return '0x' + Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString('hex')
}
},
}
export function fromList<T>(list: unknown, f: (val: unknown) => T): T[] {
assert(Array.isArray(list))
return list.map((val) => f(val))
}
export function nonNull<T>(val: T | undefined | null): T {
assert(val != null, 'non-nullable value is null')
return val
}
export const bigintTransformer = {
to(x?: bigint) {
return x?.toString()
},
from(s?: string): bigint | undefined {
return s == null ? undefined : BigInt(s)
}
}
export * from "./generated"
import {EventHandlerContext, Store, SubstrateProcessor} from "@subsquid/substrate-processor"
import {Account, HistoricalBalance} from "./model"
import {BalancesTransferEvent} from "./types/events"
const processor = new SubstrateProcessor('kusama_balances')
processor.setTypesBundle('kusama')
processor.setBatchSize(500)
processor.setDataSource({
archive: 'https://kusama.indexer.gc.subsquid.io/v4/graphql',
chain: 'wss://kusama-rpc.polkadot.io'
})
processor.addEventHandler('balances.Transfer', async ctx => {
let transfer = getTransferEvent(ctx)
let tip = ctx.extrinsic?.tip || 0n
let fromAcc = await getOrCreate(ctx.store, Account, toHex(transfer.from))
fromAcc.balance = fromAcc.balance || 0n
fromAcc.balance -= transfer.amount
fromAcc.balance -= tip
await ctx.store.save(fromAcc)
const toAcc = await getOrCreate(ctx.store, Account, toHex(transfer.to))
toAcc.balance = toAcc.balance || 0n
toAcc.balance += transfer.amount
await ctx.store.save(toAcc)
await ctx.store.save(new HistoricalBalance({
id: ctx.event.id + '-to',
account: fromAcc,
balance: fromAcc.balance,
date: new Date(ctx.block.timestamp)
}))
await ctx.store.save(new HistoricalBalance({
id: ctx.event.id + '-from',
account: toAcc,
balance: toAcc.balance,
date: new Date(ctx.block.timestamp)
}))
})
processor.run()
interface TransferEvent {
from: Uint8Array
to: Uint8Array
amount: bigint
}
function getTransferEvent(ctx: EventHandlerContext): TransferEvent {
let event = new BalancesTransferEvent(ctx)
if (event.isV1020) {
let [from, to, amount] = event.asV1020
return {from, to, amount}
} else if (event.isV1050) {
let [from, to, amount] = event.asV1050
return {from, to, amount}
} else {
return event.asLatest
}
}
function toHex(data: Uint8Array): string {
return '0x' + Buffer.from(data).toString('hex')
}
async function getOrCreate<T extends {id: string}>(
store: Store,
entityConstructor: EntityConstructor<T>,
id: string
): Promise<T> {
let e = await store.get<T>(entityConstructor, {
where: { id },
})
if (e == null) {
e = new entityConstructor()
e.id = id
}
return e
}
type EntityConstructor<T> = {
new (...args: any[]): T
}
import assert from 'assert'
import {EventContext, Result} from './support'
import * as v9130 from './v9130'
export class BalancesTransferEvent {
constructor(private ctx: EventContext) {
assert(this.ctx.event.name === 'balances.Transfer')
}
/**
* Transfer succeeded (from, to, value, fees).
*/
get isV1020(): boolean {
let h = this.ctx.block.height
return h <= 1375086
}
/**
* Transfer succeeded (from, to, value, fees).
*/
get asV1020(): [Uint8Array, Uint8Array, bigint, bigint] {
assert(this.isV1020)
return this.ctx._chain.decodeEvent(this.ctx.event)
}
/**
* Transfer succeeded (from, to, value).
*/
get isV1050(): boolean {
let h = this.ctx.block.height
return 1375086 < h && h <= 10403784
}
/**
* Transfer succeeded (from, to, value).
*/
get asV1050(): [Uint8Array, Uint8Array, bigint] {
assert(this.isV1050)
return this.ctx._chain.decodeEvent(this.ctx.event)
}
/**
* Transfer succeeded.
*/
get isLatest(): boolean {
return this.ctx.block.height > 10403784 && this.ctx._chain.getEventHash('balances.Transfer') === '68dcb27fbf3d9279c1115ef6dd9d30a3852b23d8e91c1881acd12563a212512d'
}
/**
* Transfer succeeded.
*/
get asLatest(): {from: v9130.AccountId32, to: v9130.AccountId32, amount: bigint} {
assert(this.isLatest)
return this.ctx._chain.decodeEvent(this.ctx.event)
}
}
export type Result<T, E> = {
__kind: 'Ok'
value: T
} | {
__kind: 'Err'
value: E
}
interface Event {
name: string
params: {value: unknown}[]
}
export interface EventContext {
_chain: {
getEventHash(eventName: string): string
decodeEvent(event: Event): any
}
block: {
height: number
}
event: Event
}
interface Call {
name: string
args: {value: unknown}[]
}
export interface CallContext {
_chain: {
getCallHash(name: string): string
decodeCall(call: Call): any
}
block: {
height: number
}
extrinsic: Call
}
export type AccountId32 = Uint8Array
{
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"outDir": "lib",
"rootDir": "src",
"strict": true,
"declaration": false,
"sourceMap": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true
},
"include": ["src"],
"exclude": [
"node_modules"
]
}
{
"outDir": "src/types",
"chainVersions": "kusamaVersions.json",
"typesBundle": "kusama",
"events": [
"balances.Transfer"
],
"calls": []
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment