Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing fields #110

Merged
merged 32 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0defebc
proper count on token entity
daiagi Oct 4, 2023
2456fc3
remove exapnsive debug logs
daiagi Oct 4, 2023
3ec22b8
slim down - pass only collection id
daiagi Oct 4, 2023
0bcaafc
better debug logging
daiagi Oct 4, 2023
e470451
cheapest nft
daiagi Oct 4, 2023
3ae7031
improve performance
daiagi Oct 6, 2023
88a43f1
Merge branch 'tokenEnityt/fix-count-calc' of https://github.com/daiag…
daiagi Oct 6, 2023
0a93e90
resolver for tokenEntities of specific owner
daiagi Oct 6, 2023
0db457c
add metadata and meta to TokenEntity
daiagi Oct 6, 2023
2917612
add totalCount field to avoid bug when delting token
daiagi Oct 6, 2023
ef65343
Merge branch 'tokenEnityt/fix-count-calc' of https://github.com/daiag…
daiagi Oct 6, 2023
7e6e5dc
merge migration files
daiagi Oct 6, 2023
cd0de70
add meta and metadata to resolver
daiagi Oct 6, 2023
810718e
schema and migration
daiagi Oct 8, 2023
6559ba3
re orgenize
daiagi Oct 8, 2023
9a4aa70
schema name changes
daiagi Oct 8, 2023
4578868
name changes- resolver
daiagi Oct 8, 2023
96036d3
oops - undo comment out of unique handlers
daiagi Oct 8, 2023
3a97b0d
undo migration change
daiagi Oct 8, 2023
cbeda90
supply on resolver
daiagi Oct 8, 2023
8402c5e
use more utils
daiagi Oct 9, 2023
9eae7d8
fix orderBy, add denyList, owner can be null,
daiagi Oct 10, 2023
eb9a4dd
add collection floor price, fix typings
daiagi Oct 10, 2023
b5963dc
cheapest is already on tokenEntity
daiagi Oct 10, 2023
c684a66
total count resolver
daiagi Oct 10, 2023
7060884
drop collection floor price
daiagi Oct 10, 2023
789ff7b
syntax fix
daiagi Oct 10, 2023
6671e29
get 'cheapest' in resolver, drop it from schema
daiagi Oct 11, 2023
d858519
add current owner
daiagi Oct 11, 2023
40a43d4
`SELECT DISTINCT` to improve query performance
daiagi Oct 11, 2023
23ac70a
cheapest can have price:0
daiagi Oct 12, 2023
76692c8
😱 uncomment unique tranfer
daiagi Oct 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 54 additions & 21 deletions db/migrations/1689623692588-Data.js
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never alter migration pls

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Original file line number Diff line number Diff line change
@@ -1,25 +1,58 @@
module.exports = class Data1689623692588 {
name = 'Data1689623692588'
name = 'Data1689623692588'

async up(db) {
await db.query(`CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))`)
await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `)
await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `)
await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `)
await db.query(`ALTER TABLE "nft_entity" ADD "token_id" character varying`)
await db.query(`CREATE INDEX "IDX_060d0f515d293fac1d81ee61a7" ON "nft_entity" ("token_id") `)
await db.query(`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35" FOREIGN KEY ("collection_id") REFERENCES "collection_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
await db.query(`ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_060d0f515d293fac1d81ee61a79" FOREIGN KEY ("token_id") REFERENCES "token_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`)
}
async up(db) {
await db.query(
`CREATE TABLE "token_entity" ("id" character varying NOT NULL, "block_number" numeric, "hash" text NOT NULL, "image" text, "media" text, "name" text, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "count" integer NOT NULL, "collection_id" character varying, CONSTRAINT "PK_687443f2a51af49b5472e2c5ddc" PRIMARY KEY ("id"))`
)
await db.query(`ALTER TABLE "token_entity" ADD "total_count" integer NOT NULL`)
await db.query(`CREATE INDEX "IDX_0eb2ed7929c3e81941fa1b51b3" ON "token_entity" ("collection_id") `)
await db.query(`CREATE INDEX "IDX_40d6049fd30532dada71922792" ON "token_entity" ("hash") `)
await db.query(`CREATE INDEX "IDX_47b385945a425667b9e690bc02" ON "token_entity" ("name") `)
await db.query(`ALTER TABLE "nft_entity" ADD "token_id" character varying`)
await db.query(`CREATE INDEX "IDX_060d0f515d293fac1d81ee61a7" ON "nft_entity" ("token_id") `)
await db.query(
`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35" FOREIGN KEY ("collection_id") REFERENCES "collection_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
)
await db.query(
`ALTER TABLE "nft_entity" ADD CONSTRAINT "FK_060d0f515d293fac1d81ee61a79" FOREIGN KEY ("token_id") REFERENCES "token_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
)

async down(db) {
await db.query(`DROP TABLE "token_entity"`)
await db.query(`DROP INDEX "public"."IDX_0eb2ed7929c3e81941fa1b51b3"`)
await db.query(`DROP INDEX "public"."IDX_40d6049fd30532dada71922792"`)
await db.query(`DROP INDEX "public"."IDX_47b385945a425667b9e690bc02"`)
await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "token_id"`)
await db.query(`DROP INDEX "public"."IDX_060d0f515d293fac1d81ee61a7"`)
await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35"`)
await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_060d0f515d293fac1d81ee61a79"`)
}
// Commands from Data1696428349370
await db.query(`ALTER TABLE "token_entity" ADD "cheapest_nft_id" character varying`)
await db.query(`CREATE INDEX "IDX_6d56119ac99ab53644b1b21a6e" ON "token_entity" ("cheapest_nft_id") `)
await db.query(
`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef" FOREIGN KEY ("cheapest_nft_id") REFERENCES "nft_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
)

// Commands from Data1696576165016
await db.query(`ALTER TABLE "token_entity" ADD "metadata" text`)
await db.query(`ALTER TABLE "token_entity" ADD "meta_id" character varying`)
await db.query(`CREATE INDEX "IDX_ae4ff3b28e3fec72aa14124d1e" ON "token_entity" ("meta_id") `)
await db.query(
`ALTER TABLE "token_entity" ADD CONSTRAINT "FK_ae4ff3b28e3fec72aa14124d1e1" FOREIGN KEY ("meta_id") REFERENCES "metadata_entity"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
)
}

