<style>
.left {
text-align: left;
}
.smol {
font-size: 24px;
}
.reveal pre {
height: 100%;
width: 100%;
}
</style>
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/cover_bg.png" -->
<!--  -->
# Indexing EVM logs on Astar
## To power Orcus Decentralized Finance DApp
Slides available here
https://hackmd.io/@RaekwonIII/Orcus-workshop
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## Blockchain data problems
<!--  -->
<!--  -->

Projects need custom data analysis for informed decision making
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Monolith vs modular (1)

----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Monolith vs modular (2)

----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Monolith vs modular (3)
## Offload heavy lifting to Archives
Note:
* Lower storage requirements, less computing power
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Monolith vs modular (4)
## Pre-indexed "queryable" historical data
Note:
* API updates => wait for re-sync
* Acts like a "blockchain cache"
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Monolith vs modular (5)
## Faster than RPC calls
Note:
* Can be scaled, does not depend on the DApp
* Bonus: can use Archives and Processors like LEGOs
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Permissive SDK
## Unified Substrate and EVM indexing
Note:
Can track both Substrate Events/Extrinsics AND Evm data in the same API
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Permissive SDK
## Full control of database schema
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Permissive SDK
## External libs/APIs in your project
E.g. fetch prices from Coikgecko? ✅
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Permissive SDK
## Extensions via custom resolvers
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Built for devs
## Easy setup, local development
Note:
It's much easier to test, break things, start, stop the server
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Built for devs
## Fast sync times
Note:
Fast sync times, because the raw indexing is done by Archives
Build and deploy production-ready APIs within *hours*
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Built for devs
## Serverless deployment
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Built for devs

Note:
* Generate as much boilerplate possible
* Type-safety of TypeScript
* Focus on business logic
* No manual JSON parsing
* No manual chasing of Runtime upgrades
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Codegen
## From schema
```graphql=
type Owner @entity {
id: ID!
ownedTokens: [Token!]! @derivedFrom(field: "owner")
balance: BigInt
}
```
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Codegen
## To Models
```typescript=
@Entity_()
export class Owner {
constructor(props?: Partial<Owner>) {
Object.assign(this, props)
}
@PrimaryColumn_()
id!: string
@OneToMany_(() => Token, e => e.owner)
ownedTokens!: Token[]
@Column_("numeric", {transformer: marshal.bigintTransformer, nullable: true})
balance!: bigint | undefined | null
}
```
Note:
Easier to define entities as a schema, code is built automatically
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Typegen
## Type-safe wrappers for Substrate
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## Runtime upgrades



