Skip to content

Commit

Permalink
OnCyberMultiSender (oncyberio#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
julesGoullee authored Aug 16, 2022
1 parent 1038d20 commit 1f18d45
Show file tree
Hide file tree
Showing 34 changed files with 6,671 additions and 5,447 deletions.
12 changes: 12 additions & 0 deletions .run/scripts_multiSend_localhost.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="scripts:multiSend:localhost" type="js.build_tools.npm" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/packages/contracts/package.json" />
<command value="run" />
<scripts>
<script value="scripts:multiSend:localhost" />
</scripts>
<node-interpreter value="project" />
<envs />
<method v="2" />
</configuration>
</component>
19 changes: 19 additions & 0 deletions packages/contracts/contracts/OnCyberMultiSender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import '@solidstate/contracts/token/ERC1155/IERC1155.sol';

//import 'hardhat/console.sol';

contract OnCyberMultiSender {
function transfer(
IERC1155 _token,
uint256 id,
address[] calldata _receivers,
uint256[] calldata _quantities
) external {
require(_receivers.length == _quantities.length, 'OnCyberMultiSender: receivers and quantities length mismatch');
for (uint256 i = 0; i < _receivers.length; i++) {
_token.safeTransferFrom(msg.sender, _receivers[i], id, _quantities[i], '');
}
}
}
14 changes: 14 additions & 0 deletions packages/contracts/deploy/005OnCyberMultiSender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DeployFunction } from 'hardhat-deploy/types'
import { HardhatRuntimeEnvironment } from 'hardhat/types'

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre

const namedAccounts = await getNamedAccounts()
await deployments.deploy('OnCyberMultiSender', {
from: namedAccounts.deployer,
log: true,
})
}
export default func
func.tags = ['OnCyberMultiSender']
1 change: 1 addition & 0 deletions packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const config: HardhatUserConfig = {
// accounts: [
// defaultPrivateKey,
// ]
gasPrice: parseUnits('10', 'gwei').toNumber(),
},
// ETHEREUM
rinkeby: {
Expand Down
4 changes: 4 additions & 0 deletions packages/contracts/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BigNumber, Signer, utils } from 'ethers'
import { formatEther } from 'ethers/lib/utils'

export const tokenURI = (uri: string) => `ipfs://${uri}`

Expand Down Expand Up @@ -47,3 +48,6 @@ export async function signMintRequest(

return signer.signMessage(pHash)
}

export const displayAmount = (amount: BigNumber, decimal = 2): string =>
parseFloat(formatEther(amount)).toFixed(decimal)
5 changes: 3 additions & 2 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@oncyber-scenes/contracts",
"name": "@oncyber/contracts",
"version": "1.0.0",
"description": "",
"main": "index.js",
Expand Down Expand Up @@ -36,7 +36,8 @@
"scripts:createDrop:rinkeby": "hardhat run scripts/createDrop.ts --network rinkeby",
"scripts:mint:rinkeby": "hardhat run scripts/mint.ts --network rinkeby",
"scripts:createDrop:localhost": "hardhat run scripts/createDrop.ts --network localhost",
"scripts:mint:localhost": "hardhat run scripts/mint.ts --network localhost"
"scripts:mint:localhost": "hardhat run scripts/mint.ts --network localhost",
"scripts:multiSend:localhost": "hardhat run scripts/multiSend --network localhost"
},
"author": "oncyber",
"license": "MIT",
Expand Down
15 changes: 9 additions & 6 deletions packages/contracts/scripts/createDrop.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { BigNumber } from 'ethers'
import { parseEther } from 'ethers/lib/utils'
import { deployments, ethers } from 'hardhat'
import { config, deployments, ethers, network } from 'hardhat'
import { Log } from 'hardhat-deploy/dist/types'
import { signCreateDropRequest } from '../lib/utils'