async down(db) {
// Rollback commands from Data1696576165016
await db.query(`ALTER TABLE "token_entity" DROP COLUMN "metadata"`)
await db.query(`ALTER TABLE "token_entity" DROP COLUMN "meta_id"`)
await db.query(`DROP INDEX "public"."IDX_ae4ff3b28e3fec72aa14124d1e"`)
await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_ae4ff3b28e3fec72aa14124d1e1"`)

// Rollback commands from Data1696428349370
await db.query(`ALTER TABLE "token_entity" DROP COLUMN "cheapest_nft_id"`)
await db.query(`DROP INDEX "public"."IDX_6d56119ac99ab53644b1b21a6e"`)
await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_6d56119ac99ab53644b1b21a6ef"`)

await db.query(`DROP TABLE "token_entity"`)
await db.query(`DROP INDEX "public"."IDX_0eb2ed7929c3e81941fa1b51b3"`)
await db.query(`DROP INDEX "public"."IDX_40d6049fd30532dada71922792"`)
await db.query(`DROP INDEX "public"."IDX_47b385945a425667b9e690bc02"`)
await db.query(`ALTER TABLE "nft_entity" DROP COLUMN "token_id"`)
await db.query(`DROP INDEX "public"."IDX_060d0f515d293fac1d81ee61a7"`)
await db.query(`ALTER TABLE "token_entity" DROP CONSTRAINT "FK_0eb2ed7929c3e81941fa1b51b35"`)
await db.query(`ALTER TABLE "nft_entity" DROP CONSTRAINT "FK_060d0f515d293fac1d81ee61a79"`)
}
}
4 changes: 4 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ type TokenEntity @entity {
id: ID!
blockNumber: BigInt
collection: CollectionEntity
cheapestNFT: NFTEntity
daiagi marked this conversation as resolved.
Show resolved Hide resolved
nfts: [NFTEntity!] @derivedFrom(field: "token")
hash: String! @index
image: String
media: String
meta: MetadataEntity
metadata: String
name: String @index
updatedAt: DateTime!
createdAt: DateTime!
count: Int!
totalCount: Int!
}

