From 252eca12e465892ca2941998ff9514649dde4cfb Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Mon, 18 Oct 2021 15:29:37 +0300 Subject: [PATCH] wip v4 contract integration NFT creation flow --- src/data/words.json | 204 ++++++++++++++++++++++++++++++ src/datatokens/NFTDatatoken.ts | 107 ++++++++++++++++ src/datatokens/index.ts | 1 + src/factories/NFTFactory.ts | 91 +++++++++++++ src/factories/index.ts | 1 + src/pools/balancer/OceanPool.ts | 38 ++++++ src/pools/balancer/PoolFactory.ts | 57 +++++++++ src/pools/balancer/index.ts | 2 + src/utils/DatatokenName.ts | 27 ++++ src/utils/index.ts | 1 + src/v3/index.ts | 0 11 files changed, 529 insertions(+) create mode 100644 src/data/words.json create mode 100644 src/datatokens/NFTDatatoken.ts create mode 100644 src/datatokens/index.ts create mode 100644 src/factories/NFTFactory.ts create mode 100644 src/factories/index.ts create mode 100644 src/pools/balancer/OceanPool.ts create mode 100644 src/pools/balancer/PoolFactory.ts create mode 100644 src/utils/DatatokenName.ts delete mode 100644 src/v3/index.ts diff --git a/src/data/words.json b/src/data/words.json new file mode 100644 index 00000000..7f5dbe20 --- /dev/null +++ b/src/data/words.json @@ -0,0 +1,204 @@ +{ + "nouns": [ + "Crab", + "Fish", + "Seal", + "Octopus", + "Shark", + "Seahorse", + "Walrus", + "Starfish", + "Whale", + "Orca", + "Penguin", + "Jellyfish", + "Squid", + "Lobster", + "Pelican", + "Shrimp", + "Oyster", + "Clam", + "Seagull", + "Dolphin", + "Shell", + "Cormorant", + "Otter", + "Anemone", + "Turtle", + "Coral", + "Ray", + "Barracuda", + "Krill", + "Anchovy", + "Angelfish", + "Barnacle", + "Clownfish", + "Cod", + "Cuttlefish", + "Eel", + "Fugu", + "Herring", + "Haddock", + "Ling", + "Mackerel", + "Manatee", + "Narwhal", + "Nautilus", + "Plankton", + "Porpoise", + "Prawn", + "Pufferfish", + "Swordfish", + "Tuna" + ], + "adjectives": [ + "adamant", + "adroit", + "amatory", + "ambitious", + "amused", + "animistic", + "antic", + "arcadian", + "artistic", + "astonishing", + "astounding", + "baleful", + "bellicose", + "bilious", + "blissful", + "boorish", + "brave", + "breathtaking", + "brilliant", + "calamitous", + "caustic", + "cerulean", + "clever", + "charming", + "comely", + "competent", + "concomitant", + "confident", + "contumacious", + "corpulent", + "crapulous", + "creative", + "dazzling", + "dedicated", + "defamatory", + "delighted", + "delightful", + "determined", + "didactic", + "dilatory", + "dowdy", + "efficacious", + "effulgent", + "egregious", + "empowered", + "endemic", + "enthusiastic", + "equanimous", + "exceptional", + "execrable", + "fabulous", + "fantastic", + "fastidious", + "feckless", + "fecund", + "friable", + "fulsome", + "garrulous", + "generous", + "gentle", + "guileless", + "gustatory", + "heuristic", + "histrionic", + "hubristic", + "incendiary", + "incredible", + "insidious", + "insolent", + "inspired", + "intransigent", + "inveterate", + "invidious", + "invigorated", + "irksome", + "jejune", + "juicy", + "jocular", + "joyful", + "judicious", + "kind", + "lachrymose", + "limpid", + "loquacious", + "lovely", + "luminous", + "mannered", + "marvelous", + "mendacious", + "meretricious", + "minatory", + "mordant", + "motivated", + "munificent", + "nefarious", + "noxious", + "obtuse", + "optimistic", + "parsimonious", + "pendulous", + "pernicious", + "pervasive", + "petulant", + "passionate", + "phenomenal", + "platitudinous", + "pleasant", + "powerful", + "precipitate", + "propitious", + "puckish", + "querulous", + "quiescent", + "rebarbative", + "recalcitant", + "redolent", + "rhadamanthine", + "risible", + "ruminative", + "sagacious", + "salubrious", + "sartorial", + "sclerotic", + "serpentine", + "smart", + "spasmodic", + "strident", + "stunning", + "stupendous", + "taciturn", + "tactful", + "tasty", + "tenacious", + "tremendous", + "tremulous", + "trenchant", + "turbulent", + "turgid", + "ubiquitous", + "uxorious", + "verdant", + "vibrant", + "voluble", + "voracious", + "wheedling", + "withering", + "wonderful", + "zealous" + ] +} diff --git a/src/datatokens/NFTDatatoken.ts b/src/datatokens/NFTDatatoken.ts new file mode 100644 index 00000000..ad0199b8 --- /dev/null +++ b/src/datatokens/NFTDatatoken.ts @@ -0,0 +1,107 @@ +import Web3 from 'web3' +import { AbiItem } from 'web3-utils' +import defaultNFTDatatokenABI from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json' +import { Logger, getFairGasPrice, generateDtName } from '../utils' + +/** + * ERC721 ROLES + */ +interface Roles { + manager: boolean + deployERC20: boolean + updateMetadata: boolean + store: boolean +} + +export class NFTDataToken { + public GASLIMIT_DEFAULT = 1000000 + public factory721Address: string + public factory721ABI: AbiItem | AbiItem[] + public nftDatatokenABI: AbiItem | AbiItem[] + public web3: Web3 + private logger: Logger + public startBlock: number + + constructor( + web3: Web3, + logger: Logger, + nftDatatokenABI?: AbiItem | AbiItem[], + startBlock?: number + ) { + this.nftDatatokenABI = nftDatatokenABI || (defaultNFTDatatokenABI.abi as AbiItem[]) + this.web3 = web3 + this.logger = logger + this.startBlock = startBlock || 0 + } + + /** + * Create new ERC20 datatoken - only user with ERC20Deployer permission can succeed + * @param {String} address + * @param {String} nftAddress + * @param {String} minter User set as initial minter for the ERC20 + * @param {String} name Token name + * @param {String} symbol Token symbol + * @param {String} cap Maximum cap (Number) - will be converted to wei + * @param {Number} templateIndex NFT template index + * @return {Promise} ERC20 datatoken address + */ + public async createERC20( + nftAddress: string, + address: string, + minter: string, + cap: string, + name?: string, + symbol?: string, + templateIndex?: number + ): Promise { + if (!templateIndex) templateIndex = 1 + + // Generate name & symbol if not present + if (!name || !symbol) { + ;({ name, symbol } = generateDtName()) + } + + // Create 721contract object + const contract721 = new this.web3.eth.Contract(this.nftDatatokenABI, nftAddress) + + // Estimate gas for ERC20 token creation + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await contract721.methods + .createERC20( + templateIndex, + [name, symbol], + [minter], + [this.web3.utils.toWei(cap)], + null + ) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createERC20 token function of the contract + const trxReceipt = await contract721.methods + .createERC20( + templateIndex, + [name, symbol], + [minter], + [this.web3.utils.toWei(cap)], + null + ) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + let tokenAddress = null + try { + tokenAddress = trxReceipt.events.ERC20Created.returnValues[0] + } catch (e) { + this.logger.error(`ERROR: Failed to create datatoken : ${e.message}`) + } + return tokenAddress + } +} diff --git a/src/datatokens/index.ts b/src/datatokens/index.ts new file mode 100644 index 00000000..2235441b --- /dev/null +++ b/src/datatokens/index.ts @@ -0,0 +1 @@ +export * from './NFTDatatoken' diff --git a/src/factories/NFTFactory.ts b/src/factories/NFTFactory.ts new file mode 100644 index 00000000..e69f7477 --- /dev/null +++ b/src/factories/NFTFactory.ts @@ -0,0 +1,91 @@ +import { Contract } from 'web3-eth-contract' +import Web3 from 'web3' +import { AbiItem } from 'web3-utils' +import defaultFactory721ABI from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json' +import { Logger, getFairGasPrice, generateDtName } from '../utils' + +/** + * Provides an interface for NFT DataTokens + */ +export class NFTFactory { + public GASLIMIT_DEFAULT = 1000000 + public factory721Address: string + public factory721ABI: AbiItem | AbiItem[] + public web3: Web3 + private logger: Logger + public startBlock: number + public factory721: Contract + + /** + * Instantiate DataTokens. + * @param {String} factory721Address + * @param {AbiItem | AbiItem[]} factory721ABI + * @param {Web3} web3 + */ + constructor( + factory721Address: string, + web3: Web3, + logger: Logger, + factory721ABI?: AbiItem | AbiItem[], + startBlock?: number + ) { + this.factory721Address = factory721Address + this.factory721ABI = factory721ABI || (defaultFactory721ABI.abi as AbiItem[]) + this.web3 = web3 + this.logger = logger + this.startBlock = startBlock || 0 + this.factory721 = new this.web3.eth.Contract( + this.factory721ABI, + this.factory721Address + ) + } + + /** + * Create new NFT + * @param {String} address + * @param {String} name Token name + * @param {String} symbol Token symbol + * @param {Number} templateIndex NFT template index + * @return {Promise} NFT datatoken address + */ + public async createNFT( + address: string, + name?: string, + symbol?: string, + templateIndex?: number + ): Promise { + if (!templateIndex) templateIndex = 1 + // Generate name & symbol if not present + if (!name || !symbol) { + ;({ name, symbol } = generateDtName()) + } + + // Get estimated gas value + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.factory721.methods + .deployERC721Contract(name, symbol, templateIndex, null) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.factory721.methods + .deployERC721Contract(name, symbol, templateIndex, null) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + let tokenAddress = null + try { + tokenAddress = trxReceipt.events.TokenCreated.returnValues[0] + } catch (e) { + this.logger.error(`ERROR: Failed to create datatoken : ${e.message}`) + } + return tokenAddress + } +} diff --git a/src/factories/index.ts b/src/factories/index.ts new file mode 100644 index 00000000..17b42cb9 --- /dev/null +++ b/src/factories/index.ts @@ -0,0 +1 @@ +export * from './NFTFactory' diff --git a/src/pools/balancer/OceanPool.ts b/src/pools/balancer/OceanPool.ts new file mode 100644 index 00000000..6891fd61 --- /dev/null +++ b/src/pools/balancer/OceanPool.ts @@ -0,0 +1,38 @@ +import Web3 from 'web3' +import { AbiItem } from 'web3-utils' +import { Contract } from 'web3-eth-contract' +import defaultPoolABI from '@oceanprotocol/contracts/artifacts/contracts/interfaces/IPool.sol/IPool.json' +import defaultERC20ABI from '@oceanprotocol/contracts/artifacts/contracts/interfaces/IERC20.sol/IERC20.json' +import { PoolFactory } from './PoolFactory' +import { Logger } from '../../utils' + +export class OceanPoolV4 extends PoolFactory { + public oceanAddress: string = null + public dtAddress: string = null + public startBlock: number + public vaultABI: AbiItem | AbiItem[] + public vaultAddress: string + public vault: Contract + public poolABI: AbiItem | AbiItem[] + public erc20ABI: AbiItem | AbiItem[] + + constructor( + web3: Web3, + logger: Logger, + routerAddress: string = null, + oceanAddress: string = null, + startBlock?: number + ) { + super(web3, logger, routerAddress) + + this.poolABI = defaultPoolABI.abi as AbiItem[] + this.erc20ABI = defaultERC20ABI.abi as AbiItem[] + this.vault = new this.web3.eth.Contract(this.vaultABI, this.vaultAddress) + + // if (oceanAddress) { + // this.oceanAddress = oceanAddress + // } + if (startBlock) this.startBlock = startBlock + else this.startBlock = 0 + } +} diff --git a/src/pools/balancer/PoolFactory.ts b/src/pools/balancer/PoolFactory.ts new file mode 100644 index 00000000..e9bb7198 --- /dev/null +++ b/src/pools/balancer/PoolFactory.ts @@ -0,0 +1,57 @@ +import Web3 from 'web3' +import { AbiItem } from 'web3-utils' +import { Contract } from 'web3-eth-contract' +import defaultRouterABI from '@oceanprotocol/contracts/artifacts/contracts/interfaces/IFactoryRouter.sol/IFactoryRouter.json' +import { Logger } from '../../utils' +import { TransactionReceipt } from 'web3-eth' + +export class PoolFactory { + public GASLIMIT_DEFAULT = 1000000 + public web3: Web3 = null + public routerABI: AbiItem | AbiItem[] + + public routerAddress: string + + public logger: Logger + public router: Contract + + /** + * Instantiate PoolFactory. + * @param {String} routerAddress + * @param {AbiItem | AbiItem[]} routerABI + * @param {Web3} web3 + */ + constructor( + web3: Web3, + logger: Logger, + routerAddress: string, + routerABI?: AbiItem | AbiItem[] + ) { + this.web3 = web3 + this.routerAddress = routerAddress + this.routerABI = routerABI || (defaultRouterABI.abi as AbiItem[]) + this.logger = logger + this.router = new this.web3.eth.Contract(this.routerABI, this.routerAddress) + } + + public async deployPool( + account: string, + tokens: string[], + weights: string[], + swapFeePercentage: number, + swapMarketFee: number, + owner: string + ): Promise { + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .deployPool(tokens, weightsInWei, swapFeePercentage, swapMarketFee, owner) + .estimateGas({ from: account }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + this.logger.log('Error estimate gas deployPool') + this.logger.log(e) + estGas = gasLimitDefault + } + } +} diff --git a/src/pools/balancer/index.ts b/src/pools/balancer/index.ts index e69de29b..9c700234 100644 --- a/src/pools/balancer/index.ts +++ b/src/pools/balancer/index.ts @@ -0,0 +1,2 @@ +export * from './PoolFactory' +export * from './OceanPool' diff --git a/src/utils/DatatokenName.ts b/src/utils/DatatokenName.ts new file mode 100644 index 00000000..31d3ceec --- /dev/null +++ b/src/utils/DatatokenName.ts @@ -0,0 +1,27 @@ +import wordListDefault from '../data/words.json' + +/** + * Generate new datatoken name & symbol from a word list + * @return {<{ name: String; symbol: String }>} datatoken name & symbol. Produces e.g. "Endemic Jellyfish Token" & "ENDJEL-45" + */ +export function generateDtName(wordList?: { nouns: string[]; adjectives: string[] }): { + name: string + symbol: string +} { + const list = wordList || wordListDefault + const random1 = Math.floor(Math.random() * list.adjectives.length) + const random2 = Math.floor(Math.random() * list.nouns.length) + const indexNumber = Math.floor(Math.random() * 100) + + // Capitalized adjective & noun + const adjective = list.adjectives[random1].replace(/^\w/, (c) => c.toUpperCase()) + const noun = list.nouns[random2].replace(/^\w/, (c) => c.toUpperCase()) + + const name = `${adjective} ${noun} Token` + // use first 3 letters of name, uppercase it, and add random number + const symbol = `${( + adjective.substring(0, 3) + noun.substring(0, 3) + ).toUpperCase()}-${indexNumber}` + + return { name, symbol } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index fe399232..af558d41 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './Logger' export * from './GasUtils' export * from './Logger' +export * from './DatatokenName' diff --git a/src/v3/index.ts b/src/v3/index.ts deleted file mode 100644 index e69de29b..00000000