2020-04-01 10:18:28 +00:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
2020-05-08 09:54:42 +00:00
|
|
|
const Web3 = require('web3');
|
|
|
|
const parseArgs = require('minimist');
|
|
|
|
const fs = require('fs');
|
2020-05-13 11:23:17 +00:00
|
|
|
const utils = require('./utils.js');
|
2020-05-08 09:54:42 +00:00
|
|
|
const keccak256 = require('js-sha3').keccak256;
|
|
|
|
const BigNumber = require('bignumber.js');
|
2020-05-13 11:23:17 +00:00
|
|
|
const Account = require('./account.js');
|
2020-04-01 10:18:28 +00:00
|
|
|
|
2020-05-12 12:36:00 +00:00
|
|
|
const argv = parseArgs(process.argv.slice(2), {boolean: ["nft", "deploy-factory", "deploy-bucket"], string: ["sender", "factory", "bucket", "token"], default: {"endpoint": "ws://127.0.0.1:8546", "start-in-days": 0, "validity-days": 365, "max-tx-delay-blocks": 10}});
|
2020-04-01 10:18:28 +00:00
|
|
|
|
|
|
|
const web3 = new Web3(argv["endpoint"]);
|
2020-05-13 11:23:17 +00:00
|
|
|
const account = new Account(web3);
|
2020-04-01 10:18:28 +00:00
|
|
|
|
2020-04-28 09:36:34 +00:00
|
|
|
const classPrefix = argv["nft"] ? "NFT" : "ERC20";
|
2020-04-01 10:18:28 +00:00
|
|
|
|
2020-06-23 15:34:03 +00:00
|
|
|
const BucketFactoryCode = utils.loadContractCode(`${classPrefix}BucketFactory`);
|
|
|
|
const BucketFactory = utils.loadContract(web3, `${classPrefix}BucketFactory`);
|
2020-06-29 15:41:28 +00:00
|
|
|
const Bucket = utils.loadContract(web3, `${classPrefix}Bucket`);
|
2020-06-23 15:34:03 +00:00
|
|
|
const ERC721 = utils.loadContract(web3, "IERC721");
|
|
|
|
const ERC20 = utils.loadContract(web3, "IERC20Detailed");
|
2020-04-01 10:18:28 +00:00
|
|
|
|
2020-07-01 10:35:02 +00:00
|
|
|
const KECCAK_EMPTY_STRING = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470";
|
|
|
|
const KECCAK_EMPTY_STRING2 = web3.utils.sha3(KECCAK_EMPTY_STRING);
|
|
|
|
|
2020-05-13 11:23:17 +00:00
|
|
|
async function deployFactory() {
|
2020-09-23 15:32:54 +00:00
|
|
|
let methodCall = BucketFactory.deploy({data: BucketFactoryCode});
|
2020-05-13 11:23:17 +00:00
|
|
|
let receipt = await account.sendMethod(methodCall, null);
|
2020-05-07 11:59:58 +00:00
|
|
|
return receipt.contractAddress;
|
2020-04-01 10:18:28 +00:00
|
|
|
}
|
|
|
|
|
2020-05-13 11:23:17 +00:00
|
|
|
async function deployBucket(factory, token, startInDays, validityInDays, maxTxDelayBlocks) {
|
2020-05-07 11:59:58 +00:00
|
|
|
let now = Math.round(new Date().getTime() / 1000);
|
|
|
|
let startDate = now + (60 * 60 * 24 * startInDays);
|
|
|
|
let expirationDate = now + (60 * 60 * 24 * validityInDays);
|
|
|
|
|
|
|
|
BucketFactory.options.address = factory;
|
|
|
|
let methodCall = BucketFactory.methods.create(token.toLowerCase(), startDate, expirationDate, maxTxDelayBlocks);
|
|
|
|
|
|
|
|
try {
|
2020-05-13 11:23:17 +00:00
|
|
|
let receipt = await account.sendMethod(methodCall, BucketFactory.options.address);
|
2020-07-01 10:35:02 +00:00
|
|
|
let bucketAddress = receipt.logs[0].topics[2].slice(26);
|
|
|
|
return `0x${bucketAddress}`;
|
2020-05-07 11:59:58 +00:00
|
|
|
} catch(err) {
|
|
|
|
console.error(err);
|
|
|
|
return null;
|
|
|
|
}
|
2020-04-01 10:18:28 +00:00
|
|
|
}
|
|
|
|
|
2020-05-13 11:23:17 +00:00
|
|
|
async function createRedeemable(keycard) {
|
2020-07-01 10:35:02 +00:00
|
|
|
console.log("creating redeemable", keycard.keycard, keycard.amount)
|
2020-05-07 11:59:58 +00:00
|
|
|
let methodCall = Bucket.methods.createRedeemable(keycard.keycard, keycard.amount, keycard.code);
|
2020-04-13 09:08:37 +00:00
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
try {
|
2020-05-13 11:23:17 +00:00
|
|
|
let receipt = await account.sendMethod(methodCall, Bucket.options.address);
|
2020-05-07 11:59:58 +00:00
|
|
|
return receipt;
|
|
|
|
} catch(err) {
|
|
|
|
console.error(err);
|
|
|
|
return null;
|
|
|
|
}
|
2020-04-13 09:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function createNFTData(keycard, code) {
|
2020-05-07 11:59:58 +00:00
|
|
|
return keycard.toLowerCase() + code.replace("0x", "");
|
2020-04-13 09:08:37 +00:00
|
|
|
}
|
|
|
|
|
2020-05-13 11:23:17 +00:00
|
|
|
async function transferNFT(keycard) {
|
|
|
|
let methodCall = ERC721.methods.safeTransferFrom(account.senderAddress(), Bucket.options.address, keycard.amount, createNFTData(keycard.keycard, keycard.code));
|
2020-04-13 09:08:37 +00:00
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
try {
|
2020-05-13 11:23:17 +00:00
|
|
|
let receipt = await account.sendMethod(methodCall, ERC721.options.address);
|
2020-05-07 11:59:58 +00:00
|
|
|
return receipt;
|
|
|
|
} catch(err) {
|
|
|
|
console.error(err);
|
|
|
|
return null;
|
|
|
|
}
|
2020-04-01 10:18:28 +00:00
|
|
|
}
|
|
|
|
|
2020-05-06 10:01:14 +00:00
|
|
|
function processCode(code) {
|
2020-07-01 10:35:02 +00:00
|
|
|
if (code === "" || code === undefined) {
|
|
|
|
return KECCAK_EMPTY_STRING2;
|
|
|
|
}
|
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
if (!code.startsWith("0x")) {
|
|
|
|
code = "0x" + Buffer.from(code, 'utf8').toString('hex');
|
|
|
|
}
|
|
|
|
|
2020-07-01 10:35:02 +00:00
|
|
|
return "0x" + web3.utils.sha3(code);
|
2020-05-07 11:59:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function processAmount(amount, decimals) {
|
|
|
|
if (amount.startsWith("0x")) {
|
|
|
|
return amount;
|
|
|
|
} else {
|
|
|
|
return new BigNumber(amount).multipliedBy(Math.pow(10, decimals)).toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function processLine(line, decimals) {
|
|
|
|
let c = line.split(",").map((e) => e.toLowerCase().trim());
|
|
|
|
return {keycard: c[0], amount: processAmount(c[1], decimals), code: processCode(c[2])};
|
|
|
|
}
|
2020-05-06 10:01:14 +00:00
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
async function getToken(token, readIfMissing) {
|
2020-05-12 12:36:00 +00:00
|
|
|
return (token || !readIfMissing) ? token : await Bucket.methods.tokenAddress().call();
|
2020-05-06 10:01:14 +00:00
|
|
|
}
|
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
async function getDecimals(decimals, readIfMissing) {
|
|
|
|
if (decimals) {
|
|
|
|
return decimals
|
|
|
|
} else if (readIfMissing) {
|
|
|
|
return await ERC20.methods.decimals().call();
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
2020-04-01 10:18:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function run() {
|
2020-05-07 11:59:58 +00:00
|
|
|
BucketFactory.transactionConfirmationBlocks = 3;
|
|
|
|
Bucket.transactionConfirmationBlocks = 3;
|
|
|
|
ERC721.transactionConfirmationBlocks = 3;
|
|
|
|
|
|
|
|
let hasDoneSomething = false;
|
|
|
|
|
2020-05-13 11:23:17 +00:00
|
|
|
await account.init(argv);
|
2020-04-23 12:51:05 +00:00
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
let factory;
|
|
|
|
|
|
|
|
if (argv["deploy-factory"]) {
|
2020-05-13 11:23:17 +00:00
|
|
|
factory = await deployFactory();
|
2020-05-07 11:59:58 +00:00
|
|
|
hasDoneSomething = true;
|
|
|
|
console.log("Factory deployed at: " + factory);
|
|
|
|
} else {
|
|
|
|
factory = argv["factory"];
|
|
|
|
}
|
|
|
|
|
|
|
|
let bucket;
|
|
|
|
|
|
|
|
if (argv["deploy-bucket"]) {
|
|
|
|
if (!factory) {
|
|
|
|
console.error("the --factory or --deploy-factory option must be specified");
|
|
|
|
process.exit(1);
|
2020-04-01 10:18:28 +00:00
|
|
|
}
|
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
if (!argv["token"]) {
|
|
|
|
console.error("the --token option must be specified");
|
|
|
|
process.exit(1);
|
2020-04-01 10:18:28 +00:00
|
|
|
}
|
2020-04-23 12:51:05 +00:00
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
if (!argv["validity-days"]) {
|
|
|
|
console.error("the --validity-days option must be specified");
|
|
|
|
process.exit(1);
|
2020-04-01 10:18:28 +00:00
|
|
|
}
|
|
|
|
|
2020-05-13 11:23:17 +00:00
|
|
|
bucket = await deployBucket(factory, argv["token"], argv["start-in-days"], argv["validity-days"], argv["max-tx-delay-blocks"]);
|
2020-07-01 10:35:02 +00:00
|
|
|
|
|
|
|
if (argv["relayer-uri"] !== undefined && argv["relayer-uri"] !== "") {
|
|
|
|
const uri = argv["relayer-uri"];
|
|
|
|
Bucket.options.address = bucket;
|
|
|
|
console.log("setting relayer URI to ", uri);
|
|
|
|
let setRelayerURI = Bucket.methods.setRelayerURI(uri);
|
|
|
|
await account.sendMethod(setRelayerURI, bucket);
|
|
|
|
}
|
|
|
|
|
2020-05-07 11:59:58 +00:00
|
|
|
hasDoneSomething = true;
|
|
|
|
console.log("Bucket deployed at: " + bucket);
|
|
|
|
} else {
|
|
|
|
bucket = argv["bucket"];
|
|
|
|
}
|
|
|
|
|
|
|
|
Bucket.options.address = bucket;
|
|
|
|
|
|
|
|
let keycards;
|
|
|
|
|
|
|
|
if (argv["file"]) {
|
|
|
|
const token = await getToken(argv["token"], (argv["nft"] || !argv["amount-decimals"]));
|
|
|
|
|
|
|
|
ERC721.options.address = token;
|
|
|
|
ERC20.options.address = token;
|
|
|
|
|
|
|
|
const decimals = await getDecimals(argv["amount-decimals"], !argv["nft"]);
|
|
|
|
|
|
|
|
let file = fs.readFileSync(argv["file"], 'utf8');
|
2020-07-01 16:18:03 +00:00
|
|
|
keycards = file
|
|
|
|
.split("\n")
|
|
|
|
.filter(line => line.trim() !== "" && !/^#/.test(line.trim()))
|
|
|
|
.map((line) => processLine(line, decimals));
|
2020-05-07 11:59:58 +00:00
|
|
|
|
|
|
|
for (let keycard of keycards) {
|
2020-07-01 10:35:02 +00:00
|
|
|
const create = argv["nft"] ? transferNFT : createRedeemable;
|
|
|
|
await create(keycard);
|
2020-09-23 15:32:54 +00:00
|
|
|
console.log(`http://localhost:3000/redeem/#/buckets/${bucket}/redeemables/${keycard.keycard}`)
|
2020-05-07 11:59:58 +00:00
|
|
|
}
|
|
|
|
} else if (!hasDoneSomething) {
|
|
|
|
console.error("the --file option must be specified");
|
2020-04-01 10:18:28 +00:00
|
|
|
process.exit(0);
|
2020-05-07 11:59:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
process.exit(0);
|
2020-04-01 10:18:28 +00:00
|
|
|
}
|
|
|
|
|
2020-04-29 22:34:57 +00:00
|
|
|
run();
|