type NFTEntity @entity {
Expand Down
5 changes: 4 additions & 1 deletion src/mappings/nfts/burn.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { getWith } from '@kodadot1/metasquid/entity'
import { getWith } from '@kodadot1/metasquid/entity'
import { NFTEntity as NE } from '../../model'
import { unwrap } from '../utils/extract'
import { debug, pending, success } from '../utils/logger'
import { Action, Context, createTokenId } from '../utils/types'
import { createEvent } from '../shared/event'
import { calculateCollectionOwnerCountAndDistribution } from '../utils/helper'
import { eventHandlers } from '../shared/handleTokenEntity'
import { getBurnTokenEvent } from './getters'

const OPERATION = Action.BURN
Expand All @@ -31,6 +32,8 @@ export async function handleTokenBurn(context: Context): Promise<void> {
entity.collection.ownerCount = ownerCount
entity.collection.distribution = distribution

await eventHandlers.burnHandler(context, entity)
daiagi marked this conversation as resolved.
Show resolved Hide resolved

success(OPERATION, `${id} by ${event.caller}}`)
await context.store.save(entity)
const meta = entity.metadata ?? ''
Expand Down
2 changes: 2 additions & 0 deletions src/mappings/nfts/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { unwrap } from '../utils/extract'
import { debug, pending, success } from '../utils/logger'
import { Action, Context, createTokenId } from '../utils/types'
import { createEvent } from '../shared/event'
import { eventHandlers } from '../shared/handleTokenEntity'
import { getPriceTokenEvent } from './getters'

const OPERATION = Action.LIST
Expand All @@ -22,6 +23,7 @@ export async function handleTokenList(context: Context): Promise<void> {
if (entity.price && (entity.collection.floor === 0n || entity.price < entity.collection.floor)) {
entity.collection.floor = entity.price
}
eventHandlers.listHandler(context, entity)
daiagi marked this conversation as resolved.
Show resolved Hide resolved

success(OPERATION, `${id} by ${event.caller}} for ${String(event.price)}`)
await context.store.save(entity)
Expand Down
3 changes: 3 additions & 0 deletions src/mappings/nfts/mint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { unwrap } from '../utils/extract'
import { debug, pending, success } from '../utils/logger'
import { Action, Context, createTokenId } from '../utils/types'
import { calculateCollectionOwnerCountAndDistribution, versionOf } from '../utils/helper'
import { eventHandlers } from '../shared/handleTokenEntity'
import { getCreateTokenEvent } from './getters'

const OPERATION = Action.MINT
Expand Down Expand Up @@ -60,6 +61,8 @@ export async function handleTokenCreate(context: Context): Promise<void> {
final.name = metadata?.name
final.image = metadata?.image
final.media = metadata?.animationUrl

await eventHandlers.mintHandler(context, collection, final)
}

success(OPERATION, `${final.id}`)
Expand Down
18 changes: 8 additions & 10 deletions src/mappings/nfts/setMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { get, getOptional } from '@kodadot1/metasquid/entity'
import { get, getOptional, getWith } from '@kodadot1/metasquid/entity'
import { isFetchable } from '@kodadot1/minipfs'
import { unwrap } from '../utils/extract'
import { Context, isNFT } from '../utils/types'
import { CollectionEntity, NFTEntity } from '../../model'
import { handleMetadata } from '../shared/metadata'
import { debug, warn } from '../utils/logger'
import { updateItemMetadataByCollection } from '../utils/cache'
import { handleTokenEntity } from '../shared/handleTokenEntity'
import { eventHandlers } from '../shared/handleTokenEntity'
import { tokenIdOf } from './types'
import { getMetadataEvent } from './getters'

Expand Down Expand Up @@ -51,17 +51,15 @@ export async function handleMetadataSet(context: Context): Promise<void> {
warn(OPERATION, `collection ${event.collectionId} not found`)
return
}
const nft = final as NFTEntity
const token = await handleTokenEntity(context, collection, nft)
if (token) {
nft.token = token
if (final instanceof NFTEntity) {
await eventHandlers.setMetadataHandler(context, collection, final)
}
}
}

await context.store.save(final)
await context.store.save(final)

if (!event.sn && final.metadata) {
await updateItemMetadataByCollection(context.store, event.collectionId)
if (!event.sn && final.metadata) {
await updateItemMetadataByCollection(context.store, event.collectionId)
}
}
}
174 changes: 148 additions & 26 deletions src/mappings/shared/handleTokenEntity.ts
daiagi marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,44 +1,166 @@
import { create, getOptional } from '@kodadot1/metasquid/entity'
import { create, getOptional, getWith } from '@kodadot1/metasquid/entity'
import md5 from 'md5'
import { CollectionEntity as CE, NFTEntity as NE, TokenEntity as TE } from '../../model'
import { warn } from '../utils/logger'
import { debug, warn } from '../utils/logger'
import { Context } from '../utils/types'

const OPERATION = 'TokenEntity' as any

export async function handleTokenEntity(context: Context, collection: CE, nft: NE): Promise<TE | undefined> {
const nftMedia = nft.image || nft.media
function generateTokenId(collectionId: string, nftMedia: string): string {
return `${collectionId}-${md5(nftMedia)}`
}

daiagi marked this conversation as resolved.
Show resolved Hide resolved
async function createToken(context: Context, collection: CE, nft: NE): Promise<TE | undefined> {
const nftMedia = nft.image ?? nft.media
if (!nftMedia || nftMedia === '') {
warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`)
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return
return undefined

}
const tokenId = generateTokenId(collection.id, nftMedia)
debug(OPERATION, { createToken: `Create TOKEN ${tokenId} for NFT ${nft.id}` })
const tokenName = typeof nft.name === 'string' ? nft.name?.replace(/([#_]\d+$)/g, '').trim() : ''
daiagi marked this conversation as resolved.
Show resolved Hide resolved

const tokenId = `${collection.id}-${md5(nftMedia)}`
let token = await getOptional<TE>(context.store, TE, tokenId)
const token = create(TE, tokenId, {
createdAt: nft.createdAt,
collection,
name: tokenName,
count: 1,
totalCount: 1,
hash: md5(tokenId),
image: nft.image,
media: nft.media,
metadata: nft.metadata,
meta: nft.meta,
blockNumber: nft.blockNumber,
updatedAt: nft.updatedAt,
id: tokenId,
})

if (!token) {
const tokenName = (typeof nft.name === 'string' ? nft.name?.replace(/([#_]\d+$)/g, '').trim() : '')

token = create(TE, tokenId, {
createdAt: nft.createdAt,
collection,
name: tokenName,
count: 1,
hash: md5(tokenId),
image: nft.image,
media: nft.media,
blockNumber: nft.blockNumber,
updatedAt: nft.updatedAt,
id: tokenId,
})
} else {
token.count += 1
}
nft.token = token
await context.store.save(token)
await context.store.save(nft)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any particular reason to save nft here?
cause slowest op is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it needs to be updated with new token
but indeed, update is more suitable and faster


token.updatedAt = nft.updatedAt
token.blockNumber = nft.blockNumber
return token
}

async function addNftToToken(context: Context, nft: NE, token: TE): Promise<TE> {
debug(OPERATION, { updateToken: `Add NFT ${nft.id} to TOKEN ${token.id} for ` })
token.count += 1
token.totalCount += 1
token.updatedAt = nft.updatedAt
nft.token = token
await context.store.save(token)
await context.store.save(nft)

return token
}

async function removeNftFromToken(context: Context, nft: NE, token: TE): Promise<void> {
if (!token) {
return
}
debug(OPERATION, { removeNftFromToken: `Unlink NFT ${nft.id} from TOKEN ${token.id}` })

await context.store.update(NE, nft.id, { token: null })
const updatedTotalCount = token.totalCount - 1
await context.store.update(TE, token.id, { count: token.count - 1, totalCount: updatedTotalCount , updatedAt: nft.updatedAt })

if (updatedTotalCount === 0) {
debug(OPERATION, { deleteEmptyToken: `delete empty token ${token.id}` })

await context.store.delete(TE, token.id)
}
}

async function mintHandler(context: Context, collection: CE, nft: NE): Promise<TE | undefined> {
const nftMedia = nft.image ?? nft.media
daiagi marked this conversation as resolved.
Show resolved Hide resolved
debug(OPERATION, { mintHandler: `Handle mint for NFT ${nft.id}` })

if (!nftMedia || nftMedia === '') {
warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`)
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return
return undefined

}

const existingToken = await getOptional<TE>(context.store, TE, generateTokenId(collection.id, nftMedia))
return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft))
}

