Skip to content

Commit

Permalink
CryptoContractDiviner Shared Code
Browse files Browse the repository at this point in the history
  • Loading branch information
arietrouw committed Nov 1, 2023
1 parent ab8b1fe commit d7de20f
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 121 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { assertEx } from '@xylabs/assert'
import { Promisable } from '@xylabs/promise'
import { AbstractDiviner } from '@xyo-network/abstract-diviner'
import {
CryptoContractFunctionCall,
CryptoContractFunctionCallResult,
CryptoContractFunctionCallResultSchema,
CryptoContractFunctionCallSchema,
} from '@xyo-network/crypto-contract-function-read-payload-plugin'
import { DivinerConfig, DivinerParams } from '@xyo-network/diviner-model'
import { PayloadHasher } from '@xyo-network/hash'
import { isPayloadOfSchemaType, Payload } from '@xyo-network/payload-model'

export type FindCallResult<TResult = string, TPayload = Payload> = [TResult, TPayload] | [undefined, TPayload] | [undefined, undefined]

export const CryptoContractDivinerConfigSchema = 'network.xyo.crypto.contract.diviner.config'
export type CryptoContractDivinerConfigSchema = typeof CryptoContractDivinerConfigSchema

export type CryptoContractDivinerConfig = DivinerConfig
export type CryptoContractDivinerParams = DivinerParams<CryptoContractDivinerConfig>

export const ContractInfoSchema = 'network.xyo.crypto.contract.info'
export type ContractInfoSchema = typeof ContractInfoSchema

export type OmittedContractInfo<TFields extends object | Payload | null = null, TSchema extends string | null = null> = Omit<
ContractInfo<TFields, TSchema extends null ? (TFields extends Payload ? TFields['schema'] : never) : TSchema>,
'address' | 'chainId'
>

export type ContractInfo<TFields extends object | null = null, TSchema extends string = ContractInfoSchema> = Payload<
TFields extends null
? object
: TFields & {
address: string
chainId: string
},
TSchema
>

