@ixily/activ
Version:
Alpha Capture Trade Idea Verification. Blockchain ownership proven trade ideas and strategies.
659 lines (601 loc) • 15.8 kB
text/typescript
import { BigNumber } from 'ethers'
import { BehaviorSubject } from 'rxjs'
import {
ethers,
IContract,
IdeaTrafficDirectorModule,
IOperationalCost,
LogModule as log,
OperationalCostModule,
} from '../..'
const createIdeaCore = async (
contract: IContract,
_strategyKey: string,
_rulesVersion: number,
_metadata: string,
_subscribers: string[],
_ideaKey?: number,
clientInBehalf?: string,
gasAggressivity: number = 2,
): Promise<{
nftId: number
strategyKey: string
ideaKey: number
ideaStageKey: number
openseaUrl: string
transactionHash: string
blockNumber: number
cost: IOperationalCost
}> => {
log.dev('pre commit')
const callerAddress =
clientInBehalf !== undefined
? clientInBehalf
: await contract.gate!.signer.getAddress()
log.dev(`Caller address: ${callerAddress}`)
log.dev(`Strategy key: ${_strategyKey}`)
log.dev(`Idea key: ${_ideaKey}`)
const balance = await contract.gate!.signer.getBalance()
const balanceInEth = ethers.utils.formatEther(balance)
log.dev(`Caller balance: ${Number(balanceInEth).toFixed(4)} ETH`)
// to avoid twice data
// if the user is a owner and this appears in the subscribers list, "remove" it
// (i.e. apply the filer)
_subscribers =
_subscribers?.filter(
(address) =>
address?.toLowerCase() !== callerAddress?.toLowerCase(),
) || []
log.dev(`Subscribers post:`, _subscribers)
log.dev(`clientInBehalf:`, clientInBehalf)
log.dev('_ideaKey:', _ideaKey)
const costPrior = await OperationalCostModule.getEstimatedCosts(
contract,
clientInBehalf,
_ideaKey === undefined,
)
const APPLY_SETTINGS = true
let optionalSettings: any = {}
if (APPLY_SETTINGS) {
// const gasLimit = costPrior.estimatedComputationalRawCost
const gasLimit =
costPrior.estimatedComputationalRawCost +
Math.floor((1 / 10) * costPrior.estimatedComputationalRawCost)
const gasPrice = costPrior.estimatedGasPrice.weiBigNumber.add(
costPrior.estimatedGasPrice.weiBigNumber
.mul(gasAggressivity)
.div(100),
)
const value = costPrior.ixilyTax.weiBigNumber
log.dev('currentIxilyTax:')
log.dev(value)
optionalSettings = {
gasLimit,
gasPrice,
value,
}
}
if (optionalSettings.value === 0 || optionalSettings.value === undefined) {
throw new Error('optionalSettings.value is 0 or undefined')
}
// console.log('costPrior:')
// console.log(costPrior)
const dealErrIfAboutCost = async (err: Error) => {
if (err.message) {
for (const increaseIndex of contract.recipe.errorsDealWith
.increaseGasUnits) {
if (err.message.includes(increaseIndex)) {
await OperationalCostModule.increaseEstimatedComputationalRawCost(
contract.gate!.address,
_ideaKey === undefined,
clientInBehalf !== undefined,
)
}
}
for (const decreaseIndex of contract.recipe.errorsDealWith
.decreaseGasUnits) {
if (err.message.includes(decreaseIndex)) {
await OperationalCostModule.decreaseEstimatedComputationalRawCost(
contract.gate!.address,
_ideaKey === undefined,
clientInBehalf !== undefined,
)
}
}
}
// console.log('err')
// console.log(err)
// console.log('err.message')
// console.log(err.message)
throw err
}
const nftCommit =
clientInBehalf === undefined
? _ideaKey === undefined
? await contract
.gate!.createIdea(
_strategyKey,
_rulesVersion,
_metadata,
_subscribers,
optionalSettings,
)
.catch(dealErrIfAboutCost)
: await contract
.gate!.createIdeaStage(
_strategyKey,
_rulesVersion,
_metadata,
_subscribers,
_ideaKey,
optionalSettings,
)
.catch(dealErrIfAboutCost)
: _ideaKey === undefined
? await contract
.gate!.providerCreateIdea(
clientInBehalf,
_strategyKey,
_rulesVersion,
_metadata,
_subscribers,
optionalSettings,
)
.catch(dealErrIfAboutCost)
: await contract
.gate!.providerCreateIdeaStage(
clientInBehalf,
_strategyKey,
_rulesVersion,
_metadata,
_subscribers,
_ideaKey,
optionalSettings,
)
.catch(dealErrIfAboutCost)
log.dev('nftCommit')
log.dev(nftCommit)
const nftCommitted = await nftCommit.wait().catch(dealErrIfAboutCost)
log.dev('createNftIdea (nftCommitted)')
log.dev(nftCommitted)
const abiForEvent = [
'event IdeaCreated(address,uint256,string,uint256,uint256,uint256)',
]
const ifaceForEvent = new ethers.utils.Interface(abiForEvent)
const parsedLogs: any[] = []
for (const logIn of nftCommitted.logs) {
try {
const parsedLog = ifaceForEvent.parseLog(logIn)
parsedLogs.push(parsedLog)
} catch (e) {}
}
// console.log('nftCommitted logs')
// console.log(parsedLogs)
if (parsedLogs.length !== 1) {
throw new Error('IdeaNftCreation: IdeaCreated event not found')
}
// console.log('nftCommitted events')
// console.log(nftCommitted.events!)
// for (const evt of nftCommitted.events!) {
// console.log('event:')
// console.log(evt)
// console.log('topics:')
// console.log(evt.topics)
// }
// const targetEvent = nftCommitted.events!.find(
// (one: any) => one.event === 'IdeaCreated',
// )
const targetEvent = parsedLogs[0]!
if (targetEvent == undefined) {
throw new Error('IdeaNftCreation: IdeaCreated event not found')
} else {
// log.dev(targetEvent.args)
const nftId = targetEvent.args[1]
const strategyKey = targetEvent.args[2]
const ideaKey = targetEvent.args[3]
const ideaStageKey = targetEvent.args[4]
if (_strategyKey === strategyKey) {
const openseaUrl = `https://testnets.opensea.io/assets/${
nftCommitted.to
}/${nftId.toNumber()}`
const costAfter = await OperationalCostModule.getFinalCosts(
costPrior,
nftCommitted,
contract,
clientInBehalf,
_ideaKey === undefined,
)
// console.log('costAfter')
// console.log(costAfter)
return {
nftId: (nftId as BigNumber).toNumber(),
strategyKey: _strategyKey,
ideaKey: ideaKey.toNumber(),
ideaStageKey: ideaStageKey.toNumber(),
openseaUrl,
transactionHash: nftCommitted.transactionHash,
blockNumber: nftCommitted.blockNumber,
cost: costAfter,
}
} else {
throw new Error('IdeaNftCreation: Strategy key mismatch')
}
}
}
const createIdea = async (
contract: IContract,
_strategyKey: string,
_rulesVersion: number,
_metadata: string,
_subscribers: string[],
_ideaKey?: number,
clientInBehalf?: string,
gasAggressivity: number = 2,
): Promise<{
nftId: number
strategyKey: string
ideaKey: number
ideaStageKey: number
openseaUrl: string
transactionHash: string
blockNumber: number
cost: IOperationalCost
}> => {
return IdeaTrafficDirectorModule.runSequentialTicket(
[clientInBehalf || ''],
async (): Promise<{
nftId: number
strategyKey: string
ideaKey: number
ideaStageKey: number
openseaUrl: string
transactionHash: string
blockNumber: number
cost: IOperationalCost
}> => {
const result = await createIdeaCore(
contract,
_strategyKey,
_rulesVersion,
_metadata,
_subscribers,
_ideaKey,
clientInBehalf,
gasAggressivity,
)
return result
},
)
}
const giveIdeaTo = async (
contract: IContract,
_ideaNftId: number,
_clientAddresses: string[],
) => {
log.dev('_clientAddresses:')
log.dev(_clientAddresses)
const nftCommitted = await contract.gate!.giveIdeaTo(
_ideaNftId,
_clientAddresses,
{
gasLimit: 2000000,
},
)
await nftCommitted.wait()
}
/*
* Returns the list of walllet addresses that have access to the idea
*
* @param contract The contract instance
* @param _ideaNftId The idea NFT id
* @returns The list of wallet addresses
*/
const getIdeaViewers = async (
contract: IContract,
_ideaNftId: number,
): Promise<string[]> => {
const ideaViewers = await contract.gate!.getIdeaViewers(_ideaNftId)
return ideaViewers as string[]
}
const watchEvents = async (
contract: IContract,
): Promise<
BehaviorSubject<{
network: string
contractAddress: string
}>
> => {
const ideaFilter = contract.gate!.filters.IdeaCreated()
const ideaEvent$ = new BehaviorSubject<{
network: string
contractAddress: string
event?: {
creatorAddress: string
nftId: number
strategyKey: string
ideaKey: number
stageKey: number
blockNumber: number
}
}>({
network: contract.recipe.chain,
contractAddress: contract.gate!.address,
})
// console.log('Watching the Mint events...')
contract.gate!.on(ideaFilter, (...args) => {
ideaEvent$.next({
network: contract.recipe.chain,
contractAddress: contract.gate!.address,
event: {
creatorAddress: args[0],
nftId: args[1].toNumber(),
strategyKey: args[2],
ideaKey: args[3].toNumber(),
stageKey: args[4].toNumber(),
blockNumber: args[5].toNumber(),
},
})
})
return ideaEvent$
}
const readEvents = async (
contract: IContract,
fromBlock?: number,
toBlock?: number,
): Promise<any> => {
const ideaFilter = contract.gate!.filters.IdeaCreated()
// console.log('Querying the Mint events...')
const mintEvents = await contract.gate!.queryFilter(
ideaFilter,
fromBlock,
toBlock,
)
// console.log('mintEvents')
// console.log(mintEvents)
return mintEvents
}
const getFirstBlock = async (contract: IContract): Promise<BigNumber> => {
return contract.gate!.getFirstEventBlock()
}
const getLastBlock = async (contract: IContract): Promise<BigNumber> => {
return contract.gate!.getLastEventBlock()
}
const listStrategies = async (
_creator: string,
contract: IContract,
): Promise<Array<[string, string, string]>> => {
const listStrategies = await contract.gate!.listStrategies(_creator)
return listStrategies as Array<[string, string, string]>
}
const listAllStrategies = async (
contract: IContract,
): Promise<Array<[string, string, string]>> => {
const listedAllStrategies = await contract.gate!.listAllStrategies()
return listedAllStrategies as Array<[string, string, string]>
}
const listIdeas = async (
_creator: string,
_strategyKey: string,
contract: IContract,
): Promise<number[]> => {
// log.dev('pre listIdeas:')
const listedIdeas = await contract.gate!.listIdeas(_creator, _strategyKey)
// log.dev('ends here')
// log.dev('listedIdeas')
// log.dev(listedIdeas)
return listedIdeas.map((each: { toNumber: () => number }) =>
each.toNumber(),
)
}
const listStages = async (
_creator: string,
_strategyKey: string,
_ideaKey: number,
contract: IContract,
): Promise<number[]> => {
const listedStages = await floodProtectiveWrapper(async () => {
return contract.gate!.listStages(_creator, _strategyKey, _ideaKey)
}, 'listStages')
return listedStages.map((each: { toNumber: () => number }) =>
each.toNumber(),
)
}
const getCreatorOfIdea = async (
nftIdea: number,
contract: IContract,
): Promise<string> => {
const creator = await contract.gate!.getCreatorOfNft(nftIdea)
return creator.toString()
}
const getIdeaNftIdByKeys = async (
_creator: string,
_strategyKey: string,
_ideaKey: number,
_ideaStageKey: number,
contract: IContract,
): Promise<number> => {
// provide contract
return floodProtectiveWrapper(async () => {
return (
(await contract.gate!.getIdeaByKeys(
_creator,
_strategyKey,
_ideaKey,
_ideaStageKey,
)) as BigNumber
).toNumber()
}, 'getIdeaNftIdByKeys')
}
const getIdeaMetadataUriByNftId = async (
ideaNft: number | BigNumber,
contract: IContract,
): Promise<string> => {
if (typeof ideaNft === 'number') {
try {
ideaNft = BigNumber.from(ideaNft.toString())
} catch (e) {}
}
let mid = await floodProtectiveWrapper(
async () => {
return contract.gate!.uri(Number(ideaNft))
},
'getIdeaMetadataUriByNftId',
10,
)
if (mid === 'https://ipfs.io/ipfs/') {
throw new Error('NFT_NOT_FOUND')
}
return mid
}
const getMetadataIdByBlockId = async (
blockIdParam: number,
contract: IContract,
): Promise<{
blockId: number
metadataId: string
nftId: number
}> => {
// console.log(
// 'nft.module getMetadataIdByBlockId (blockIdParam)',
// blockIdParam,
// )
const data = await contract.gate!.getMetadataIdByBlockId(blockIdParam)
if ([null, undefined, 'none'].includes(data[2])) {
throw new Error('NFT MODULE ERROR: No metadata found for this block')
}
const blockId = data[0].toNumber()
const nftId = data[1].toNumber()
const metadataId = data[2]
console.log('nft.module getMetadataIdByBlockId (blockId)', blockId)
console.log('nft.module getMetadataIdByBlockId (nftId)', nftId)
console.log('nft.module getMetadataIdByBlockId (metadataId)', metadataId)
const response = {
blockId,
nftId,
metadataId,
}
console.log('nft.module getMetadataIdByBlockId (response)', response)
return response
}
const authorizeProvider = async (provider: string, contract: IContract) => {
const auth = await contract.gate!.authorizeProvider(provider, {
gasLimit: 2000000,
})
await auth.wait()
}
const revokeProvider = async (provider: string, contract: IContract) => {
const auth = await contract.gate!.revokeProvider(provider)
await auth.wait()
}
/*
* Provider check is supposed to be called by PROVIDER BACKEND to
* test if he is authorized by client
*/
const providerCheck = async (
client: string,
contract: IContract,
): Promise<boolean> => {
const auth = await contract.gate!.providerCheck(client)
return auth
}
/*
* Authorize Check is supposed to be called by CLIENT ON BROWSER to
* test if he authorized the provider
*/
const authorizeCheck = async (
provider: string,
contract: IContract,
): Promise<boolean> => {
const auth = await contract.gate!.authorizeCheck(provider)
return auth
}
const getOwnedBy = async (owner: string, contract: IContract): Promise<any> => {
const nfts = await contract.gate!.getNftsOwnedBy(owner)
return nfts.map((each: { toNumber: () => any }) => each.toNumber())
}
const getCreatedBy = async (
creator: string,
contract: IContract,
): Promise<number[]> => {
const nfts = await contract.gate!.getNftsCreatedBy(creator)
return nfts.map((each: { toNumber: () => any }) => each.toNumber())
}
const getLastId = async (contract: IContract): Promise<number> => {
const lastNftId = await contract.gate!.getLastNftId()
return lastNftId?.toNumber()
}
const floodProtectiveWrapper = async <T>(
_f: () => Promise<T>,
_fName: string,
times: number = 10,
): Promise<T> => {
let attempts = 0
let value: T | 'null' | undefined = 'null'
const randomBetweenOneAnd20 = Math.floor(Math.random() * 20) + 1
await new Promise((resolve) => setTimeout(resolve, randomBetweenOneAnd20))
while (value === 'null') {
try {
value = await _f()
} catch (err) {
if (attempts >= times) {
const errorParsed =
(err as unknown as any).message || JSON.stringify(err)
console.error(errorParsed)
throw new Error(
`Error in ${_fName} ` + times + ` times as logged.`,
)
}
const randomBetweenOneAnd20 = Math.floor(Math.random() * 20) + 1
await new Promise((resolve) =>
setTimeout(resolve, randomBetweenOneAnd20 * 10),
)
}
attempts++
}
return value
}
export const NftModule = {
/* MINT METHODS */
mint: {
/* can be called by client or provider */
general: {
createIdea,
giveIdeaTo,
},
/* by client user direct only */
clientOnly: {
authorizeProvider,
revokeProvider,
},
},
/* VIEWS */
view: {
/* can be called by client or provider */
general: {
getFirstBlock,
getLastBlock,
watchEvents,
readEvents,
listStrategies,
listAllStrategies,
listIdeas,
listStages,
getCreatorOfIdea,
getIdeaNftIdByKeys,
getIdeaMetadataUriByNftId,
getMetadataIdByBlockId,
getOwnedBy,
getCreatedBy,
getLastId,
getIdeaViewers,
},
/* by provider only */
providerOnly: {
providerCheck,
},
/* by client only */
clientOnly: {
authorizeCheck,
},
},
}