async function handleMetadataSet(context: Context, collection: CE, nft: NE): Promise<TE | undefined> {
debug(OPERATION, { handleMetadataSet: `Handle set metadata for NFT ${nft.id}` })
const nftMedia = nft.image ?? nft.media

if (!nftMedia || nftMedia === '') {
daiagi marked this conversation as resolved.
Show resolved Hide resolved
warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`)
return
}

let nftWithToken, existingToken
try {
[nftWithToken, existingToken] = await Promise.all([
getWith(context.store, NE, nft.id, { token: true }),
getOptional<TE>(context.store, TE, generateTokenId(collection.id, nftMedia)),
])
} catch (error) {
warn(OPERATION, `ERROR ${error}`)
return
}
if (nftWithToken.token) {
await removeNftFromToken(context, nft, nftWithToken.token)
}
return await (existingToken ? addNftToToken(context, nft, existingToken) : createToken(context, collection, nft))
}

async function handleBurn(context: Context, nft: NE): Promise<void> {
debug(OPERATION, { handleBurn: `Handle Burn for NFT ${nft.id}` })
const nftMedia = nft.image ?? nft.media

if (!nftMedia || nftMedia === '') {
warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`)
return
}

const token = await getOptional<TE>(context.store, TE, generateTokenId(nft.collection.id, nftMedia))

