diff --git a/text/Assets/web3/sales/group/en.json b/text/Assets/web3/sales/group/en.json
new file mode 100644
index 0000000..617e00d
--- /dev/null
+++ b/text/Assets/web3/sales/group/en.json
@@ -0,0 +1,19 @@
+{
+ "sales": {
+ "group": {
+ "errors": {
+ "AddressInvalid":"Address invalid",
+ "GroupNameInvalid":"Group name invalid"
+ },
+ "titles": {
+ "AddtoWhitelist": "Add to Whitelist",
+ "SetGroup":"Set Group",
+ "OnWhitelist":"On whitelist",
+ "NotYetOnWhitelist":"Not yet on whitelist"
+ },
+ "placeholder": {
+ "ExactGroupName": "Exact Group Name"
+ }
+ }
+ }
+}
diff --git a/text/Assets/web3/sales/main/en.json b/text/Assets/web3/sales/main/en.json
new file mode 100644
index 0000000..8ec5148
--- /dev/null
+++ b/text/Assets/web3/sales/main/en.json
@@ -0,0 +1,20 @@
+{
+ "sales": {
+ "main": {
+ "test": "test",
+ "placeholder": {
+ "amountInUSD": "Amount in USD"
+ },
+
+ "titles": {
+ "condition1":"Condition, tokens",
+ "bonus":"Bonus, %",
+ "condition2":"Condition",
+ "price":"Price, $",
+ "buy": "Buy",
+ "bonuses": "See Bonuses",
+ "prices": "See Prices"
+ }
+ }
+ }
+}
diff --git a/views/Assets/templates/Uniswap/V2/Pair.abi.json b/views/Assets/templates/Uniswap/V2/Pair.abi.json
new file mode 100644
index 0000000..8632967
--- /dev/null
+++ b/views/Assets/templates/Uniswap/V2/Pair.abi.json
@@ -0,0 +1,755 @@
+[
+ {
+ "inputs":[
+
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"constructor"
+ },
+ {
+ "anonymous":false,
+ "inputs":[
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"owner",
+ "type":"address"
+ },
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"spender",
+ "type":"address"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"value",
+ "type":"uint256"
+ }
+ ],
+ "name":"Approval",
+ "type":"event"
+ },
+ {
+ "anonymous":false,
+ "inputs":[
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"sender",
+ "type":"address"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"amount0",
+ "type":"uint256"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"amount1",
+ "type":"uint256"
+ },
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ }
+ ],
+ "name":"Burn",
+ "type":"event"
+ },
+ {
+ "anonymous":false,
+ "inputs":[
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"sender",
+ "type":"address"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"amount0",
+ "type":"uint256"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"amount1",
+ "type":"uint256"
+ }
+ ],
+ "name":"Mint",
+ "type":"event"
+ },
+ {
+ "anonymous":false,
+ "inputs":[
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"sender",
+ "type":"address"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"amount0In",
+ "type":"uint256"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"amount1In",
+ "type":"uint256"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"amount0Out",
+ "type":"uint256"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"amount1Out",
+ "type":"uint256"
+ },
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ }
+ ],
+ "name":"Swap",
+ "type":"event"
+ },
+ {
+ "anonymous":false,
+ "inputs":[
+ {
+ "indexed":false,
+ "internalType":"uint112",
+ "name":"reserve0",
+ "type":"uint112"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint112",
+ "name":"reserve1",
+ "type":"uint112"
+ }
+ ],
+ "name":"Sync",
+ "type":"event"
+ },
+ {
+ "anonymous":false,
+ "inputs":[
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"from",
+ "type":"address"
+ },
+ {
+ "indexed":true,
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ },
+ {
+ "indexed":false,
+ "internalType":"uint256",
+ "name":"value",
+ "type":"uint256"
+ }
+ ],
+ "name":"Transfer",
+ "type":"event"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"DOMAIN_SEPARATOR",
+ "outputs":[
+ {
+ "internalType":"bytes32",
+ "name":"",
+ "type":"bytes32"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"MINIMUM_LIQUIDITY",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"PERMIT_TYPEHASH",
+ "outputs":[
+ {
+ "internalType":"bytes32",
+ "name":"",
+ "type":"bytes32"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"",
+ "type":"address"
+ },
+ {
+ "internalType":"address",
+ "name":"",
+ "type":"address"
+ }
+ ],
+ "name":"allowance",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"spender",
+ "type":"address"
+ },
+ {
+ "internalType":"uint256",
+ "name":"value",
+ "type":"uint256"
+ }
+ ],
+ "name":"approve",
+ "outputs":[
+ {
+ "internalType":"bool",
+ "name":"",
+ "type":"bool"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"",
+ "type":"address"
+ }
+ ],
+ "name":"balanceOf",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ }
+ ],
+ "name":"burn",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"amount0",
+ "type":"uint256"
+ },
+ {
+ "internalType":"uint256",
+ "name":"amount1",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"decimals",
+ "outputs":[
+ {
+ "internalType":"uint8",
+ "name":"",
+ "type":"uint8"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"factory",
+ "outputs":[
+ {
+ "internalType":"address",
+ "name":"",
+ "type":"address"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"getReserves",
+ "outputs":[
+ {
+ "internalType":"uint112",
+ "name":"_reserve0",
+ "type":"uint112"
+ },
+ {
+ "internalType":"uint112",
+ "name":"_reserve1",
+ "type":"uint112"
+ },
+ {
+ "internalType":"uint32",
+ "name":"_blockTimestampLast",
+ "type":"uint32"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"_token0",
+ "type":"address"
+ },
+ {
+ "internalType":"address",
+ "name":"_token1",
+ "type":"address"
+ }
+ ],
+ "name":"initialize",
+ "outputs":[
+
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"kLast",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ }
+ ],
+ "name":"mint",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"liquidity",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"name",
+ "outputs":[
+ {
+ "internalType":"string",
+ "name":"",
+ "type":"string"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"",
+ "type":"address"
+ }
+ ],
+ "name":"nonces",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"owner",
+ "type":"address"
+ },
+ {
+ "internalType":"address",
+ "name":"spender",
+ "type":"address"
+ },
+ {
+ "internalType":"uint256",
+ "name":"value",
+ "type":"uint256"
+ },
+ {
+ "internalType":"uint256",
+ "name":"deadline",
+ "type":"uint256"
+ },
+ {
+ "internalType":"uint8",
+ "name":"v",
+ "type":"uint8"
+ },
+ {
+ "internalType":"bytes32",
+ "name":"r",
+ "type":"bytes32"
+ },
+ {
+ "internalType":"bytes32",
+ "name":"s",
+ "type":"bytes32"
+ }
+ ],
+ "name":"permit",
+ "outputs":[
+
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"price0CumulativeLast",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"price1CumulativeLast",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ }
+ ],
+ "name":"skim",
+ "outputs":[
+
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"uint256",
+ "name":"amount0Out",
+ "type":"uint256"
+ },
+ {
+ "internalType":"uint256",
+ "name":"amount1Out",
+ "type":"uint256"
+ },
+ {
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ },
+ {
+ "internalType":"bytes",
+ "name":"data",
+ "type":"bytes"
+ }
+ ],
+ "name":"swap",
+ "outputs":[
+
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"symbol",
+ "outputs":[
+ {
+ "internalType":"string",
+ "name":"",
+ "type":"string"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+
+ ],
+ "name":"sync",
+ "outputs":[
+
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"token0",
+ "outputs":[
+ {
+ "internalType":"address",
+ "name":"",
+ "type":"address"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"token1",
+ "outputs":[
+ {
+ "internalType":"address",
+ "name":"",
+ "type":"address"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":true,
+ "inputs":[
+
+ ],
+ "name":"totalSupply",
+ "outputs":[
+ {
+ "internalType":"uint256",
+ "name":"",
+ "type":"uint256"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"view",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ },
+ {
+ "internalType":"uint256",
+ "name":"value",
+ "type":"uint256"
+ }
+ ],
+ "name":"transfer",
+ "outputs":[
+ {
+ "internalType":"bool",
+ "name":"",
+ "type":"bool"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ },
+ {
+ "constant":false,
+ "inputs":[
+ {
+ "internalType":"address",
+ "name":"from",
+ "type":"address"
+ },
+ {
+ "internalType":"address",
+ "name":"to",
+ "type":"address"
+ },
+ {
+ "internalType":"uint256",
+ "name":"value",
+ "type":"uint256"
+ }
+ ],
+ "name":"transferFrom",
+ "outputs":[
+ {
+ "internalType":"bool",
+ "name":"",
+ "type":"bool"
+ }
+ ],
+ "payable":false,
+ "stateMutability":"nonpayable",
+ "type":"function"
+ }
+]
\ No newline at end of file
diff --git a/web/css/tools/web3/sales/main.css b/web/css/tools/web3/sales/main.css
new file mode 100644
index 0000000..6383a03
--- /dev/null
+++ b/web/css/tools/web3/sales/main.css
@@ -0,0 +1,3 @@
+.Assets_web3_sales_main_prices, .Assets_web3_sales_main_bonuses {
+ display:block;
+}
\ No newline at end of file
diff --git a/web/js/Assets.js b/web/js/Assets.js
index 4d3a46a..49f5722 100644
--- a/web/js/Assets.js
+++ b/web/js/Assets.js
@@ -401,6 +401,7 @@ var Assets = Q.Assets = Q.plugins.Assets = {
getAll: new Q.Method(),
getFundConfig: new Q.Method(),
getWhitelisted: new Q.Method(),
+ getOwner: new Q.Method(),
adjustFundConfig: function(infoConfig, options) {
//make output data an userfriendly
var infoConfigAdjusted = Object.assign({}, infoConfig);
@@ -408,9 +409,11 @@ var Assets = Q.Assets = Q.plugins.Assets = {
infoConfigAdjusted._endTs = new Date(parseInt(infoConfig._endTs) * 1000).toDateString();
infoConfigAdjusted._prices = infoConfig._prices.map(
x => ethers.utils.formatUnits(
- x.toString(),
+ (x/100).toString(), //div to 100 to get $ instead cents
Q.isEmpty(options.priceDenom)?18:Math.log10(options.priceDenom)
));
+
+ infoConfigAdjusted._amountRaised = infoConfig._amountRaised.map(x => Math.trunc(ethers.utils.formatUnits(x.toString(), 18)));
infoConfigAdjusted._thresholds = infoConfig._thresholds.map(x => ethers.utils.formatUnits(x.toString(), 18));
infoConfigAdjusted._timestamps = infoConfig._timestamps.map(x => new Date(parseInt(x) * 1000).toDateString());
@@ -522,6 +525,7 @@ var Assets = Q.Assets = Q.plugins.Assets = {
}}
}
}, '{{Assets}}/js/methods/Assets/Web3'),
+
};
@@ -656,6 +660,15 @@ Q.Tool.define({
js: "{{Assets}}/js/tools/web3/coin/admin.js",
css: ["{{Assets}}/css/tools/web3/coin/admin.css", "{{Q}}/css/bootstrap-custom/bootstrap.css"]
},
+ "Assets/web3/sales/main": {
+ js: "{{Assets}}/js/tools/web3/sales/main.js",
+ css: ["{{Assets}}/css/tools/web3/sales/main.css"],
+ text: ["Assets/content", "Assets/web3/sales/main"]
+ },
+ "Assets/web3/sales/group": {
+ js: "{{Assets}}/js/tools/web3/sales/group.js",
+ text: ["Assets/content", "Assets/web3/sales/group"]
+ },
"Assets/web3/coin/staking/start": {
js: "{{Assets}}/js/tools/web3/coin/staking/start.js",
css: ["{{Assets}}/css/tools/web3/coin/staking/start.css", "{{Q}}/css/bootstrap-custom/bootstrap.css"],
diff --git a/web/js/methods/Assets/Funds/getOwner.js b/web/js/methods/Assets/Funds/getOwner.js
new file mode 100644
index 0000000..c00a2f2
--- /dev/null
+++ b/web/js/methods/Assets/Funds/getOwner.js
@@ -0,0 +1,27 @@
+Q.exports(function () {
+ /**
+ * get contract owner address
+ * @param {type} contractAddress
+ * @param {type} chainId
+ * @param {type} callback
+ */
+ return function getOwner(contractAddress, chainId, callback){
+ return Q.Users.Web3.getContract(
+ 'Assets/templates/R1/Sales/contract',
+ {
+ contractAddress: contractAddress,
+ readOnly: true,
+ chainId: chainId
+ }
+ ).then(function (contract) {
+ return contract.owner();
+ }).then(function (ret) {
+ Q.handle(callback, null, [null, ret]);
+ return ret;
+ }).catch(function(err){
+ Q.handle(callback, null, [err]);
+ console.warn(err);
+ });
+ }
+
+});
\ No newline at end of file
diff --git a/web/js/tools/web3/sales/group.js b/web/js/tools/web3/sales/group.js
new file mode 100644
index 0000000..621b297
--- /dev/null
+++ b/web/js/tools/web3/sales/group.js
@@ -0,0 +1,295 @@
+(function (window, Q, $, undefined) {
+
+ /**
+ * @module Assets
+ */
+ var Assets = Q.Assets;
+
+ /**
+ * Sales
+ * @class Assets Web3 Sales Group
+ * @constructor
+ * @param {Object} options Override various options for this tool
+ * @param {String} [options.salesContractAddress] address of Sales contract
+ * @param {String} [options.chainId] chainId
+ * @param {String} [options.abiPath] ABI path for SalesWithStablePrice contract
+ */
+ Q.Tool.define("Assets/web3/sales/group", function (options) {
+ var tool = this;
+ var state = this.state;
+ state.choosen = {};
+ tool.loggedInUserXid = Q.Users.Web3.getLoggedInUserXid();
+
+ tool.refresh();
+
+ },
+
+ { // default options here
+ salesContractAddress: null,
+ chainId: null,
+ abiPath: 'Assets/templates/R1/Sales/contract'
+ },
+
+ { // methods go here
+ refresh: function () {
+
+ var tool = this;
+ var state = tool.state;
+
+ Q.Template.render("Assets/web3/sales/group/preloader", {
+ src: Q.url("{{Q}}/img/throbbers/loading.gif")
+ }, function (err, html) {
+ Q.replace(tool.element, html);
+ });
+
+ var p = [];
+ p.push(Q.Assets.Funds.getWhitelisted(state.salesContractAddress, tool.loggedInUserXid, state.chainId));
+ p.push(Q.Assets.Funds.getOwner(state.salesContractAddress, state.chainId));
+
+ Promise.allSettled(p).then(function(_ref){
+
+ if (_ref[0].status == 'fulfilled' && _ref[1].status == 'fulfilled') {
+
+ Q.Template.render("Assets/web3/sales/group", {
+ isOwner: _ref[1].value.toLowerCase() == tool.loggedInUserXid.toLowerCase(),
+ whitelisted: _ref[0].value
+
+ }, function (err, html) {
+ Q.replace(tool.element, html);
+ tool.bindLinks();
+
+ });
+ } else {
+ throw 'Can\'t get data';
+ return;
+ }
+ })
+ },
+ bindLinks: function(){
+ var tool = this;
+ var state = tool.state;
+
+ $('.userWeb3Address', tool.element).tool('Users/web3/address', {
+ onAddress: function(address, userId, avatar) {
+ state.choosen.address = address;
+ },
+ userChooser: {
+ onChoose: function(userId, avatar) {
+
+ _getXid(tool, userId, function (err, xid) {
+ if (err) {
+ return Q.alert(err);
+ }
+
+ state.choosen.address = xid;
+// if (false !== Q.handle(state.onAddress, tool, [xid, userId, avatar])) {
+// state.chosen.address = xid;
+// }
+ });
+ }
+ }
+ }).activate(function(){
+ //console.log('activated');
+ });
+ $('.addToWhitelist', tool.element).off(Q.Pointer.click).on(Q.Pointer.fastclick, function(e){
+ e.preventDefault();
+ e.stopPropagation();
+
+ $(tool.element).addClass("Q_working");
+
+ var validated = true;
+ if (
+ Q.Users.Web3.validate.notEmpty(state.choosen.address) &&
+ Q.Users.Web3.validate.address(state.choosen.address)
+ ) {
+ //
+ } else {
+
+ Q.Notices.add({
+ content: tool.text.sales.group.errors.AddressInvalid,
+ timeout: 5
+ });
+ validated = false;
+ }
+
+ if (!validated) {
+ $(tool.element).removeClass("Q_working");
+ return;
+ }
+
+ Q.Users.Web3.getContract(
+ state.abiPath,
+ {
+ contractAddress: state.salesContractAddress,
+ chainId: state.chainId
+ }
+ ).then(function (contract) {
+ return contract.whitelistAdd(
+ state.choosen.address,
+ );
+ }).then(function (tx) {
+ return tx.wait();
+ }).then(function (receipt) {
+ if (receipt.status == 0) {
+ throw 'Smth unexpected when approve';
+ }
+ tool.refresh();
+ }).finally(function(){
+ $(tool.element).removeClass("Q_working");
+ })
+
+ });
+
+ $('.addToGroup', tool.element).off(Q.Pointer.click).on(Q.Pointer.fastclick, function(e){
+ e.preventDefault();
+ e.stopPropagation();
+
+ $(tool.element).addClass("Q_working");
+
+ var validated = true;
+ var groupName = $('.groupName').val();
+ if (
+ Q.Users.Web3.validate.notEmpty(groupName)
+ ) {
+ //
+ } else {
+
+ Q.Notices.add({
+ content: tool.text.sales.group.errors.GroupNameInvalid,
+ timeout: 5
+ });
+ validated = false;
+ }
+
+ if (
+ Q.Users.Web3.validate.notEmpty(state.choosen.address) &&
+ Q.Users.Web3.validate.address(state.choosen.address)
+ ) {
+ //
+ } else {
+
+ Q.Notices.add({
+ content: tool.text.sales.group.errors.AddressInvalid,
+ timeout: 5
+ });
+ validated = false;
+ }
+
+ if (!validated) {
+ $(tool.element).removeClass("Q_working");
+ return;
+ }
+
+ Q.Users.Web3.getContract(
+ state.abiPath,
+ {
+ contractAddress: state.salesContractAddress,
+ chainId: state.chainId
+ }
+ ).then(function (contract) {
+ //function setGroup(address[] memory addresses, string memory groupName) public onlyOwner {
+ return contract.setGroup(
+ [state.choosen.address],
+ groupName
+ );
+ }).then(function (tx) {
+ return tx.wait();
+ }).then(function (receipt) {
+ if (receipt.status == 0) {
+ throw 'Smth unexpected when approve';
+ }
+ tool.refresh();
+ }).finally(function(){
+ $(tool.element).removeClass("Q_working");
+ })
+
+ });
+ },
+ Q: {
+ beforeRemove: function () {
+
+ }
+ }
+ });
+
+ Q.Template.set("Assets/web3/sales/group/preloader",
+ `
+
+ `,
+ {text: ["Assets/content", "Assets/web3/sales/main"]}
+ );
+
+ Q.Template.set("Assets/web3/sales/group",
+ `
+ {{#if this.isOwner}}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{else}}
+ {{/if}}
+
+ {{#if this.whitelisted}}
+ {{sales.group.titles.OnWhitelist}}
+ {{else}}
+ {{sales.group.titles.NotYetOnWhitelist}}
+ {{/if}}
+ Group: TBD
+ `,
+ {text: ["Assets/content", "Assets/web3/sales/group"]}
+ );
+
+ // this function might eventually be moved to Q.Streams.Web3
+ function _getXid(tool, userId, callback) {
+ Q.Streams.get(userId, "Streams/user/xid/web3", function (err) {
+ if (err) {
+ return callback(err);
+ }
+
+ var wallet, walletError;
+ if (!this.testReadLevel("content")) {
+ walletError = tool.text.errors.NotEnoughPermissionsWallet;
+ } else {
+ wallet = this.fields.content;
+ if (!wallet) {
+ walletError = tool.text.errors.ThisUserHaveNoWallet;
+ } else if (!ethers.utils.isAddress(wallet)) {
+ walletError = tool.text.errors.TheWalletOfThisUserInvalid;
+ }
+ }
+
+ if (walletError) {
+ return callback(walletError);
+ }
+
+ callback(null, this.fields.content);
+ });
+ }
+})(window, Q, Q.jQuery);
+ /**
+ p.push(Q.Assets.Funds.getWhitelisted(contractAddress, userAddress, chainId));
+ *
+Make a second tool:
+Assets/web3/sales/group
+It can have an option to specify existing chainId and address. Otherwise it will have the following elements
+Users/web3/chain tool for selecting chain
+This one will have a Users/web3/address tool
+It checks if logged in user has web3 wallet and this wallet is owner, otherwise it will be read-only and not show any buttons or input boxes,
+just say “not yet on whitelist” and “not yet in a group”.
+When this tool’s address changes, onUpdate event (or similar) you get the whitelist membership and group membership and re-render template
+1) [Add to Whitelist] button or “Already on whitelist”
+2) or show the group name if already in one
+all text should come from Assets/content text, see how templates incorporate placeholders, the text is autoloaded by Qbix
+ */
+
+/*
+
+*/
\ No newline at end of file
diff --git a/web/js/tools/web3/sales/main.js b/web/js/tools/web3/sales/main.js
new file mode 100644
index 0000000..71d8a9e
--- /dev/null
+++ b/web/js/tools/web3/sales/main.js
@@ -0,0 +1,359 @@
+(function (window, Q, $, undefined) {
+
+ /**
+ * Sales
+ * @class Assets Web3 Sales Main
+ * @constructor
+ * @param {Object} options Override various options for this tool
+ * @param {String} [options.salesContractAddress] address of Sales contract
+ * @param {String} [options.chainId] chainId
+ * @param {String} [options.uniswapPair] ABI path for uniswapPair contract
+ * @param {String} [options.abiPath] ABI path for SalesWithStablePrice contract
+ */
+ Q.Tool.define("Assets/web3/sales/main", function (options) {
+ var tool = this;
+ var state = this.state;
+
+ if (Q.isEmpty(state.salesContractAddress)) {
+ return console.warn("salesContractAddress required!");
+ }
+ if (Q.isEmpty(state.chainId)) {
+ return console.warn("chainId required!");
+ }
+ if (Q.isEmpty(state.uniswapPair)) {
+ return console.warn("uniswapPair required!");
+ }
+
+ tool.loggedInUserXid = Q.Users.Web3.getLoggedInUserXid();
+
+ // be carefull (1*10**8) it's for cents(Sales with stableprices) or for bative coins
+ tool.priceDenom = 100_000_000;//(1*10**8);
+
+ tool.refresh();
+
+ },
+ { // default options here
+ salesContractAddress: null,
+ chainId: null,
+ uniswapPair: null,
+ abiPath: 'Assets/templates/R1/Sales/contract'
+ },
+
+ { // methods go here
+ refresh: function () {
+
+ var tool = this;
+ var state = tool.state;
+
+ Q.Template.render("Assets/web3/sales/main/preloader", {
+ src: Q.url("{{Q}}/img/throbbers/loading.gif")
+ }, function (err, html) {
+ Q.replace(tool.element, html);
+ });
+ Q.Assets.Funds.getFundConfig( state.salesContractAddress, state.chainId, ethers.utils.getAddress(tool.loggedInUserXid), function(err, infoConfig){
+ if (err) {
+ console.warn('error happens')
+ return;
+ }
+ tool.infoConfig = infoConfig;
+
+ Q.Template.render("Assets/web3/sales/main", {}, function (err, html) {
+ Q.replace(tool.element, html);
+ tool.refreshCurrentTokenPrice();
+ tool.refreshCurrentUSDPrice();
+ tool.bindLinks();
+ });
+ });
+
+ },
+ updateNativeCoinBalance: function(){
+ var tool = this;
+ var state = tool.state;
+
+ Q.Assets.Currencies.balanceOf(tool.loggedInUserXid, state.chainId, function (err, moralisBalance) {
+ tool.nativeCoinBalance = moralisBalance[0].balance
+ },{tokenAddresses: null});
+ },
+ refreshCurrentUSDPrice: function(){
+ var tool = this;
+ var state = tool.state;
+ return Q.Users.Web3.getContract(
+ 'Assets/templates/Uniswap/V2/Pair', {
+ chainId: state.chainId,
+ contractAddress: state.uniswapPair,
+ readOnly: true
+ }
+ ).then(function (contract) {
+ return contract.getReserves();
+ }).then(function (reserves) {
+ tool.reserves = reserves;
+ }).catch(function(err){
+ console.warn(err);
+ })
+ },
+ refreshCurrentTokenPrice: function(){
+ var tool = this;
+ var state = tool.state;
+
+ var $priceContainer = $(tool.element).find('.currentPriceContainer');
+ Q.Template.render("Assets/web3/sales/main/preloader", {
+ src: Q.url("{{Q}}/img/throbbers/loading.gif")
+ }, function (err, html) {
+ Q.replace($priceContainer[0], html);
+ });
+
+
+ return Q.Users.Web3.getContract(
+ state.abiPath, {
+ chainId: state.chainId,
+ contractAddress: state.salesContractAddress,
+ readOnly: true
+ }
+ ).then(function (contract) {
+ return contract.getTokenPrice();
+ }).then(function (price) {
+
+ tool.tokenPrice = price;
+ var adjustPrice = ethers.utils.formatUnits(
+ (price/100).toString(), //div to 100 to get $ instead cents
+ Q.isEmpty(tool.priceDenom)?18:Math.log10(tool.priceDenom)
+ );
+ $priceContainer.html(`${adjustPrice} USD per ${tool.infoConfig.erc20TokenInfo.name}`);
+ return price;
+ }).catch(function(err){
+ console.warn(err);
+ })
+ },
+ bindLinks: function(){
+ var tool = this;
+ var state = tool.state;
+ var $toolElement = $(this.element);
+
+ $('.Assets_web3_sales_main_prices', tool.element).off(Q.Pointer.click).on(Q.Pointer.fastclick, function(){
+
+ Q.Dialogs.push({
+ title: tool.text.sales.main.titles.prices,
+ //className: "Assets_web3_transfer_transferTokens",
+ onActivate: function (dialog) {
+
+ if (Q.isEmpty(tool.infoConfig)) {
+ Q.Template.render("Assets/web3/sales/main/preloader", {
+ src: Q.url("{{Q}}/img/throbbers/loading.gif")
+ }, function (err, html) {
+ Q.replace($(dialog).find('.Q_dialog_content').get(0), html);
+ });
+
+ // fill or refresh tool.infoConfig
+ }
+
+ Q.Template.render("Assets/web3/sales/main/prices", {
+ //src: Q.url("{{Q}}/img/throbbers/loading.gif")
+ data: Q.Assets.Funds.adjustFundConfig(tool.infoConfig, {priceDenom: tool.priceDenom})
+ }, function (err, html) {
+ Q.replace($(dialog).find('.Q_dialog_content').get(0), html);
+ });
+
+ }
+ })
+ });
+
+ $('.Assets_web3_sales_main_bonuses', tool.element).off(Q.Pointer.click).on(Q.Pointer.fastclick, function(){
+
+ Q.Dialogs.push({
+ title: tool.text.sales.main.titles.bonuses,
+ //className: "Assets_web3_transfer_transferTokens",
+ onActivate: function (dialog) {
+
+ if (Q.isEmpty(tool.infoConfig)) {
+ Q.Template.render("Assets/web3/sales/main/preloader", {
+ src: Q.url("{{Q}}/img/throbbers/loading.gif")
+ }, function (err, html) {
+ Q.replace($(dialog).find('.Q_dialog_content').get(0), html);
+ });
+ }
+
+ Q.Template.render("Assets/web3/sales/main/bonuses", {
+ data: Q.Assets.Funds.adjustFundConfig(tool.infoConfig, {priceDenom: tool.priceDenom})
+ }, function (err, html) {
+ Q.replace($(dialog).find('.Q_dialog_content').get(0), html);
+ });
+
+ }
+ })
+ });
+
+ $('.buyContainer input', tool.element).off('keyup').on('keyup', function(){
+
+ var val = $(this).val();
+ if (val != parseFloat(val)) {
+ return;
+ }
+
+ Q.Template.render("Assets/web3/sales/main/ethEquivalentText", {
+ ethAmount: tool.reserves[1]/tool.reserves[0]*parseFloat(val)
+ }, function (err, html) {
+ $('.ethEquivalent', tool.element).html(html);
+ });
+ });
+
+ $('.buyBtn', tool.element).off(Q.Pointer.click).on(Q.Pointer.fastclick, function(){
+ $toolElement.addClass("Q_working");
+ var amount = $('.buyContainer input', tool.element).val();
+ var validated = true;
+
+ if (
+ !Q.Users.Web3.validate.notEmpty(amount) ||
+ !Q.Users.Web3.validate.numeric(amount)
+ ) {
+ Q.Notices.add({
+ content: "Amount invalid",
+ timeout: 5
+ });
+ validated = false;
+ }
+
+ if (!tool.infoConfig.inWhitelist) {
+ Q.Notices.add({
+ content: "Not in Whitelist",
+ timeout: 5
+ });
+ validated = false;
+ }
+
+ if (!validated) {
+ $toolElement.removeClass("Q_working");
+ return;
+ }
+
+ Q.Users.Web3.transaction(
+ state.salesContractAddress,
+ tool.reserves[1]/tool.reserves[0]*parseFloat(amount),
+ function(err, transactionRequest, transactionReceipt){
+ if (err) {
+ Q.alert(Q.Users.Web3.parseMetamaskError(err));
+ return $toolElement.removeClass("Q_working");
+ }
+
+ $toolElement.removeClass("Q_working");
+ tool.updateNativeCoinBalance();
+
+ },
+ {
+ chainId: state.chainId
+ }
+
+ )
+
+ })
+ },
+
+ Q: {
+ beforeRemove: function () {
+
+ }
+ }
+ });
+
+ Q.Template.set("Assets/web3/sales/main/preloader",
+ `
+
+ `,
+ {text: ["Assets/content", "Assets/web3/sales/main"]}
+ );
+
+ Q.Template.set("Assets/web3/sales/main/ethEquivalentText",
+ `
+ {{ethAmount}} bnb
+ `,
+ {text: ["Assets/content", "Assets/web3/sales/main"]}
+ );
+
+ Q.Template.set("Assets/web3/sales/main/bonuses",
+ `
+
+
+
+ | {{sales.main.titles.condition1}} |
+ {{sales.main.titles.bonus}} |
+
+
+
+
+ {{#each data._thresholds}}
+
+ |
+ {{this}}
+ |
+
+ {{lookup ../data._bonuses @index}}
+ |
+
+ {{/each}}
+
+
+ `,
+ {
+ text: ["Assets/content", "Assets/web3/sales/main"]
+ }
+ );
+
+ Q.Template.set("Assets/web3/sales/main/prices",
+ `
+
+
+
+ | {{sales.main.titles.condition2}} |
+ {{sales.main.titles.price}} |
+
+
+
+ {{#each data._amountRaised}}
+
+ |
+ {{#ifEquals this 0}}
+ {{#ifEquals (lookup ../data._timestamps @index) "Thu Jan 01 1970"}}
+ error
+ {{else}}
+ From {{lookup ../data._timestamps @index}}
+ {{/ifEquals}}
+ {{else}}
+ {{#ifEquals (lookup ../data._timestamps @index) "Thu Jan 01 1970"}}
+ Reach {{this}} tokens
+ {{else}}
+ {{this}} - {{lookup ../data._timestamps @index}}
+ {{/ifEquals}}
+ {{/ifEquals}}
+ |
+
+ {{lookup ../data._prices @index}}
+ |
+
+ {{/each}}
+
+
+ `,
+ {
+ text: ["Assets/content", "Assets/web3/sales/main"]
+ }
+ );
+
+ Q.Template.set("Assets/web3/sales/main",
+ `
+
+
+
+
+
+
+ {{sales.main.titles.prices}}
+ {{sales.main.titles.bonuses}}
+ `,
+ {
+ text: ["Assets/content", "Assets/web3/sales/main"]
+ }
+ );
+
+
+
+
+})(window, Q, Q.jQuery);
\ No newline at end of file