async function main() {
const contractName = 'DiamondCyberDestinationFactory'
const contractName = 'DiamondOnCyberAndFriendsFactory'

const accounts = await ethers.getSigners()
const manager = accounts[2]
const manager = accounts[3]
const minter = accounts[0]

const Contract = await deployments.get(contractName)
Expand All @@ -16,8 +17,8 @@ async function main() {
const uri = 'QmQwfto3zFsasHnvNpyKW7jZVVkAgxpLAKfxQhTbnykHh8'
const timeStart = parseInt((Date.now() / 1000).toString())
const timeEnd = parseInt((Date.now() / 1000 + 100000000).toString())
const price = parseEther('1')
const amountCap = 100
const price = parseEther('0')
const amountCap = 10000
const shareCyber = 50
const nonce = await contract.minterNonce(minter.address)
const signature = await signCreateDropRequest(
Expand All @@ -31,7 +32,9 @@ async function main() {
nonce,
manager
)
const tx = await contract.createDrop(uri, timeStart, timeEnd, price, amountCap, shareCyber, signature)
const tx = await contract.createDrop(uri, timeStart, timeEnd, price, amountCap, shareCyber, signature, {
gasPrice: BigNumber.from(config.networks[network.name].gasPrice),
})
const txReceipt = await tx.wait()
const iFace = new ethers.utils.Interface(Contract.abi)
let tokenId = null
Expand Down
8 changes: 6 additions & 2 deletions packages/contracts/scripts/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { deployments, ethers, getNamedAccounts } from 'hardhat'
import { BigNumber } from 'ethers'
import { config, deployments, ethers, getNamedAccounts, network } from 'hardhat'

async function main() {
const contractName = 'DiamondCyberDestinationUtilityFactory'
Expand All @@ -14,7 +15,10 @@ async function main() {
namedAccounts.managerDestinationUtility,
namedAccounts.biconomyForwarder,
namedAccounts.opensea,
namedAccounts.oncyber
namedAccounts.oncyber,
{
gasPrice: BigNumber.from(config.networks[network.name].gasPrice),
}
)
console.log('tx', tx)
}
Expand Down
20 changes: 11 additions & 9 deletions packages/contracts/scripts/mint.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
// @ts-ignore-next-line
import { deployments, ethers } from 'hardhat'
import { BigNumber } from 'ethers'
import { config, deployments, ethers, network } from 'hardhat'
import { signMintRequest } from '../lib/utils'

async function main() {
const contractName = 'DiamondCyberDestinationFactory'
const contractName = 'DiamondOnCyberAndFriendsFactory'

const accounts = await ethers.getSigners()
const minter = accounts[3]
const manager = accounts[2]
const minter = accounts[5]
const manager = accounts[3]

const Contract = await deployments.get(contractName)
const contract = await ethers.getContractAt(Contract.abi, Contract.address, minter)
const tokenId = 0
const quantity = 1
const mintPrice = await contract.getMintPriceForToken(tokenId)
const quantity = 10000
const mintPrice = 0

const signatureMint = await signMintRequest(tokenId, quantity, minter.address, 0, manager)

const estimation = await contract.estimateGas.mint(tokenId, signatureMint, {
const estimation = await contract.estimateGas.mint(tokenId, quantity, signatureMint, {
gasPrice: BigNumber.from(config.networks[network.name].gasPrice),
value: mintPrice,
})

const tx = await contract.mint(tokenId, signatureMint, {
const tx = await contract.mint(tokenId, quantity, signatureMint, {
value: mintPrice,
gasPrice: BigNumber.from(config.networks[network.name].gasPrice),
gasLimit: estimation.mul(100).div(90),
})

Expand Down
149 changes: 149 additions & 0 deletions packages/contracts/scripts/multiSend/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import assert from 'assert'
import { BigNumber } from 'ethers'
import { getAddress } from 'ethers/lib/utils'
import * as fs from 'fs'
import { config, deployments, ethers, network } from 'hardhat'
import path from 'path'
import { displayAmount } from '../../lib/utils'
import receiversData from './receiversData.json'

function chunkArray(array: any[], chunkSize: number): any[] {
const chunks = []
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize))
}
return chunks
}

async function main() {
const receiversDataPath = path.join(__dirname, './receiversData.json')
const contractName = 'DiamondOnCyberAndFriendsFactory'
const batchSize = 50
const tokenId = 0

const accounts = await ethers.getSigners()
const sender = accounts[5]

const Contract = await deployments.get(contractName)
const contract = await ethers.getContractAt(Contract.abi, Contract.address, sender)
const onCyberMultiSenderDeployment = await deployments.get('OnCyberMultiSender')
const onCyberMultiSender = await ethers.getContractAt(
onCyberMultiSenderDeployment.abi,
onCyberMultiSenderDeployment.address,
sender
)
const receiversDataRemains = receiversData.filter(
(receiverData: any) => !['pending', 'done'].includes(receiverData.status)
)
const quantityRemains = receiversDataRemains.reduce((acc, receiverDataRemains) => {
acc += receiverDataRemains.quantity
return acc
}, 0)
const senderBalance = await contract.balanceOf(sender.address, tokenId)
const senderNativeBalance = await ethers.provider.getBalance(sender.address)
assert(
senderBalance.gte(quantityRemains),
`Sender balance ${senderBalance.toString()} not enough to send ${quantityRemains.toString()}`
)
const remainsAddresses = receiversDataRemains.map((receiverDataRemains: any) => receiverDataRemains.address)
assert(Array.from(new Set(remainsAddresses)).length === remainsAddresses.length, 'Receivers address duplicated')
assert(
!receiversDataRemains.find(
(receiverDataRemains: any) => receiverDataRemains.address !== getAddress(receiverDataRemains.address)
),
'Address incorrect'
)
console.info(
`Sender ${
sender.address
} balance token ${senderBalance.toString()} quantity remains to send ${quantityRemains} native balance ${displayAmount(
senderNativeBalance
)}`
)

const isApproved = await contract.isApprovedForAll(sender.address, onCyberMultiSender.address)
if (!isApproved) {
console.info('Approve multi sender')
const tx = await contract.setApprovalForAll(onCyberMultiSender.address, true, {
gasPrice: BigNumber.from(config.networks[network.name].gasPrice),
})
console.info(`Tx approval pending ${tx.hash}`)
await tx.wait()
console.info(`Tx approval done ${tx.hash}`)
} else {
console.info('Multi sender is approved')
}

console.info(
`Start multi send ${contractName} token id ${tokenId} remains ${receiversDataRemains.length}/${receiversData.length} receivers`
)

const receiversDataRemainsChunks = chunkArray(receiversDataRemains, batchSize)
console.info(`Receivers remains chunks ${receiversDataRemainsChunks.length}`)

for (let i = 0; i < receiversDataRemainsChunks.length; i++) {
const receivers = receiversDataRemainsChunks[i].map(
(receiversDataRemainsChunk: any) => receiversDataRemainsChunk.address
)
const quantities = receiversDataRemainsChunks[i].map(
(receiversDataRemainsChunk: any) => receiversDataRemainsChunk.quantity
)
const senderNativeBalanceBatch = await ethers.provider.getBalance(sender.address)
console.info(
`Sending batch ${i + 1}/${receiversDataRemainsChunks.length} sender native balance ${displayAmount(
senderNativeBalanceBatch
)}`
)
const tx = await onCyberMultiSender.transfer(contract.address, tokenId, receivers, quantities, {
gasPrice: BigNumber.from(config.networks[network.name].gasPrice),
})
console.info(`Tx multi send batch ${i + 1} pending ${tx.hash}`)
receiversDataRemainsChunks[i].forEach((receiversDataRemainsChunk: any) => {
const receiverData: any = receiversData.find(
(receiverData: any) => receiverData.address === receiversDataRemainsChunk.address
)
if (!receiverData) {
throw new Error('Invalid receiver data')
}
receiverData.status = 'pending'
})
fs.writeFileSync(receiversDataPath, JSON.stringify(receiversData, null, 2))
const receipt = await tx.wait()

console.info(`Tx multi send batch ${i + 1} done ${tx.hash}`)
receiversDataRemainsChunks[i].forEach((receiversDataRemainsChunk: any) => {
const receiverData: any = receiversData.find(
(receiverData: any) => receiverData.address === receiversDataRemainsChunk.address
)
if (!receiverData) {
throw new Error('Invalid receiver data')
}
receiverData.status = 'done'
})
fs.writeFileSync(receiversDataPath, JSON.stringify(receiversData, null, 2))

const senderNativeBalanceBatchEnd = await ethers.provider.getBalance(sender.address)
console.info(
`End batch ${i + 1} sender native balance ${displayAmount(
senderNativeBalanceBatchEnd
)} gas used ${receipt.gasUsed.toString()} cost ${displayAmount(
senderNativeBalanceBatch.sub(senderNativeBalanceBatchEnd),
4
)}`
)
}
const senderNativeBalanceEnd = await ethers.provider.getBalance(sender.address)
console.info(
`Multi send end sender native balance ${displayAmount(senderNativeBalanceEnd)} cost ${displayAmount(
senderNativeBalance.sub(senderNativeBalanceEnd),
4
)}`
)
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})
Loading

0 comments on commit 1f18d45

Please sign in to comment.