if (!token) {
return
}

debug(OPERATION, { BURN: `decrement Token's ${token.id} count` })

await context.store.update(TE, token.id, { count: token.count - 1, updatedAt: nft.updatedAt })
}

async function listHandler(context: Context, nft: NE): Promise<TE | undefined> {
daiagi marked this conversation as resolved.
Show resolved Hide resolved
const nftMedia = nft.image ?? nft.media

if (!nftMedia || nftMedia === '') {
warn(OPERATION, `MISSING NFT MEDIA ${nft.id}`)
return
}
daiagi marked this conversation as resolved.
Show resolved Hide resolved

try {
const nftWithToken = await getWith(context.store, NE, nft.id, { token: true })
if (!nftWithToken.token) {
warn(OPERATION, `nft ${nft.id} is not linked to a token`)
return
}
const cheapestNFT = nftWithToken.token.cheapestNFT
if (!nft.price && !cheapestNFT?.price) {
return;
}

if (!cheapestNFT?.price || (nft.price && nft.price < cheapestNFT.price)) {
context.store.update(TE, nftWithToken.token.id, { cheapestNFT: nft, updatedAt: nft.updatedAt })
}
} catch (error) {
warn(OPERATION, `ERROR ${error}`)
}
}


export const eventHandlers = {
setMetadataHandler: handleMetadataSet,
mintHandler,
burnHandler: handleBurn,
listHandler
}
Loading