export abstract class CryptoContractDiviner<
TContractInfo extends Payload<Omit<ContractInfo, 'schema'>> = ContractInfo,
TParams extends CryptoContractDivinerParams = CryptoContractDivinerParams,
> extends AbstractDiviner<TParams> {
static override configSchemas = [CryptoContractDivinerConfigSchema]

protected static async findCallResult<TResult = string>(
address: string,
functionName: string,
params: unknown[],
payloads: CryptoContractFunctionCallResult[],
): Promise<TResult | undefined> {
const callHash = await this.generateCallHash(address, functionName, params)
const foundPayload = payloads.find((payload) => payload.call === callHash)
return foundPayload?.result.value as TResult | undefined
}

protected static async generateCallHash(address: string, functionName: string, params: unknown[]) {
const callPayload: CryptoContractFunctionCall = {
address,
functionName,
params,
schema: CryptoContractFunctionCallSchema,
}
return await PayloadHasher.hashAsync(callPayload)
}

protected static matchingExistingField<R = string, T extends Payload = Payload>(objs: T[], field: keyof T) {
const expectedValue = objs.at(0)?.[field] as R
const didNotMatch = objs.reduce((prev, obj) => {
return prev || obj[field] !== expectedValue
}, false)
return didNotMatch ? undefined : expectedValue
}

protected contractInfoRequiredFields(callResults: CryptoContractFunctionCallResult[]): Promisable<Omit<ContractInfo, 'schema'>> {
return {
address: assertEx(CryptoContractDiviner.matchingExistingField(callResults, 'address'), 'Mismatched address'),
chainId: assertEx(CryptoContractDiviner.matchingExistingField(callResults, 'chainId'), 'Mismatched chainId'),
}
}

protected override async divineHandler(inPayloads: CryptoContractFunctionCallResult[] = []): Promise<TContractInfo[]> {
const callResults = inPayloads.filter(isPayloadOfSchemaType<CryptoContractFunctionCallResult>(CryptoContractFunctionCallResultSchema))
const addresses = Object.keys(
callResults.reduce<Record<string, boolean>>((prev, result) => {
if (result.address) {
prev[result.address] = true
}
return prev
}, {}),
)
const result = await Promise.all(
addresses.map(async (address) => {
const foundCallResults = callResults.filter((callResult) => callResult.address === address)
return {
...(await this.reduceResults(address, foundCallResults)),
...this.contractInfoRequiredFields(foundCallResults),
} as TContractInfo
}),
)

return result
}

protected abstract reduceResults(
address: string,
callResults: CryptoContractFunctionCallResult[],
): Promisable<Omit<TContractInfo, 'address' | 'chainId'>>
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { AbstractDiviner } from '@xyo-network/abstract-diviner'
import {
CryptoContractFunctionCall,
CryptoContractFunctionCallResult,
CryptoContractFunctionCallResultSchema,
CryptoContractFunctionCallSchema,
} from '@xyo-network/crypto-contract-function-read-payload-plugin'
import { CryptoContractFunctionCallResult } from '@xyo-network/crypto-contract-function-read-payload-plugin'
import { DivinerConfig, DivinerParams } from '@xyo-network/diviner-model'
import { PayloadHasher } from '@xyo-network/hash'
import { isPayloadOfSchemaType, Payload } from '@xyo-network/payload-model'

import { ContractInfo, CryptoContractDiviner, OmittedContractInfo } from './CryptoContractDiviner'

export const CryptoContractErc1155DivinerConfigSchema = 'network.xyo.crypto.contract.erc1155.info.diviner.config'
export type CryptoContractErc1155DivinerConfigSchema = typeof CryptoContractErc1155DivinerConfigSchema
Expand All @@ -18,53 +12,26 @@ export type CryptoContractErc1155DivinerParams = DivinerParams<CryptoContractErc
export const Erc1155ContractInfoSchema = 'network.xyo.crypto.contract.erc1155.info'
export type Erc1155ContractInfoSchema = typeof Erc1155ContractInfoSchema

export type Erc1155ContractInfo = Payload<
export type Erc1155ContractInfo = ContractInfo<
{
uri: string
uri?: string
},
Erc1155ContractInfoSchema
>

const generateCallHash = async (address: string, functionName: string, params: unknown[]) => {
const callPayload: CryptoContractFunctionCall = {
address,
functionName,
params,
schema: CryptoContractFunctionCallSchema,
}
return await PayloadHasher.hashAsync(callPayload)
}

const findCallResult = async (address: string, functionName: string, params: unknown[], payloads: CryptoContractFunctionCallResult[]) => {
const callHash = await generateCallHash(address, functionName, params)
const foundPayload = payloads.find((payload) => payload.call === callHash)
return foundPayload?.result.value as string
}

export class CryptoContractErc1155Diviner<
TParams extends CryptoContractErc1155DivinerParams = CryptoContractErc1155DivinerParams,
> extends AbstractDiviner<TParams> {
> extends CryptoContractDiviner<Erc1155ContractInfo, TParams> {
static override configSchemas = [CryptoContractErc1155DivinerConfigSchema]
protected override async divineHandler(inPayloads: CryptoContractFunctionCallResult[] = []): Promise<Erc1155ContractInfo[]> {
const callResults = inPayloads.filter(isPayloadOfSchemaType<CryptoContractFunctionCallResult>(CryptoContractFunctionCallResultSchema))
const addresses = Object.keys(
callResults.reduce<Record<string, boolean>>((prev, result) => {
if (result.address) {
prev[result.address] = true
}
return prev
}, {}),
)
const result = await Promise.all(
addresses.map(async (address) => {
const erc1155Info: Erc1155ContractInfo = {
schema: Erc1155ContractInfoSchema,
uri: await findCallResult(address, 'uri', [], callResults),
}
return erc1155Info
}),
)

return result
protected override async reduceResults(
address: string,
callResults: CryptoContractFunctionCallResult[],
): Promise<OmittedContractInfo<Erc1155ContractInfo>> {
const uri = await CryptoContractErc1155Diviner.findCallResult(address, 'uri', [], callResults)
return {
schema: Erc1155ContractInfoSchema,
uri,
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { assertEx } from '@xylabs/assert'
import { exists } from '@xylabs/exists'
import { AbstractDiviner } from '@xyo-network/abstract-diviner'
import {
CryptoContractFunctionCall,
CryptoContractFunctionCallResult,
CryptoContractFunctionCallResultSchema,
CryptoContractFunctionCallSchema,
} from '@xyo-network/crypto-contract-function-read-payload-plugin'
import { CryptoContractFunctionCallResult } from '@xyo-network/crypto-contract-function-read-payload-plugin'
import { DivinerConfig, DivinerParams } from '@xyo-network/diviner-model'
import { PayloadHasher } from '@xyo-network/hash'
import { isPayloadOfSchemaType, Payload } from '@xyo-network/payload-model'

import { ContractInfo, CryptoContractDiviner, OmittedContractInfo } from './CryptoContractDiviner'

export const CryptoContractErc721DivinerConfigSchema = 'network.xyo.crypto.contract.erc721.info.diviner.config'
export type CryptoContractErc721DivinerConfigSchema = typeof CryptoContractErc721DivinerConfigSchema
Expand All @@ -20,79 +12,30 @@ export type CryptoContractErc721DivinerParams = DivinerParams<CryptoContractErc7
export const Erc721ContractInfoSchema = 'network.xyo.crypto.contract.erc721.info'
export type Erc721ContractInfoSchema = typeof Erc721ContractInfoSchema

export type Erc721ContractInfo = Payload<
export type Erc721ContractInfo = ContractInfo<
{
address: string
chainId: string
name?: string
symbol?: string
totalSupply?: string
},
Erc721ContractInfoSchema
>

const generateCallHash = async (address: string, functionName: string, params: unknown[]) => {
const callPayload: CryptoContractFunctionCall = {
address,
functionName,
params,
schema: CryptoContractFunctionCallSchema,
}
return await PayloadHasher.hashAsync(callPayload)
}

const matchingExistingField = <R = string, T extends Payload = Payload>(objs: T[], field: keyof T) => {
const expectedValue = objs.at(0)?.[field] as R
const didNotMatch = objs.reduce((prev, obj) => {
return prev || obj[field] !== expectedValue
}, false)
return didNotMatch ? undefined : expectedValue
}

type FindCallResult<TResult = string, TPayload = Payload> = [TResult, TPayload] | [undefined, TPayload] | [undefined, undefined]

const findCallResult = async <TResult = string>(
address: string,
functionName: string,
params: unknown[],
payloads: CryptoContractFunctionCallResult[],
): Promise<FindCallResult<TResult, CryptoContractFunctionCallResult>> => {
const callHash = await generateCallHash(address, functionName, params)
const foundPayload = payloads.find((payload) => payload.call === callHash)
return foundPayload ? [foundPayload?.result.value as TResult, foundPayload] : [undefined, undefined]
}

export class CryptoContractErc721Diviner<
TParams extends CryptoContractErc721DivinerParams = CryptoContractErc721DivinerParams,
> extends AbstractDiviner<TParams> {
> extends CryptoContractDiviner<Erc721ContractInfo, TParams> {
static override configSchemas = [CryptoContractErc721DivinerConfigSchema]
protected override async divineHandler(inPayloads: CryptoContractFunctionCallResult[] = []): Promise<Erc721ContractInfo[]> {
const callResults = inPayloads.filter(isPayloadOfSchemaType<CryptoContractFunctionCallResult>(CryptoContractFunctionCallResultSchema))
const addresses = Object.keys(
callResults.reduce<Record<string, boolean>>((prev, result) => {
if (result.address) {
prev[result.address] = true
}
return prev
}, {}),
)
const result = await Promise.all(
addresses.map(async (address) => {
const [name, namePayload] = await findCallResult(address, 'name', [], callResults)
const [symbol, symbolPayload] = await findCallResult(address, 'symbol', [], callResults)
const callResultPayloads = [namePayload, symbolPayload].filter(exists)

const erc721Info: Erc721ContractInfo = {
address: assertEx(matchingExistingField(callResultPayloads, 'address'), 'Mismatched address'),
chainId: assertEx(matchingExistingField(callResultPayloads, 'chainId'), 'Mismatched chainId'),
name,
schema: Erc721ContractInfoSchema,
symbol,
}
return erc721Info
}),
)

return result
protected override async reduceResults(
address: string,
callResults: CryptoContractFunctionCallResult[],
): Promise<OmittedContractInfo<Erc721ContractInfo>> {
const name = await CryptoContractErc721Diviner.findCallResult(address, 'name', [], callResults)
const symbol = await CryptoContractErc721Diviner.findCallResult(address, 'symbol', [], callResults)
return {
name,
schema: Erc721ContractInfoSchema,
symbol,
}
}
}

0 comments on commit d7de20f

Please sign in to comment.