Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:05 +08:00
commit 8bcde7080b
26 changed files with 5957 additions and 0 deletions

View File

@@ -0,0 +1,301 @@
# Entity Patterns in HyperIndex
Patterns for defining and working with entities in HyperIndex.
## Schema Definition
### Basic Entity
```graphql
type Token {
id: ID!
name: String!
symbol: String!
decimals: BigInt!
totalSupply: BigInt!
}
```
**Key differences from TheGraph:**
- No `@entity` decorator needed
- Use `String!` instead of `Bytes!`
- Use `BigInt!` for numbers (not `BigDecimal!` in schema)
### Entity Relationships
Use `_id` suffix for relationships:
```graphql
type Transfer {
id: ID!
from: String!
to: String!
amount: BigInt!
token_id: String! # References Token.id
blockNumber: BigInt!
}
type Token {
id: ID!
name: String!
symbol: String!
transfers: [Transfer!]! @derivedFrom(field: "token")
}
```
**Critical:** Entity arrays MUST have `@derivedFrom`:
```graphql
# WRONG - Will fail codegen
type Transaction {
mints: [Mint!]! # Missing @derivedFrom
}
# CORRECT
type Transaction {
id: ID!
mints: [Mint!]! @derivedFrom(field: "transaction")
}
type Mint {
id: ID!
transaction_id: String! # The relationship field
}
```
### Optional Fields
Use nullable types for optional fields:
```graphql
type Token {
id: ID!
name: String!
symbol: String!
logoUrl: String # Optional - no !
}
```
**Important:** Use `undefined` not `null` in TypeScript:
```typescript
const token = {
id: "0x...",
name: "Token",
symbol: "TKN",
logoUrl: undefined, // Not null
};
```
## Creating Entities
### Basic Creation
```typescript
MyContract.Transfer.handler(async ({ event, context }) => {
const transfer = {
id: `${event.chainId}-${event.transaction.hash}-${event.logIndex}`,
from: event.params.from,
to: event.params.to,
amount: event.params.amount,
token_id: event.srcAddress, // Relationship
blockNumber: BigInt(event.block.number),
};
context.Transfer.set(transfer);
});
```
### With Multichain ID
Always prefix IDs with chainId for multichain:
```typescript
const id = `${event.chainId}-${event.params.tokenId}`;
```
### Entity ID Patterns
```typescript
// Event-based (unique per event)
`${event.chainId}-${event.transaction.hash}-${event.logIndex}`
// Address-based (singleton per address per chain)
`${event.chainId}-${event.srcAddress}`
// Composite (multiple keys)
`${event.chainId}-${event.params.user}-${event.params.tokenId}`
// Time-based (daily aggregates)
`${event.chainId}-${dayTimestamp}-${event.srcAddress}`
```
## Updating Entities
**Entities are immutable.** Use spread operator for updates:
```typescript
MyContract.Transfer.handler(async ({ event, context }) => {
const token = await context.Token.get(event.srcAddress);
if (token) {
// CORRECT - Use spread operator
const updatedToken = {
...token,
totalSupply: token.totalSupply + event.params.amount,
lastUpdated: BigInt(event.block.timestamp),
};
context.Token.set(updatedToken);
}
});
```
**Never mutate directly:**
```typescript
// WRONG - Entities are read-only
token.totalSupply = newSupply;
context.Token.set(token); // Won't work
```
## Loading Entities
### Get by ID
```typescript
const token = await context.Token.get(tokenId);
if (token) {
// Token exists
}
```
### Get or Create Pattern
```typescript
MyContract.Transfer.handler(async ({ event, context }) => {
let token = await context.Token.get(event.srcAddress);
if (!token) {
token = {
id: event.srcAddress,
name: "Unknown",
symbol: "???",
decimals: BigInt(18),
totalSupply: BigInt(0),
};
}
const updatedToken = {
...token,
totalSupply: token.totalSupply + event.params.amount,
};
context.Token.set(updatedToken);
});
```
## Querying Related Entities
`@derivedFrom` arrays are virtual - cannot access in handlers:
```typescript
// WRONG - derivedFrom arrays don't exist in handlers
const transfers = token.transfers;
// CORRECT - Query using indexed field
// (If using indexed field operations)
const transfers = await context.Transfer.getWhere.token_id.eq(tokenId);
```
## BigDecimal Handling
For precision in calculations, use BigDecimal:
```typescript
import { BigDecimal } from "generated";
const ZERO_BD = new BigDecimal(0);
const ONE_BD = new BigDecimal(1);
// Convert token amount to decimal
function convertToDecimal(amount: bigint, decimals: bigint): BigDecimal {
const divisor = new BigDecimal(10n ** decimals);
return new BigDecimal(amount.toString()).div(divisor);
}
```
**Schema types vs code types:**
- Schema `BigInt!` → TypeScript `bigint`
- Schema `BigDecimal!` → TypeScript `BigDecimal`
- Schema `Int!` → TypeScript `number`
## Timestamp Handling
Always cast timestamps:
```typescript
// CORRECT
timestamp: BigInt(event.block.timestamp)
// For day calculations
const dayTimestamp = Math.floor(event.block.timestamp / 86400) * 86400;
const dayId = `${event.chainId}-${dayTimestamp}`;
```
## Common Entity Types
### Token Entity
```graphql
type Token {
id: ID!
address: String!
name: String!
symbol: String!
decimals: BigInt!
totalSupply: BigInt!
transfers: [Transfer!]! @derivedFrom(field: "token")
}
```
### Transfer Entity
```graphql
type Transfer {
id: ID!
token_id: String!
from: String!
to: String!
amount: BigInt!
blockNumber: BigInt!
blockTimestamp: BigInt!
transactionHash: String!
}
```
### Daily Aggregate Entity
```graphql
type TokenDayData {
id: ID! # chainId-dayTimestamp-tokenAddress
token_id: String!
date: Int! # Unix timestamp of day start
volume: BigDecimal!
txCount: BigInt!
open: BigDecimal!
high: BigDecimal!
low: BigDecimal!
close: BigDecimal!
}
```
## Entity Checklist
When defining entities:
- [ ] Use `ID!` for id field
- [ ] Use `String!` for addresses (not `Bytes!`)
- [ ] Use `_id` suffix for relationships
- [ ] Add `@derivedFrom` to all entity arrays
- [ ] No `@entity` decorator
- [ ] Consider multichain ID prefixes
- [ ] Match field types exactly (BigInt vs BigDecimal vs Int)