Note:
Runtime upgrades break things...
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## 👋Runtime upgrade bugs
```typescript=
export class BalancesTransferEvent {
constructor(private ctx: EventContext) {
assert(this.ctx.event.name === 'balances.Transfer')
}
get isV49(): boolean {
return this.ctx._chain.getEventHash('balances.Transfer') === 'dfcae516f053c47e7cb49e0718f01587efcb64cea4e3baf4c6973a29891f7841'
}
get asV49(): [Uint8Array, Uint8Array, bigint] {
assert(this.isV49)
return this.ctx._chain.decodeEvent(this.ctx.event)
}
get isV1201(): boolean {
return this.ctx._chain.getEventHash('balances.Transfer') === '23222c59f2992c12387568241620899d2d399ab9027595daca8255637f62ece3'
}
get asV1201(): {from: v1201.AccountId20, to: v1201.AccountId20, amount: bigint} {
assert(this.isV1201)
return this.ctx._chain.decodeEvent(this.ctx.event)
}
}
```
Note:
* Keep track of Runtime changes
* No manual procedure
* Reduce bugs
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# EVM Typegen
## Type-safe interfaces from ABI deserialization
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## Type-safe interfaces from ABI deserialization
### From JSON...
```json=
[
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "symbol",
"type": "string"
},
{
"internalType": "string",
"name": "baseURI",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "baseURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "tokenByIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "tokenOfOwnerByIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
```
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## ABI automated parsing
### ...to TypeScript
```typescript=
export interface TransferAddressAddressUint256Event {
from: string;
to: string;
tokenId: ethers.BigNumber;
}
export const events = {
// ...
"Transfer(address,address,uint256)": {
topic: abi.getEventTopic("Transfer(address,address,uint256)"),
decode(data: EvmEvent): TransferAddressAddressUint256Event {
const result = abi.decodeEventLog(
abi.getEvent("Transfer(address,address,uint256)"),
data.data || "",
data.topics
);
return {
from: result[0],
to: result[1],
tokenId: result[2],
}
}
},
}
```
Note:
* No manual JSON parsing
* Pass data through interfaces
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## Resources
* [Subsquid Docs](https://docs.subsquid.io)
* [Template project](https://github.com/subsquid/squid-template)
* [Aquarium](https://app.subsquid.io/aquarium)
* [Stackexchange](https://substrate.stackexchange.com/) (subsquid tag):
* Community: [Telegram](https://t.me/HydraDevs) group, [Discord](https://discord.gg/dxR4wNgdjV) server
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Let’s get coding…🦑
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## Goal: Track Investment Deposits
1. Setup<!-- .element: class="fragment" -->
2. Review the schema<!-- .element: class="fragment" -->
3. Generate models<!-- .element: class="fragment" -->
4. Generate Interfaces<!-- .element: class="fragment" -->
5. Implement logic in Processor<!-- .element: class="fragment" -->
6. Launch database container<!-- .element: class="fragment" -->
7. Create and apply database migration<!-- .element: class="fragment" -->
8. Launch processor and GraphQL server<!-- .element: class="fragment" -->
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# Setup
* Requisites: [Node.js](https://nodejs.org/en/download/) (16 or later), [Docker](https://docs.docker.com/get-docker/)
* GitHub [template](https://github.com/subsquid/squid-evm-template), *Use this template*, then
```bash
git clone git@github.com:<account>/squid-evm-template.git
```
* Install dependencies from project's root folder
```bash
cd squid-template && npm i
```
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# Codegen (schema)
Define entities we want to track in `schema.graphql` file in the project root folder
* Investment
```graphql=
type Investements @entity {
id: ID!
timestamp: BigInt!
value: Float!
}
```
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# Codegen (proj structure)
* From project's root folder, launch
```bash
npx sqd codegen # or `make codegen`
```
* New files will be created 
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# Codegen (classes)
### Let's take the `Owner` entity as an example
```typescript=
import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_} from "typeorm"
import * as marshal from "./marshal"
@Entity_()
export class Investement {
constructor(props?: Partial<Investement>) {
Object.assign(this, props)
}
@PrimaryColumn_()
id!: string
@Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false})
timestamp!: bigint
@Column_("numeric", {nullable: false})
value!: number
}
```
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# EVM Typegen
* We need the "bank safe" ABI to be able to process its logs
* Copy it from [here](https://blockscout.com/astar/address/0xd89dEa2daC8Fb73F4107C2cbeA5Eb36dab511F64/contracts)
* Create a `src/abi` folder to contain all ABIs
* Paste it into a file named `bank-safe.json` in `src/abi`
* From project root folder, launch
```bash
npx squid-evm-typegen --abi src/abi/bank-safe.json --output src/abi/bank-safe.ts
```
Note:
https://github.com/R312r0/orcus-squid-evm/blob/main/src/abis/bank-safe.json
----
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## Linting config
We may need to add these configurations to the `.eslint` file:
```typescript=
{
"prettier/prettier": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"import/extensions": "off"
}
```
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# Processor logic
Processor logic needs to be updated, we maintain the handling of Substrate Events, but add EVM logs handling. Let's change the file like so:
```typescript=
import { EvmLogHandlerContext, SubstrateEvmProcessor } from "@subsquid/substrate-evm-processor";
import { lookupArchive } from "@subsquid/archive-registry";
import { events } from "./abi/bank-safe";
import { Investement } from "./model";
const processor = new SubstrateEvmProcessor("astar-substrate");
processor.setBatchSize(500);
processor.setDataSource({
chain: "wss://astar.api.onfinality.io/public-ws",
archive: lookupArchive("astar")[0].url,
});
processor.setTypesBundle("astar");
processor.addEvmLogHandler(
"0xd89dEa2daC8Fb73F4107C2cbeA5Eb36dab511F64".toLowerCase(),
{
filter: [events["InvestDeposited(uint256)"].topic],
range: {from: 864889}
},
bankSafeHandler
);
processor.run();
export async function bankSafeHandler(
ctx: EvmLogHandlerContext
): Promise<void> {
const investDeposit = events["InvestDeposited(uint256)"].decode(ctx);
await ctx.store.save(
new Investement({
id: ctx.txHash,
timestamp: BigInt(ctx.substrate.block.timestamp),
value: (Number(investDeposit.amount) / 1e6)
})
)
}
```
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# Database
* Squid APIs need a database to store processed data
* Templates have `docker-compose.yml` file to launch a container
* From project's root folder, launch
```bash
docker-compose up
```
* or, alternatively
```bash
make up
```
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# Create and apply database migration
* Build code
```bash=
npm run build
```
* Clean up existing migrations
```bash=
rm db/migrations/*.js
```
* Drop database (and create new one)
```bash=
sqd db drop
sqd db create
```
* Create and apply new migration
```bash=
sqd db create-migration Data # make migration
sqd db migrate # make migrate
```
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
<!-- .slide: class="smol" -->
<!-- .slide: class="left" -->
# Launch the project
The project consists of a Processor (which we just implemented) and a GraphQL server.
* Launch the Processor (this will lock the console window)
```bash=
node -r dotenv/config lib/processor.js # make process
```
* Launch the GraphQL server (in another console window)
```bash=
npx squid-graphql-server # make serve
```
* Open the browser at http://localhost:4350/graphql
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## We got you, bruh!

This project is available in our [Aquarium](https://app.subsquid.io/aquarium/Orcus-EVM-example)
The complete Orcus Finanance Squid is available in their [repo](https://github.com/R312r0/orcus-squid-evm)
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
## What's next?
* Get contract: https://blockscout.com/astar/
* Change `CHAIN_NODE` in `contract.ts` (`"wss://shiden.api.onfinality.io/public-ws"`)
* Change `archive` arg of `processor.setDataSource` in `processor.ts`
* Optional
* change ABI
* change EVM call signature
💣💣💣
---
<!-- .slide: data-background="https://github.com/RaekwonIII/orcus-workshop/raw/master/base_bg.png" -->
# Thank you 🦑
Follow the project on GitHub
https://github.com/subsquid/squid

Give us a ⭐, would you?
{"metaMigratedAt":"2023-06-17T00:22:07.271Z","metaMigratedFrom":"YAML","title":"Orcus EVM workshop","breaks":false,"description":"View the slide with \"Slide Mode\".","slideOptions":"{\"center\":false,\"transition\":\"fade\",\"spotlight\":{\"enabled\":false}}","contributors":"[{\"id\":\"66313dde-8798-4860-b88b-c44c1fc3df78\",\"add\":30524,\"del\":6071}]"}