1import {ApiPromise} from '@polkadot/api';2import {IKeyringPair} from '@polkadot/types/types';3import Web3 from 'web3';4import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, recordEthFee, usingWeb3} from './eth/util/helpers';5import usingApi, {executeTransaction} from './substrate/substrate-api';6import {createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, UNIQUE, waitNewBlocks} from './util/helpers';7import nonFungibleAbi from './eth/nonFungibleAbi.json';89function linearRegression(points: { x: bigint, y: bigint }[]) {10 let sumxy = 0n;11 let sumx = 0n;12 let sumy = 0n;13 let sumx2 = 0n;14 const n = points.length;15 for (let i = 0; i < n; i++) {16 const p = points[i];17 sumxy += p.x * p.y;18 sumx += p.x;19 sumy += p.y;20 sumx2 += p.x * p.x;21 }2223 const nb = BigInt(n);2425 const a = (nb * sumxy - sumx * sumy) / (nb * sumx2 - sumx * sumx);26 const b = (sumy - a * sumx) / nb;2728 return {a, b};29}30313233function sqrt(value: bigint) {34 if (value < 0n) {35 throw 'square root of negative numbers is not supported';36 }3738 if (value < 2n) {39 return value;40 }4142 function newtonIteration(n: bigint, x0: bigint): bigint {43 const x1 = ((n / x0) + x0) >> 1n;44 if (x0 === x1 || x0 === (x1 - 1n)) {45 return x0;46 }47 return newtonIteration(n, x1);48 }4950 return newtonIteration(value, 1n);51}5253function _error(points: { x: bigint, y: bigint }[], hypothesis: (a: bigint) => bigint) {54 return sqrt(points.map(p => {55 const v = hypothesis(p.x);56 const vv = p.y;5758 return (v - vv) ** 2n;59 }).reduce((a, b) => a + b, 0n) / BigInt(points.length));60}6162async function calibrateWeightToFee(api: ApiPromise, privateKey: (account: string) => IKeyringPair) {63 const alice = privateKey('//Alice');64 const bob = privateKey('//Bob');65 const dataPoints = [];6667 {68 const collectionId = await createCollectionExpectSuccess();69 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');70 const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt();71 await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT');72 const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt();7374 console.log(`Original price: ${Number(aliceBalanceBefore - aliceBalanceAfter) / Number(UNIQUE)} UNQ`);75 }7677 const defaultCoeff = (api.consts.configuration.defaultWeightToFeeCoefficient as any).toBigInt();78 for (let i = -5; i < 5; i++) {79 await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(defaultCoeff + defaultCoeff / 1000n * BigInt(i))));8081 const coefficient = (await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt();82 const collectionId = await createCollectionExpectSuccess();83 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');8485 const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt();86 await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT');87 const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt();8889 const transferPrice = aliceBalanceBefore - aliceBalanceAfter;9091 dataPoints.push({x: transferPrice, y: coefficient});92 }93 const {a, b} = linearRegression(dataPoints);9495 9697 const perfectValue = a * UNIQUE / 10n + b;98 await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(perfectValue.toString())));99100 {101 const collectionId = await createCollectionExpectSuccess();102 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');103 const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt();104 await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT');105 const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt();106107 console.log(`Calibrated price: ${Number(aliceBalanceBefore - aliceBalanceAfter) / Number(UNIQUE)} UNQ`);108 }109}110111async function calibrateMinGasPrice(api: ApiPromise, web3: Web3, privateKey: (account: string) => IKeyringPair) {112 const alice = privateKey('//Alice');113 const caller = await createEthAccountWithBalance(api, web3, privateKey);114 const receiver = createEthAccount(web3);115 const dataPoints = [];116117 {118 const collectionId = await createCollectionExpectSuccess();119 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller});120121 const address = collectionIdToAddress(collectionId);122 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});123124 const cost = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller));125126 console.log(`Original price: ${Number(cost) / Number(UNIQUE)} UNQ`);127 }128129 const defaultCoeff = (api.consts.configuration.defaultMinGasPrice as any).toBigInt();130 for (let i = -8; i < 8; i++) {131 const gasPrice = defaultCoeff + defaultCoeff / 100000n * BigInt(i);132 const gasPriceStr = '0x' + gasPrice.toString(16);133 await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(gasPrice)));134135 const coefficient = (await api.query.configuration.minGasPriceOverride() as any).toBigInt();136 const collectionId = await createCollectionExpectSuccess();137 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller});138139 const address = collectionIdToAddress(collectionId);140 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, gasPrice: gasPriceStr, ...GAS_ARGS});141142 const transferPrice = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller));143144 dataPoints.push({x: transferPrice, y: coefficient});145 }146147 const {a, b} = linearRegression(dataPoints);148149 150151 152 const perfectValue = a * UNIQUE * 1000000n / 6666666n + b;153 await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(perfectValue.toString())));154155 {156 const collectionId = await createCollectionExpectSuccess();157 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller});158159 const address = collectionIdToAddress(collectionId);160 const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});161162 const cost = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller));163164 console.log(`Calibrated price: ${Number(cost) / Number(UNIQUE)} UNQ`);165 }166}167168(async () => {169 await usingApi(async (api, privateKey) => {170 171172 await calibrateWeightToFee(api, privateKey);173 await calibrateWeightToFee(api, privateKey);174175 await usingWeb3(async web3 => {176 await calibrateMinGasPrice(api, web3, privateKey);177 await calibrateMinGasPrice(api, web3, privateKey);178 });179180 await api.disconnect();181 });182})();