import { ApproveNftSpending, AuctionService, BidOnAuction, CancelOrSettleAuction, GenerateAuction, UpdateFeeRecipient, } from '@tatumio/api-client' import { EvmBasedBlockchain } from '@tatumio/shared-core' import { TransactionConfig } from 'web3-core' import { BroadcastFunction, FromPrivateKeyOrSignatureId } from '@tatumio/shared-blockchain-abstract' import BigNumber from 'bignumber.js' import { EvmBasedWeb3 } from './evm-based.web3' import { Erc1155, Erc20Token, Erc721Token_Cashback, MarketplaceSmartContract } from '../contracts' import { AbiItem, toWei } from 'web3-utils' import { evmBasedUtils } from '../evm-based.utils' import { erc20 } from '../transactions/erc20' import { WithoutChain } from '@tatumio/shared-abstract-sdk' export const evmBasedAuction = (args: { blockchain: EvmBasedBlockchain web3: EvmBasedWeb3 broadcastFunction: BroadcastFunction }) => { const { blockchain, web3, broadcastFunction } = args return { /** * For more details, see Tatum API documentation */ getAuction: async (contractAddress: string, auctionId: string) => { return AuctionService.getAuction( blockchain as 'ETH' | 'ONE' | 'CELO' | 'MATIC' | 'BSC', contractAddress, auctionId, ) }, /** * For more details, see Tatum API documentation */ getAuctionFee: async (contractAddress: string) => { return AuctionService.getAuctionFee( blockchain as 'ETH' | 'ONE' | 'CELO' | 'MATIC' | 'BSC', contractAddress, ) }, /** * For more details, see Tatum API documentation */ getAuctionFeeRecipient: async (contractAddress: string): Promise<{ address?: string }> => { return AuctionService.getAuctionFeeRecipient( blockchain as 'ETH' | 'ONE' | 'CELO' | 'MATIC' | 'BSC', contractAddress, ) }, prepare: { /** * Sign ETH deploy NFT Auction contract transaction with private keys locally. Nothing is broadcast to the blockchain. * @param body content of the transaction to broadcast * @param provider url of the ETH Server to connect to. If not set, default public server will be used. * @returns transaction data to be broadcast to blockchain, or signatureId in case of Tatum KMS */ deployAuctionSignedTransaction: async (body: DeployNftAuction, provider?: string) => deployAuctionSignedTransaction(body, web3, provider), auctionUpdateFeeRecipientSignedTransaction: async ( body: UpdateAuctionFeeRecipient, provider?: string, ) => auctionUpdateFeeRecipientSignedTransaction(body, web3, provider), /** * Create new auction on the auction contract. Before auction, seller must approve spending of the NFT token for the Auction contract. * After auction is created, auction contract transfers the asset to the auction smart contract. * Only auction for existing NFTs can be created - seller must be owner of the NFT asset. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns transaction data to be broadcast to blockchain, or signatureId in case of Tatum KMS */ createAuctionSignedTransaction: async (body: CreateAuction, provider?: string) => createAuctionSignedTransaction(body, web3, provider), /** * Approve NFT transfer for auction to perform listing of the asset. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns transaction data to be broadcast to blockchain, or signatureId in case of Tatum KMS */ auctionApproveNftTransferSignedTransaction: async (body: ApproveNftTransfer, provider?: string) => auctionApproveNftTransferSignedTransaction(body, web3, provider), /** * Approve ERC20 transfer for auction to perform bidding on the asset in the auction. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns transaction data to be broadcast to blockchain, or signatureId in case of Tatum KMS */ auctionApproveErc20TransferSignedTransaction: async (body: ApproveNftTransfer, provider?: string) => auctionApproveErc20TransferSignedTransaction(body, web3, provider), /** * Bid on the auction. Buyer must either send native assets with this operation, or approve ERC20 token spending before. * After auction is sold, it's in a pending state to be processed by the auction. Noone receives the assets unless the auction operator processes that. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns transaction data to be broadcast to blockchain, or signatureId in case of Tatum KMS */ auctionBidSignedTransaction: async (body: AuctionBid, provider?: string) => auctionBidSignedTransaction(body, blockchain, broadcastFunction, web3, provider), /** * Cancel auction on the auction. Only possible for the seller or the operator. There must be no buyer present for that auction. NFT asset is sent back to the seller. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns transaction data to be broadcast to blockchain, or signatureId in case of Tatum KMS */ auctionCancelSignedTransaction: async (body: CancelAuction, provider?: string) => auctionCancelSignedTransaction(body, web3, provider), /** * Settle auction. There must be buyer present for that auction. NFT will be sent to the bidder, assets to the seller and fee to the operator. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns {txId: string} Transaction ID of the operation, or signatureID in case of Tatum KMS */ auctionSettleSignedTransaction: async (body: SettleAuction, provider?: string) => auctionSettleSignedTransaction(body, web3, provider), }, send: { /** * Deploy new smart contract for NFT auction logic. Smart contract enables auction operator to create new auction for NFT (ERC-721/1155). * Operator can set a fee in percentage, which will be paid on top of the price of the asset. * can be offered for native asset - ETH, BSC, etc. - or any ERC20 token - this is configurable during auction creation. * Before auction is created, seller must approve transfer of the NFT to the auction contract. * Buyer will bid for the asset from the auction using native asset - send assets along the gid() smart contract call, or via ERC20 token. * Buyer of the auction must perform approval for the smart contract to access ERC20 token, before the actual bid() method is called. * Once there is higher bid than the actual one, the previous bidder's funds will be returned to him and new bidder will be the current winning one. * When auction ends, anyone can settle the auction - NFT will be sent to the bidder, assets to the seller and fee to the operator. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns {txId: string} Transaction ID of the operation, or signatureID in case of Tatum KMS */ deployAuctionSignedTransaction: async (body: DeployNftAuction, provider?: string) => broadcastFunction({ txData: await deployAuctionSignedTransaction(body, web3, provider), signatureId: body.signatureId, }), /** * Update auction fee recipient. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns {txId: string} Transaction ID of the operation, or signatureID in case of Tatum KMS */ auctionUpdateFeeRecipientSignedTransaction: async ( body: UpdateAuctionFeeRecipient, provider?: string, ) => broadcastFunction({ txData: await auctionUpdateFeeRecipientSignedTransaction(body, web3, provider), signatureId: body.signatureId, }), /** * Create new auction on the auction contract. Before auction, seller must approve spending of the NFT token for the Auction contract. * After auction is created, auction contract transfers the asset to the auction smart contract. * Only auction for existing NFTs can be created - seller must be owner of the NFT asset. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns {txId: string} Transaction ID of the operation, or signatureID in case of Tatum KMS */ createAuctionSignedTransaction: async (body: CreateAuction, provider?: string) => broadcastFunction({ txData: await createAuctionSignedTransaction(body, web3, provider), signatureId: body.signatureId, }), /** * Approve NFT transfer for auction to perform listing of the asset. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns {txId: string} Transaction ID of the operation, or signatureID in case of Tatum KMS */ auctionApproveNftTransferSignedTransaction: async (body: ApproveNftTransfer, provider?: string) => broadcastFunction({ txData: await auctionApproveNftTransferSignedTransaction(body, web3, provider), signatureId: body.signatureId, }), /** * Approve ERC20 transfer for auction to perform bidding on the asset in the auction. * @param testnet use testnet or not * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns {txId: string} Transaction ID of the operation, or signatureID in case of Tatum KMS */ auctionApproveErc20TransferSignedTransaction: async (body: ApproveNftTransfer, provider?: string) => broadcastFunction({ txData: await auctionApproveErc20TransferSignedTransaction(body, web3, provider), signatureId: body.signatureId, }), auctionBidSignedTransaction: async (body: AuctionBid, provider?: string) => broadcastFunction({ txData: await auctionBidSignedTransaction(body, blockchain, broadcastFunction, web3, provider), signatureId: body.signatureId, }), /** * Cancel auction on the auction. Only possible for the seller or the operator. There must be no buyer present for that auction. NFT asset is sent back to the seller. * @param body request data * @param provider optional provider to enter. if not present, Tatum Web3 will be used. * @returns {txId: string} Transaction ID of the operation, or signatureID in case of Tatum KMS */ auctionCancelSignedTransaction: async (body: CancelAuction, provider?: string) => broadcastFunction({ txData: await auctionCancelSignedTransaction(body, web3, provider), signatureId: body.signatureId, }), auctionSettleSignedTransaction: async (body: SettleAuction, provider?: string) => broadcastFunction({ txData: await auctionSettleSignedTransaction(body, web3, provider), signatureId: body.signatureId, }), }, } } export type DeployNftAuction = FromPrivateKeyOrSignatureId> const deployAuctionSignedTransaction = async ( body: DeployNftAuction, web3: EvmBasedWeb3, provider?: string, ) => { const client = web3.getClient(provider) // TODO any type const contract = new client.eth.Contract(MarketplaceSmartContract.abi as any, undefined, { data: MarketplaceSmartContract.bytecode, }) const deploy = contract.deploy({ arguments: [body.auctionFee, body.feeRecipient], data: MarketplaceSmartContract.bytecode, }) const tx: TransactionConfig = { from: 0, data: deploy.encodeABI(), nonce: body.nonce, } return await evmBasedUtils.prepareSignedTransactionAbstraction( client, tx, web3, body.signatureId, body.fromPrivateKey, body.fee?.gasLimit, body.fee?.gasPrice, ) } export type UpdateAuctionFeeRecipient = FromPrivateKeyOrSignatureId> & Amount const auctionUpdateFeeRecipientSignedTransaction = ( body: UpdateAuctionFeeRecipient, web3: EvmBasedWeb3, provider?: string, ) => { const client = web3.getClient(provider) const methodName = 'setAuctionFeeRecipient' const methodAbi = MarketplaceSmartContract.abi.find((a) => a.name === methodName) // TODO any type const contract = new client.eth.Contract([methodAbi as any]) const params = [body.feeRecipient] const tx: TransactionConfig = { from: 0, to: body.contractAddress.trim(), value: body.amount ? `0x${new BigNumber(client.utils.toWei(body.amount, 'ether')).toString(16)}` : undefined, data: contract.methods[methodName as string](...params).encodeABI(), nonce: body.nonce, } return evmBasedUtils.prepareSignedTransactionAbstraction( client, tx, web3, body.signatureId, body.fromPrivateKey, body.fee?.gasLimit, body.fee?.gasPrice, ) } export type ApproveNftTransfer = FromPrivateKeyOrSignatureId> & Amount const auctionApproveNftTransferSignedTransaction = async ( body: ApproveNftTransfer, web3: EvmBasedWeb3, provider?: string, ) => { const client = web3.getClient(provider) const methodName = body.isErc721 ? 'approve' : 'setApprovalForAll' const abi = body.isErc721 ? Erc721Token_Cashback.abi : Erc1155.abi const methodAbi = abi.find((a) => a.name === methodName) // TODO any type const contract = new client.eth.Contract([methodAbi as any]) const params = body.isErc721 ? [body.spender, `0x${new BigNumber(body.tokenId).toString(16)}`] : [body.spender, true] const data = contract.methods[methodName](...params).encodeABI() const tx: TransactionConfig = { from: 0, to: body.contractAddress.trim(), value: body.amount ? `0x${new BigNumber(client.utils.toWei(body.amount, 'ether')).toString(16)}` : undefined, data, nonce: body.nonce, } return evmBasedUtils.prepareSignedTransactionAbstraction( client, tx, web3, body.signatureId, body.fromPrivateKey, body.fee?.gasLimit, body.fee?.gasPrice, ) } const auctionApproveErc20TransferSignedTransaction = async ( body: ApproveNftTransfer, web3: EvmBasedWeb3, provider?: string, ) => { const client = web3.getClient(provider) const amount = new BigNumber(body.amount) .multipliedBy( new BigNumber(10).pow( // TODO any type await new client.eth.Contract(Erc20Token.abi as any, body.contractAddress.trim()).methods .decimals() .call(), ), ) .toString(16) const params = [body.spender.trim(), `0x${amount}`] body.amount = '0' const methodName = 'approve' const methodAbi = MarketplaceSmartContract.abi.find((a) => a.name === methodName) as AbiItem const contract = new client.eth.Contract([methodAbi]) const tx: TransactionConfig = { from: 0, to: body.contractAddress.trim(), value: amount ? `0x${new BigNumber(toWei(amount, 'ether')).toString(16)}` : undefined, data: contract.methods[methodName as string](...params).encodeABI(), nonce: body.nonce, } return evmBasedUtils.prepareSignedTransactionAbstraction( client, tx, web3, body.signatureId, body.fromPrivateKey, body.fee?.gasLimit, body.fee?.gasPrice, ) } export type AuctionBid = FromPrivateKeyOrSignatureId> const auctionBidSignedTransaction = async ( body: AuctionBid, blockchain: EvmBasedBlockchain, broadcastFunction: BroadcastFunction, web3: EvmBasedWeb3, provider?: string, ) => { const client = web3.getClient(provider) const a = await new client.eth.Contract(MarketplaceSmartContract.abi as any, body.contractAddress).methods .getAuction(body.id) .call() let decimals = 18 let methodName = 'bid' let amount: string | undefined = undefined if (a[6] !== '0x0000000000000000000000000000000000000000') { decimals = await erc20({ blockchain, web3, broadcastFunction }).decimals(a[6], provider) if (body.bidder) { methodName = 'bidForExternalBidder' } } else if (body.bidder) { throw new Error('Bidder could be present only for ERC20 based auctions.') } else { amount = body.bidValue } if (!body.bidValue) { throw new Error('No budValue set') } const params = [ body.id, `0x${new BigNumber(body.bidValue).multipliedBy(new BigNumber(10).pow(decimals)).toString(16)}`, ] if (body.bidder) { params.push(body.bidder.trim()) } // TODO any type const contract = new client.eth.Contract([MarketplaceSmartContract.abi as any]) const tx: TransactionConfig = { from: 0, to: body.contractAddress.trim(), value: amount ? `0x${new BigNumber(client.utils.toWei(amount, 'ether')).toString(16)}` : undefined, data: contract.methods[methodName as string](...params).encodeABI(), nonce: body.nonce, } return evmBasedUtils.prepareSignedTransactionAbstraction( client, tx, web3, body.signatureId, body.fromPrivateKey, body.fee?.gasLimit, body.fee?.gasPrice, ) } export type SettleAuction = FromPrivateKeyOrSignatureId> & Partial export type CancelAuction = SettleAuction const auctionCancelSignedTransaction = async (body: CancelAuction, web3: EvmBasedWeb3, provider?: string) => { const client = web3.getClient(provider) const params = [body.id] // TODO any type const contract = new client.eth.Contract(MarketplaceSmartContract.abi as any, body.contractAddress.trim()) const data = contract.methods['cancelAuction']({ arguments: params }).encodeABI() const tx: TransactionConfig = { from: 0, to: body.contractAddress.trim(), value: body.amount ? `0x${new BigNumber(client.utils.toWei(body.amount, 'ether')).toString(16)}` : undefined, data, nonce: body.nonce, } return evmBasedUtils.prepareSignedTransactionAbstraction( client, tx, web3, body.signatureId, body.fromPrivateKey, body.fee?.gasLimit, body.fee?.gasPrice, ) } const auctionSettleSignedTransaction = async (body: SettleAuction, web3: EvmBasedWeb3, provider?: string) => { const client = web3.getClient(provider) const params = [body.id] const methodName = 'settleAuction' const methodAbi = MarketplaceSmartContract.abi.find((a) => a.name === methodName) // TODO any type const contract = new client.eth.Contract([methodAbi as any]) const tx: TransactionConfig = { from: 0, to: body.contractAddress.trim(), value: body.amount ? `0x${new BigNumber(client.utils.toWei(body.amount, 'ether')).toString(16)}` : undefined, data: contract.methods[methodName as string](...params).encodeABI(), nonce: body.nonce, } return evmBasedUtils.prepareSignedTransactionAbstraction( client, tx, web3, body.signatureId, body.fromPrivateKey, body.fee?.gasLimit, body.fee?.gasPrice, ) } export type CreateAuction = FromPrivateKeyOrSignatureId< WithoutChain> > & { contractAddress: string id: string nftAddress: string seller: string erc20Address?: string endedAt: number tokenId: string amount?: string isErc721: boolean nonce?: number } const createAuctionSignedTransaction = async ( body: CreateAuction, web3: EvmBasedWeb3, provider?: string, ): Promise => { const client = web3.getClient(provider) const params = [ body.id, body.isErc721, body.nftAddress.trim(), `0x${new BigNumber(body.tokenId).toString(16)}`, body.seller.trim(), `0x${new BigNumber(body.amount || 0).toString(16)}`, `0x${new BigNumber(body.endedAt).toString(16)}`, body.erc20Address || '0x0000000000000000000000000000000000000000', ] body.amount = undefined const methodName = 'createAuction' const methodAbi = MarketplaceSmartContract.abi.find((a) => a.name === methodName) // TODO any type const contract = new client.eth.Contract([methodAbi as any]) const tx: TransactionConfig = { from: 0, to: body.contractAddress.trim(), data: contract.methods[methodName as string](...params).encodeABI(), nonce: body.nonce, } return evmBasedUtils.prepareSignedTransactionAbstraction( client, tx, web3, body.signatureId, body.fromPrivateKey, body.fee?.gasLimit, body.fee?.gasPrice, ) } interface Amount { amount: string }