(function (EXPORTS) { //floBlockchainAPI v3.1.3 /* FLO Blockchain Operator to send/receive data from blockchain using API calls via FLO Blockbook*/ 'use strict'; const floBlockchainAPI = EXPORTS; const DEFAULT = { blockchain: floGlobals.blockchain, apiURL: { FLO: ['https://blockbook.ranchimall.net/','https://blockbook.flocard.app/'], FLO_TEST: ['https://blockbook-testnet.ranchimall.net/'] }, sendAmt: 0.0003, fee: 0.0002, minChangeAmt: 0.0002, receiverID: floGlobals.adminID }; const SATOSHI_IN_BTC = 1e8; const isUndefined = val => typeof val === 'undefined'; const torExitNodes = new Set(["185.241.208.232", "194.26.192.64", "171.25.193.25", "80.67.167.81", "192.42.116.187", "198.98.51.189", "89.58.26.216", "109.70.100.4", "149.56.22.133", "5.45.102.93", "178.17.174.14", "192.42.116.196", "185.220.101.4", "45.141.215.62", "94.102.51.15", "192.42.116.213", "107.189.28.166", "185.241.208.243", "45.141.215.80", "193.26.115.61", "192.42.116.175", "149.56.44.47", "107.189.13.91", "87.118.116.103", "178.17.171.102", "185.243.218.110", "192.42.116.208", "89.58.41.156", "2.58.56.43", "104.192.1.138", "45.95.169.184", "107.189.8.56", "176.58.121.177", "185.220.101.31", "45.141.215.200", "109.70.100.1", "185.244.192.175", "185.129.61.2", "144.172.118.41", "192.42.116.184", "45.151.167.10", "185.220.101.27", "91.203.144.194", "45.141.215.88", "179.43.182.232", "185.220.101.5", "109.70.100.2", "107.189.14.4", "94.16.116.81", "185.220.101.8", "185.220.101.12", "88.80.20.86", "23.154.177.15", "45.141.215.56", "5.42.66.6", "23.129.64.225", "104.244.75.74", "45.95.169.228", "37.187.5.192", "45.141.215.169", "109.70.100.66", "45.79.144.222", "185.227.68.78", "179.43.159.199", "2.57.122.246", "192.42.116.201", "185.220.102.248", "195.176.3.23", "45.138.16.42", "216.73.159.75", "185.165.169.239", "23.129.64.213", "109.70.100.6", "45.80.158.27", "45.138.16.240", "178.20.55.16", "192.42.116.173", "51.15.249.160", "192.42.116.200", "185.220.102.254", "45.141.215.63", "193.218.118.151", "192.42.116.211", "185.100.85.24", "185.195.71.12", "107.189.8.181", "193.189.100.199", "109.70.100.69", "185.100.87.250", "31.220.93.201", "89.236.112.100", "45.141.215.90", "185.35.202.222", "109.70.100.65", "95.142.161.63", "192.42.116.181", "192.42.116.23", "194.26.192.77", "193.189.100.198", "180.150.226.99", "23.129.64.227", "107.189.4.23", "45.141.215.235", "185.220.102.252", "109.70.100.67", "185.220.100.255", "185.220.101.21", "185.100.85.22", "128.31.0.13", "46.182.21.248", "192.42.116.174", "185.241.208.115", "185.220.101.1", "192.42.116.202", "45.141.215.97", "185.243.218.204", "78.142.18.219", "192.42.116.192", "190.120.229.98", "192.42.116.177", "45.138.16.113", "192.42.116.212", "185.220.101.3", "45.138.16.222", "5.42.80.232", "87.118.122.51", "107.189.11.166", "185.220.102.245", "185.220.102.251", "46.182.21.250", "5.255.103.235", "185.243.218.89", "185.193.52.180", "185.220.101.24", "2.57.122.215", "45.15.157.177", "185.220.100.253", "37.48.120.64", "204.8.156.142", "192.42.116.179", "185.220.100.240", "185.241.208.236", "185.195.71.244", "193.105.134.155", "51.15.59.15", "185.100.85.23", "45.151.167.11", "82.197.182.161", "192.42.116.191", "27.255.75.198", "171.25.193.79", "45.95.169.255", "45.138.16.230", "107.189.29.103", "163.172.213.212", "95.143.193.125", "23.154.177.7", "185.220.101.23", "195.176.3.24", "107.189.1.9", "192.42.116.182", "23.137.249.240", "192.42.116.189", "23.129.64.146", "45.138.16.107", "107.189.5.121", "107.189.30.236", "94.16.121.91", "109.70.100.70", "185.254.196.141", "194.15.112.133", "192.42.116.180", "173.249.57.253", "185.220.102.250", "185.100.85.25", "185.220.101.13", "185.220.101.25", "192.42.116.199", "23.154.177.2", "107.189.31.232", "45.141.215.81", "192.42.116.220", "185.67.82.114", "45.141.215.114", "185.243.218.61", "107.189.13.184", "107.189.10.141", "104.244.79.61", "185.106.94.195", "176.126.253.190", "23.154.177.22", "192.42.116.210", "185.220.102.249", "23.184.48.127", "192.42.116.218", "91.208.75.4", "192.42.116.178", "178.175.148.209", "208.109.36.224", "23.137.251.61", "94.142.241.194", "162.251.5.152", "23.154.177.4", "45.138.16.76", "45.9.150.103", "213.252.140.118", "185.243.218.95", "45.134.225.36", "109.70.100.5", "185.243.218.202", "185.220.101.19", "192.42.116.176", "109.70.100.71", "45.151.167.13", "185.220.102.4", "185.220.102.7", "104.244.79.50", "178.17.174.198", "199.195.249.214", "66.146.193.33", "107.189.8.238", "139.99.8.57", "45.141.215.95", "192.42.116.219", "114.199.75.111", "185.220.100.242", "5.42.80.234", "173.237.206.68", "139.99.172.11", "23.129.64.143", "80.241.60.207", "192.42.116.194", "45.95.169.226", "185.220.102.8", "109.70.100.3", "179.43.159.200", "192.42.116.217", "185.220.101.6", "198.98.50.199", "185.100.87.192", "193.189.100.202", "163.172.45.102", "185.220.101.0", "107.189.8.133", "185.129.61.6", "104.244.78.233", "192.42.116.15", "192.42.116.195", "45.141.215.110", "193.189.100.203", "77.48.28.237", "104.244.79.232", "193.26.115.43", "199.195.250.165", "190.211.254.97", "45.141.215.61", "185.220.101.17", "192.42.116.203", "185.220.102.247", "91.132.144.59", "185.141.147.129", "23.129.64.149", "185.183.157.214", "95.211.244.28", "192.42.116.188", "188.214.104.21", "192.42.116.186", "192.42.116.197", "107.189.13.247", "212.73.134.204", "185.235.146.29", "188.68.49.235", "92.205.237.227", "23.154.177.12", "199.195.253.180", "171.25.193.234", "185.241.208.71", "96.66.15.152", "94.16.121.226", "204.85.191.9", "91.210.59.57", "5.255.115.42", "185.220.103.113", "216.239.90.19", "77.91.87.79", "192.42.116.216", "23.154.177.23", "192.42.116.198", "173.255.255.215", "144.217.80.80", "107.189.10.175", "45.95.169.227", "103.251.167.20", "185.220.101.30", "5.255.125.196", "198.98.48.192", "185.220.102.242", "23.154.177.18", "185.86.148.90", "185.142.239.49", "185.220.101.2", "5.255.100.219", "107.189.5.7", "199.195.251.119", "185.220.101.10", "92.246.84.133", "66.220.242.222", "184.105.48.40", "23.129.64.133", "185.130.44.108", "192.42.116.20", "185.181.61.115", "192.42.116.19", "149.202.79.129", "146.59.35.38", "23.154.177.20", "185.191.204.254", "23.154.177.3", "185.233.100.23", "23.154.177.19", "45.92.1.74", "107.189.31.225", "89.58.18.10", "138.59.18.110", "185.246.188.73", "192.42.116.221", "104.244.77.192", "192.42.116.214", "178.170.37.11", "188.68.41.191", "192.42.116.183", "185.220.103.115", "178.175.135.7", "209.141.51.30", "141.98.11.62", "171.25.193.235", "23.137.249.143", "179.43.159.197", "192.99.168.180", "185.220.101.11", "185.243.218.41", "89.234.157.254", "47.243.74.136", "107.189.28.199", "185.129.61.9", "185.220.101.28", "185.220.101.29", "5.255.99.5", "179.43.182.58", "185.129.61.3", "23.129.64.135", "107.189.30.69", "51.15.227.109", "185.207.107.216", "185.129.61.129", "185.100.87.41", "23.129.64.145", "179.43.159.201", "23.129.64.224", "192.42.116.28", "93.99.104.194", "185.244.192.184", "45.95.169.223", "104.244.73.43", "185.56.83.83", "87.120.254.48", "185.185.170.27", "195.88.74.206", "107.174.138.172", "109.70.100.68", "23.129.64.139", "94.230.208.147", "77.91.85.147", "77.81.247.72", "2.58.56.220", "185.220.103.7", "149.202.79.101", "5.255.104.202", "178.175.148.195", "83.96.213.63", "185.100.87.174", "79.137.195.103", "185.220.101.20", "107.189.3.11", "185.220.101.22", "185.220.101.7", "217.12.221.131", "179.43.159.196", "45.95.169.230", "107.189.1.160", "208.109.215.188", "171.25.193.78", "204.194.29.4", "104.244.77.80", "162.247.72.199", "89.58.52.25", "192.42.116.209", "217.146.2.41", "185.220.103.117", "23.154.177.10", "91.208.75.3", "94.230.208.148", "95.128.43.164", "171.25.193.20", "102.130.113.9", "91.92.109.43", "107.189.7.144", "185.220.102.240", "5.255.124.150", "198.98.60.158", "185.227.134.106", "193.233.233.221", "71.19.144.106", "185.84.31.254", "23.129.64.132", "62.171.137.169", "193.189.100.196", "185.220.101.18", "107.189.12.3", "91.208.75.178", "193.35.18.49", "185.246.188.74", "45.132.246.245", "209.141.55.26", "198.98.48.20", "185.129.61.1", "108.61.189.136", "185.220.102.243", "107.189.1.96", "185.100.87.136", "213.95.149.22", "23.129.64.217", "192.42.116.185", "5.45.104.176", "192.42.116.193", "23.154.177.16", "198.98.49.203", "171.25.193.77", "91.208.75.153", "162.247.74.216", "179.43.159.194", "54.36.108.162", "198.98.48.33", "188.68.52.231", "185.220.100.252", "205.185.124.193", "104.244.73.190", "185.100.87.139", "23.154.177.25", "77.105.146.42", "79.137.202.92", "51.38.81.135", "87.118.116.90", "23.129.64.134", "185.246.188.67", "185.129.62.62", "185.220.100.241", "82.221.131.71", "209.141.59.116", "194.195.120.132", "185.207.107.130", "178.218.144.99", "172.104.243.155", "93.99.104.128", "87.118.122.30", "185.100.87.253", "51.195.91.124", "104.192.3.74", "185.252.232.218", "23.129.64.141", "5.196.95.34", "185.220.102.6", "23.184.48.128", "193.239.232.102", "185.220.101.16", "91.203.145.116", "185.129.61.4", "23.129.64.147", "37.228.129.63", "45.151.167.12", "93.95.228.205", "185.220.102.244", "209.141.54.203", "93.95.230.165", "94.142.244.16", "162.247.72.192", "185.146.232.234", "81.16.33.42", "107.189.30.86", "51.81.222.62", "23.154.177.5", "77.220.196.253", "72.167.47.69", "185.220.101.26", "104.219.236.100", "192.42.116.204", "185.246.128.161", "200.122.181.2", "199.195.253.247", "109.201.133.100", "142.44.234.69", "89.147.110.202", "89.185.85.140", "104.244.79.44", "5.2.79.179", "23.129.64.130", "104.244.78.187", "23.154.177.13", "5.255.97.221", "92.205.129.119", "80.82.78.14", "23.154.177.8", "51.38.113.118", "45.61.184.205", "107.189.31.134", "185.220.103.114", "179.48.251.188", "135.125.205.25", "198.98.54.49", "193.189.100.205", "185.220.102.253", "45.79.50.161", "202.69.76.36", "79.137.198.213", "46.166.139.111", "5.255.111.64", "51.89.138.51", "216.73.159.101", "166.70.207.2", "96.27.198.133", "194.15.115.212", "46.234.47.105", "146.59.35.246", "23.137.248.100", "185.220.102.241", "107.189.14.43", "212.95.50.77", "128.127.180.156", "80.67.172.162", "185.129.61.5", "185.129.61.10", "23.129.64.214", "185.220.100.254", "160.119.249.240", "185.243.218.46", "185.220.102.246", "104.244.74.97", "23.129.64.228", "23.129.64.218", "185.220.100.243", "54.36.101.21", "5.255.99.124", "107.189.13.253", "130.149.80.199", "171.25.193.80", "144.24.197.112", "199.195.251.78", "23.129.64.223", "195.80.151.30", "185.7.33.146", "107.189.4.12", "45.95.169.229", "107.189.6.124", "46.38.255.27", "107.189.8.226", "143.42.199.223", "103.251.167.10", "185.34.33.2", "5.255.98.23", "74.82.47.194", "194.163.157.49", "192.42.116.215", "185.220.101.14", "194.15.113.118", "89.147.108.62", "185.220.101.15", "185.42.170.203", "23.154.177.6", "162.247.74.27", "199.195.253.124", "193.189.100.201", "62.182.84.146", "191.101.217.24", "23.129.64.229", "85.93.218.204", "178.17.174.164", "205.185.117.149", "193.218.118.133", "23.154.177.21", "5.255.101.10", "82.221.131.5", "193.189.100.204", "103.196.37.111", "103.109.101.105", "192.42.116.18", "23.129.64.226", "107.189.13.251", "45.56.81.190", "192.42.116.13", "107.189.11.111", "198.46.166.157", "185.220.103.119", "54.38.183.101", "77.68.20.217", "185.220.101.36", "103.236.201.88", "162.247.74.213", "185.129.61.8", "89.147.110.154", "45.95.169.225", "141.239.149.94", "82.221.128.191", "72.14.179.10", "46.232.251.191", "23.129.64.215", "162.247.74.7", "23.154.177.14", "89.147.109.226", "193.41.226.117", "89.147.108.209", "23.129.64.137", "93.123.12.112", "185.14.97.37", "103.163.218.11", "23.129.64.131", "23.129.64.142", "23.137.249.185", "89.58.41.251", "185.220.101.9", "202.182.99.129", "205.185.119.35", "193.189.100.194", "204.85.191.8", "185.56.171.94", "23.129.64.144", "102.130.127.117", "192.42.116.24", "179.43.159.198", "185.38.175.133", "185.220.101.39", "193.168.143.129", "5.255.127.222", "95.211.210.103", "185.220.103.116", "23.129.64.211", "23.129.64.220", "185.113.128.30", "151.80.148.159", "192.99.149.111", "23.129.64.210", "37.228.129.128", "91.208.75.239", "185.220.103.120", "185.165.171.84", "193.105.134.150", "209.141.46.203", "209.141.50.178", "104.244.74.23", "45.95.169.224", "23.129.64.140", "176.118.193.33", "204.85.191.7", "104.244.73.193", "162.247.74.204", "91.208.75.156", "205.185.116.34", "125.212.241.131", "5.2.72.110", "179.43.159.195", "185.154.110.142", "91.206.26.26", "45.79.177.21", "23.154.177.9", "193.189.100.197", "46.165.243.36", "107.189.2.108", "23.154.177.17", "23.129.64.148", "5.45.98.162", "5.255.101.131", "23.129.64.136", "107.189.31.33", "185.82.219.109", "104.244.73.136", "185.129.61.7", "5.255.115.58", "23.154.177.24", "165.73.242.163", "193.189.100.200", "192.46.227.185", "5.196.8.113", "77.91.86.95", "85.209.176.103", "23.137.249.8", "5.255.98.151", "23.129.64.221", "23.129.64.219", "23.129.64.216", "185.243.218.35", "104.244.77.208", "94.228.169.70", "51.75.64.23", "176.58.100.98", "23.154.177.11", "23.129.64.138", "143.42.110.237", "94.16.112.22", "144.172.118.4", "185.130.47.58", "185.154.110.17", "104.244.72.132", "5.2.79.190", "23.129.64.212", "109.169.33.163", "5.2.67.226", "109.69.67.17", "108.181.27.205", "5.255.103.190", "107.189.14.106", "5.255.99.147", "193.189.100.206", "193.218.118.182", "185.181.61.142", "23.129.64.222", "193.35.18.77", "185.100.86.128", "91.203.5.118", "83.97.20.77", "45.138.16.203", "2.57.122.58", "185.181.61.18", "195.176.3.19", "195.176.3.20", "198.58.107.53", "138.128.222.68", "118.163.74.160", "185.241.208.54", "38.97.116.244", "104.244.77.79", "103.253.24.18", "185.225.69.203", "162.247.74.206", "79.124.8.241", "91.203.5.115", "144.172.118.102", "144.172.118.124", "185.225.69.232", "163.5.143.76", "144.172.118.51", "178.20.55.182", "109.104.153.22", "193.233.133.109", "51.158.115.62", "92.205.31.137", "185.193.158.134", "217.12.215.167", "45.15.158.39", "185.174.136.114", "91.219.239.166", "91.219.237.56", "51.159.211.57", "192.210.255.181", "185.170.114.25", "205.185.123.93", "205.185.121.170", "107.189.13.180", "104.244.78.162", "104.244.76.170", "104.244.74.57", "195.160.220.104", "31.220.98.139", "158.220.92.203", "23.184.48.101", "178.31.22.116", "79.102.34.63", "185.220.103.5", "179.43.128.16", "45.128.133.242", "185.220.103.118", "185.100.85.132", "107.189.7.48", "5.135.174.211", "45.8.22.207", "185.220.101.159", "185.220.101.141", "185.220.101.134", "185.220.101.147", "185.220.101.153", "185.220.101.145", "185.220.101.158", "185.220.101.160", "185.220.101.137", "185.220.101.140", "185.220.101.132", "185.220.101.157", "185.220.101.150", "185.220.101.143", "158.69.201.47", "107.189.1.175", "176.58.89.182", "185.220.101.138", "82.118.242.158", "217.170.201.71", "193.189.100.195", "144.172.118.48", "185.220.101.135", "185.220.101.191", "185.220.101.136", "185.220.101.179", "185.220.101.170", "185.220.101.149", "185.220.101.173", "185.220.101.171", "185.220.101.161", "185.220.101.163", "185.220.101.152", "185.220.101.162", "185.220.101.176", "185.220.101.188", "185.82.127.128", "85.235.145.205", "172.81.131.139", "5.255.100.26", "62.63.244.7", "104.219.236.101", "23.137.248.139", "185.241.208.204", "45.141.215.111", "185.241.208.202", "45.141.215.21", "45.61.185.172", "185.241.208.206", "205.185.113.180", "93.242.68.75", "185.220.100.248", "185.220.100.251", "185.220.100.247", "185.220.100.245", "185.220.100.246", "185.220.100.249", "185.220.100.250", "185.220.100.244", "77.72.85.30", "51.222.142.67", "107.172.31.165", "107.174.231.197", "198.144.178.163", "23.137.250.34", "107.172.13.143", "107.172.31.146", "173.232.195.137", "50.3.182.156", "173.232.195.144", "173.232.195.146", "172.81.131.168", "172.81.131.84", "77.48.28.239", "172.81.131.156", "185.183.159.40", "196.189.30.114", "107.189.8.5", "185.220.101.168", "185.220.101.165", "185.220.101.142", "185.220.101.167", "185.220.101.166", "185.220.101.169", "77.48.28.193", "37.228.129.5", "144.172.73.11", "107.189.14.57", "84.16.224.227", "185.220.103.4", "162.247.74.202", "185.220.103.6", "162.247.74.200", "185.220.103.9", "185.220.103.8", "154.12.254.57", "94.103.124.184", "185.220.101.189", "67.219.109.141", "185.220.101.187", "185.220.101.186", "185.220.101.183", "50.3.182.133", "185.220.101.182", "185.220.101.184", "188.172.229.15", "89.58.18.210", "45.9.150.130", "190.103.179.98", "108.181.124.143", "178.218.144.51", "185.220.101.66", "185.220.101.70", "185.220.101.68", "185.220.101.77", "185.220.101.78", "185.220.101.81", "185.220.101.71", "185.220.101.83", "185.220.101.75", "185.220.101.85", "185.220.101.73", "185.220.101.82", "185.220.101.65", "185.220.101.84", "185.220.101.76", "185.220.101.86", "185.220.101.69", "185.220.101.67", "185.220.101.80", "185.220.101.64", "185.220.101.74", "185.220.101.79", "185.220.101.72", "185.220.101.87", "199.249.230.120", "184.75.221.171", "5.182.86.212", "104.244.72.115", "198.23.133.132", "23.94.36.142", "198.98.60.90", "84.19.182.20", "45.9.148.219", "217.160.88.146", "104.219.232.126", "45.139.122.241", "199.195.253.156", "75.119.142.240", "199.249.230.103", "199.249.230.104", "199.249.230.116", "199.249.230.101", "199.249.230.119", "199.249.230.100", "199.249.230.102", "199.249.230.109", "199.249.230.81", "199.249.230.176", "199.249.230.79", "199.249.230.167", "199.249.230.88", "199.249.230.188", "199.249.230.80", "199.249.230.144", "199.249.230.78", "199.249.230.111", "199.249.230.68", "199.249.230.180", "199.249.230.150", "199.249.230.70", "199.249.230.77", "199.249.230.112", "199.249.230.65", "199.249.230.183", "199.249.230.189", "199.249.230.178", "199.249.230.145", "199.249.230.115", "199.249.230.147", "199.249.230.66", "199.249.230.140", "199.249.230.114", "199.249.230.170", "199.249.230.71", "199.249.230.148", "199.249.230.67", "199.249.230.75", "199.249.230.146", "199.249.230.151", "199.249.230.187", "199.249.230.174", "199.249.230.143", "199.249.230.118", "199.249.230.64", "199.249.230.85", "199.249.230.113", "199.249.230.155", "199.249.230.153", "199.249.230.89", "45.77.67.251", "123.253.35.32", "45.83.104.137", "94.32.66.15", "185.220.101.181", "185.220.101.178", "185.220.101.177", "185.220.101.175", "185.220.101.172", "94.16.116.86", "5.181.80.107", "198.50.207.20", "107.189.7.168", "85.215.76.62", "185.247.184.105", "178.236.247.122", "109.107.190.171", "193.233.233.124", "193.218.118.188", "2.58.95.45", "45.154.98.102", "92.205.185.52", "92.205.163.226", "185.217.125.210", "5.255.118.104", "212.69.167.80", "23.137.249.227", "5.255.118.244", "71.19.148.129", "143.42.114.46", "45.33.15.243", "104.237.158.32", "172.232.161.205", "172.232.161.206", "74.207.248.172", "172.233.209.179", "45.66.35.21", "45.66.35.35", "45.66.35.10", "45.66.35.20", "45.66.35.22", "51.210.138.64", "130.204.161.3", "175.214.127.6", "31.220.85.162", "198.96.155.3", "50.118.225.160", "45.135.132.20", "23.152.24.77", "45.95.169.99", "94.75.225.81", "37.228.129.131", "23.137.249.62", "103.172.134.26", "199.249.230.121", "191.252.111.55", "35.0.127.52", "185.129.62.63", "23.94.211.25", "185.220.101.139", "185.220.101.144", "185.220.101.130", "185.220.101.156", "185.220.101.128", "185.220.101.131", "185.220.101.154", "185.220.101.164", "185.220.101.180", "185.220.101.155", "185.220.101.133", "185.220.101.190", "185.220.101.151", "185.220.101.174", "185.220.101.148", "185.220.101.129", "185.220.101.185", "37.221.208.68", "87.120.254.132", "5.255.106.9", "45.15.158.165", "193.35.18.105", "178.17.170.23", "185.146.232.243", "194.163.178.164", "94.140.115.63", "37.228.129.24", "81.0.248.210", "193.35.18.98", "45.128.232.170", "193.35.18.96", "45.128.232.102", "193.35.18.94", "193.35.18.95", "149.102.128.242", "89.187.143.31", "193.239.232.228", "103.208.86.5", "193.35.18.120", "185.130.44.43", "185.219.142.126", "37.1.201.144", "5.255.99.108", "85.204.116.211", "130.193.10.21", "130.193.15.79", "84.239.46.144", "178.218.162.62", "199.249.230.122", "199.249.230.84", "45.141.202.164", "199.249.230.74", "148.113.2.107", "199.249.230.105", "199.249.230.73", "199.249.230.110", "199.249.230.72", "199.249.230.86", "103.129.222.46", "64.5.123.66", "185.239.71.160", "5.42.80.233", "5.42.80.235", "200.25.27.112", "46.226.107.206", "103.106.3.175", "96.42.26.63", "192.42.116.26", "192.42.116.17", "192.42.116.14", "192.42.116.22", "192.42.116.25", "192.42.116.27", "74.208.106.128", "213.232.235.83", "91.208.197.144", "51.195.166.174", "198.98.53.136", "157.143.80.38", "198.50.128.237", "193.233.232.86", "144.126.152.77", "158.220.80.216", "154.16.116.61", "45.88.223.151", "144.126.132.30", "89.147.110.214", "89.163.155.136", "107.189.13.93", "77.232.143.255", "77.232.143.243", "77.232.143.248", "94.228.163.25", "199.249.230.186", "199.249.230.177", "199.249.230.159", "199.249.230.161", "199.249.230.163", "199.249.230.149", "199.249.230.154", "199.249.230.164", "199.249.230.160", "199.249.230.173", "199.249.230.158", "199.249.230.157", "199.249.230.108", "199.249.230.83", "199.249.230.168", "199.249.230.82", "199.249.230.166", "199.249.230.123", "199.249.230.106", "199.249.230.76", "199.249.230.117", "199.249.230.169", "199.249.230.171", "199.249.230.175", "199.249.230.107", "199.249.230.152", "199.249.230.162", "2.58.95.53", "199.249.230.69", "2.58.95.47", "2.58.95.59", "2.58.95.56", "178.175.142.26", "199.249.230.156", "199.249.230.87", "103.28.52.93", "185.107.70.56", "89.147.108.56", "148.113.2.104", "38.242.203.135", "162.247.74.201", "172.232.238.10", "5.255.98.198", "5.255.98.231", "23.137.249.150", "149.102.155.205", "199.249.230.179", "199.249.230.165", "199.249.230.182", "199.249.230.184", "199.249.230.142", "136.243.147.59", "199.249.230.185", "185.220.101.89", "149.102.145.222", "185.220.101.90", "185.220.101.88", "87.118.110.27", "37.48.70.156", "185.165.190.111", "5.255.125.153", "205.185.124.176", "107.189.14.41", "93.95.228.81", "172.81.131.140", "185.38.142.4", "95.168.173.143", "178.218.144.18", "189.147.238.226", "189.147.187.10", "189.147.242.169", "104.219.236.93", "161.35.129.51", "86.104.194.13", "104.244.74.159", "185.220.101.40", "185.220.101.32", "185.220.101.38", "185.220.101.37", "185.220.101.35", "185.220.101.33", "185.220.101.34", "185.220.101.41", "185.220.101.42", "205.185.127.100", "185.220.101.57", "185.220.101.43", "185.220.101.46", "185.220.101.58", "185.220.101.61", "185.220.101.60", "185.220.101.63", "185.220.101.54", "185.220.101.52", "185.220.101.62", "185.220.101.56", "185.220.101.44", "185.220.101.49", "5.255.117.56", "185.220.101.55", "185.220.101.45", "185.220.101.53", "185.220.101.59", "185.220.101.51", "185.220.101.48", "185.220.101.47", "185.220.101.50", "95.111.238.0", "152.89.233.169", "89.147.110.82", "176.58.117.81", "23.155.8.104", "51.89.153.112", "5.61.51.143", "5.135.174.213", "37.120.166.23", "37.252.255.135", "82.153.138.48", "185.81.115.120", "45.139.122.176", "84.211.225.54", "31.220.87.46", "144.172.73.6", "51.89.200.109", "212.44.107.82", "89.147.111.124", "94.177.106.59", "94.177.106.55", "94.177.106.46", "93.95.231.88", "152.32.238.235", "74.208.96.95", "38.242.239.62", "87.118.114.44", "80.78.25.9", "185.193.125.95", "107.173.179.59", "179.43.159.78", "81.17.28.95", "45.79.129.209", "82.221.139.190", "107.189.13.254", "81.19.137.127", "149.102.129.11", "81.0.218.34", "93.90.74.31", "51.81.254.4", "109.123.231.55", "185.196.8.2", "158.220.81.45", "62.149.23.133", "158.220.81.47", "158.220.81.78", "209.141.51.180", "176.121.81.51", "178.17.170.184", "202.61.226.98", "202.139.229.157", "89.147.111.106"]); const checkIfTor = floBlockchainAPI.checkIfTor = () => { return fetch('https://api.ipify.org?format=json').then(response => response.json()) .then(result => { return torExitNodes.has(result.ip) }).catch(e => { console.error(e) return false }) } let isTor = false; checkIfTor().then(result => { isTor = result if (isTor) { DEFAULT.apiURL.FLO.push('http://kvrddx6heo47rbbt77etxg6litckacbgos3nv5z7vc23ol2kjjeq72id.onion/') // DEFAULT.apiURL.FLO_TEST.push('http://omwkzk6bd6zuragdqsrhdyzgxzre7yx4vzrou4vzftintzc2dmagp6qd.onion:15017/') } }); const util = floBlockchainAPI.util = {}; util.Sat_to_FLO = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8)); util.FLO_to_Sat = value => parseInt(value * SATOSHI_IN_BTC); util.toFixed = value => parseFloat((value).toFixed(8)); Object.defineProperties(floBlockchainAPI, { sendAmt: { get: () => DEFAULT.sendAmt, set: amt => !isNaN(amt) ? DEFAULT.sendAmt = amt : null }, fee: { get: () => DEFAULT.fee, set: fee => !isNaN(fee) ? DEFAULT.fee = fee : null }, defaultReceiver: { get: () => DEFAULT.receiverID, set: floID => DEFAULT.receiverID = floID }, blockchain: { get: () => DEFAULT.blockchain } }); if (floGlobals.sendAmt) floBlockchainAPI.sendAmt = floGlobals.sendAmt; if (floGlobals.fee) floBlockchainAPI.fee = floGlobals.fee; Object.defineProperties(floGlobals, { sendAmt: { get: () => DEFAULT.sendAmt, set: amt => !isNaN(amt) ? DEFAULT.sendAmt = amt : null }, fee: { get: () => DEFAULT.fee, set: fee => !isNaN(fee) ? DEFAULT.fee = fee : null } }); const allServerList = new Set(floGlobals.apiURL && floGlobals.apiURL[DEFAULT.blockchain] ? floGlobals.apiURL[DEFAULT.blockchain] : DEFAULT.apiURL[DEFAULT.blockchain]); var serverList = Array.from(allServerList); var curPos = floCrypto.randInt(0, serverList.length - 1); function fetch_retry(apicall, rm_node) { return new Promise((resolve, reject) => { let i = serverList.indexOf(rm_node) if (i != -1) serverList.splice(i, 1); curPos = floCrypto.randInt(0, serverList.length - 1); fetch_api(apicall, false) .then(result => resolve(result)) .catch(error => reject(error)); }) } function fetch_api(apicall, ic = true) { return new Promise((resolve, reject) => { if (serverList.length === 0) { if (ic) { serverList = Array.from(allServerList); curPos = floCrypto.randInt(0, serverList.length - 1); fetch_api(apicall, false) .then(result => resolve(result)) .catch(error => reject(error)); } else reject("No FLO blockbook server working"); } else { let serverURL = serverList[curPos]; fetch(serverURL + apicall).then(response => { if (response.ok) response.json().then(data => resolve(data)); else { fetch_retry(apicall, serverURL) .then(result => resolve(result)) .catch(error => reject(error)); } }).catch(error => { fetch_retry(apicall, serverURL) .then(result => resolve(result)) .catch(error => reject(error)); }) } }) } Object.defineProperties(floBlockchainAPI, { serverList: { get: () => Array.from(serverList) }, current_server: { get: () => serverList[curPos] } }); //Promised function to get data from API const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall, query_params = undefined) { return new Promise((resolve, reject) => { if (!isUndefined(query_params)) apicall += '?' + new URLSearchParams(JSON.parse(JSON.stringify(query_params))).toString(); //console.debug(apicall); fetch_api(apicall) .then(result => resolve(result)) .catch(error => reject(error)); }); } //Get balance for the given Address const getBalance = floBlockchainAPI.getBalance = function (addr) { return new Promise((resolve, reject) => { let api = `api/address/${addr}`; promisedAPI(api, { details: "basic" }) .then(result => resolve(result["balance"])) .catch(error => reject(error)) }); } function getScriptPubKey(address) { var tx = bitjs.transaction(); tx.addoutput(address, 0); let outputBuffer = tx.outputs.pop().script; return Crypto.util.bytesToHex(outputBuffer) } const getUTXOs = address => new Promise((resolve, reject) => { promisedAPI(`api/utxo/${address}`, { confirmed: true }).then(utxos => { let scriptPubKey = getScriptPubKey(address); utxos.forEach(u => u.scriptPubKey = scriptPubKey); resolve(utxos); }).catch(error => reject(error)) }) //create a transaction with single sender const createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { if (!floCrypto.validateASCII(floData)) return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); else if (!floCrypto.validateFloID(senderAddr, true)) return reject(`Invalid address : ${senderAddr}`); else if (!floCrypto.validateFloID(receiverAddr)) return reject(`Invalid address : ${receiverAddr}`); else if (typeof sendAmt !== 'number' || sendAmt <= 0) return reject(`Invalid sendAmt : ${sendAmt}`); getBalance(senderAddr).then(balance => { var fee = DEFAULT.fee; if (balance < sendAmt + fee) return reject("Insufficient FLO balance!"); getUTXOs(senderAddr).then(utxos => { //form/construct the transaction data var trx = bitjs.transaction(); var utxoAmt = 0.0; for (var i = utxos.length - 1; (i >= 0) && (utxoAmt < sendAmt + fee); i--) { //use only utxos with confirmations (strict_utxo mode) if (utxos[i].confirmations || !strict_utxo) { trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); utxoAmt += utxos[i].amount; }; } if (utxoAmt < sendAmt + fee) reject("Insufficient FLO: Some UTXOs are unconfirmed"); else { trx.addoutput(receiverAddr, sendAmt); var change = utxoAmt - sendAmt - fee; if (change > DEFAULT.minChangeAmt) trx.addoutput(senderAddr, change); trx.addflodata(floData.replace(/\n/g, ' ')); resolve(trx); } }).catch(error => reject(error)) }).catch(error => reject(error)) }) } floBlockchainAPI.createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo) .then(trx => resolve(trx.serialize())) .catch(error => reject(error)) }) } //Send Tx to blockchain const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { if (!floCrypto.validateFloID(senderAddr, true)) return reject(`Invalid address : ${senderAddr}`); else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr)) return reject("Invalid Private key!"); createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo).then(trx => { var signedTxHash = trx.sign(privKey, 1); broadcastTx(signedTxHash) .then(txid => resolve(txid)) .catch(error => reject(error)) }).catch(error => reject(error)) }); } //Write Data into blockchain floBlockchainAPI.writeData = function (senderAddr, data, privKey, receiverAddr = DEFAULT.receiverID, options = {}) { let strict_utxo = options.strict_utxo === false ? false : true, sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt; return new Promise((resolve, reject) => { if (typeof data != "string") data = JSON.stringify(data); sendTx(senderAddr, receiverAddr, sendAmt, privKey, data, strict_utxo) .then(txid => resolve(txid)) .catch(error => reject(error)); }); } //merge all UTXOs of a given floID into a single UTXO floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') { return new Promise((resolve, reject) => { if (!floCrypto.validateFloID(floID, true)) return reject(`Invalid floID`); if (!floCrypto.verifyPrivKey(privKey, floID)) return reject("Invalid Private Key"); if (!floCrypto.validateASCII(floData)) return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); var trx = bitjs.transaction(); var utxoAmt = 0.0; var fee = DEFAULT.fee; getUTXOs(floID).then(utxos => { for (var i = utxos.length - 1; i >= 0; i--) if (utxos[i].confirmations) { trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); utxoAmt += utxos[i].amount; } trx.addoutput(floID, utxoAmt - fee); trx.addflodata(floData.replace(/\n/g, ' ')); var signedTxHash = trx.sign(privKey, 1); broadcastTx(signedTxHash) .then(txid => resolve(txid)) .catch(error => reject(error)) }).catch(error => reject(error)) }) } //split sufficient UTXOs of a given floID for a parallel sending floBlockchainAPI.splitUTXOs = function (floID, privKey, count, floData = '') { return new Promise((resolve, reject) => { if (!floCrypto.validateFloID(floID, true)) return reject(`Invalid floID`); if (!floCrypto.verifyPrivKey(privKey, floID)) return reject("Invalid Private Key"); if (!floCrypto.validateASCII(floData)) return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); var fee = DEFAULT.fee; var splitAmt = DEFAULT.sendAmt + fee; var totalAmt = splitAmt * count; getBalance(floID).then(balance => { var fee = DEFAULT.fee; if (balance < totalAmt + fee) return reject("Insufficient FLO balance!"); //get unconfirmed tx list getUTXOs(floID).then(utxos => { var trx = bitjs.transaction(); var utxoAmt = 0.0; for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < totalAmt + fee); i--) { //use only utxos with confirmations (strict_utxo mode) if (utxos[i].confirmations || !strict_utxo) { trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); utxoAmt += utxos[i].amount; }; } if (utxoAmt < totalAmt + fee) reject("Insufficient FLO: Some UTXOs are unconfirmed"); else { for (let i = 0; i < count; i++) trx.addoutput(floID, splitAmt); var change = utxoAmt - totalAmt - fee; if (change > DEFAULT.minChangeAmt) trx.addoutput(floID, change); trx.addflodata(floData.replace(/\n/g, ' ')); var signedTxHash = trx.sign(privKey, 1); broadcastTx(signedTxHash) .then(txid => resolve(txid)) .catch(error => reject(error)) } }).catch(error => reject(error)) }).catch(error => reject(error)) }) } /**Write data into blockchain from (and/or) to multiple floID * @param {Array} senderPrivKeys List of sender private-keys * @param {string} data FLO data of the txn * @param {Array} receivers List of receivers * @param {boolean} preserveRatio (optional) preserve ratio or equal contribution * @return {Promise} */ floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], options = {}) { return new Promise((resolve, reject) => { if (!Array.isArray(senderPrivKeys)) return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array"); if (options.preserveRatio === false) { let tmp = {}; let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length; senderPrivKeys.forEach(key => tmp[key] = amount); senderPrivKeys = tmp; } if (!Array.isArray(receivers)) return reject("Invalid receivers: Receivers must be Array"); else { let tmp = {}; let amount = options.sendAmt || DEFAULT.sendAmt; receivers.forEach(floID => tmp[floID] = amount); receivers = tmp } if (typeof data != "string") data = JSON.stringify(data); sendTxMultiple(senderPrivKeys, receivers, data) .then(txid => resolve(txid)) .catch(error => reject(error)) }) } /**Send Tx from (and/or) to multiple floID * @param {Array or Object} senderPrivKeys List of sender private-key (optional: with coins to be sent) * @param {Object} receivers List of receivers with respective amount to be sent * @param {string} floData FLO data of the txn * @return {Promise} */ const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function (senderPrivKeys, receivers, floData = '') { return new Promise((resolve, reject) => { if (!floCrypto.validateASCII(floData)) return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); let senders = {}, preserveRatio; //check for argument validations try { let invalids = { InvalidSenderPrivKeys: [], InvalidSenderAmountFor: [], InvalidReceiverIDs: [], InvalidReceiveAmountFor: [] } let inputVal = 0, outputVal = 0; //Validate sender privatekeys (and send amount if passed) //conversion when only privateKeys are passed (preserveRatio mode) if (Array.isArray(senderPrivKeys)) { senderPrivKeys.forEach(key => { try { if (!key) invalids.InvalidSenderPrivKeys.push(key); else { let floID = floCrypto.getFloID(key); senders[floID] = { wif: key } } } catch (error) { invalids.InvalidSenderPrivKeys.push(key) } }) preserveRatio = true; } //conversion when privatekeys are passed with send amount else { for (let key in senderPrivKeys) { try { if (!key) invalids.InvalidSenderPrivKeys.push(key); else { if (typeof senderPrivKeys[key] !== 'number' || senderPrivKeys[key] <= 0) invalids.InvalidSenderAmountFor.push(key); else inputVal += senderPrivKeys[key]; let floID = floCrypto.getFloID(key); senders[floID] = { wif: key, coins: senderPrivKeys[key] } } } catch (error) { invalids.InvalidSenderPrivKeys.push(key) } } preserveRatio = false; } //Validate the receiver IDs and receive amount for (let floID in receivers) { if (!floCrypto.validateFloID(floID)) invalids.InvalidReceiverIDs.push(floID); if (typeof receivers[floID] !== 'number' || receivers[floID] <= 0) invalids.InvalidReceiveAmountFor.push(floID); else outputVal += receivers[floID]; } //Reject if any invalids are found for (let i in invalids) if (!invalids[i].length) delete invalids[i]; if (Object.keys(invalids).length) return reject(invalids); //Reject if given inputVal and outputVal are not equal if (!preserveRatio && inputVal != outputVal) return reject(`Input Amount (${inputVal}) not equal to Output Amount (${outputVal})`); } catch (error) { return reject(error) } //Get balance of senders let promises = []; for (let floID in senders) promises.push(getBalance(floID)); Promise.all(promises).then(results => { let totalBalance = 0, totalFee = DEFAULT.fee, balance = {}; //Divide fee among sender if not for preserveRatio if (!preserveRatio) var dividedFee = totalFee / Object.keys(senders).length; //Check if balance of each sender is sufficient enough let insufficient = []; for (let floID in senders) { balance[floID] = parseFloat(results.shift()); if (isNaN(balance[floID]) || (preserveRatio && balance[floID] <= totalFee) || (!preserveRatio && balance[floID] < senders[floID].coins + dividedFee)) insufficient.push(floID); totalBalance += balance[floID]; } if (insufficient.length) return reject({ InsufficientBalance: insufficient }) //Calculate totalSentAmount and check if totalBalance is sufficient let totalSendAmt = totalFee; for (let floID in receivers) totalSendAmt += receivers[floID]; if (totalBalance < totalSendAmt) return reject("Insufficient total Balance"); //Get the UTXOs of the senders let promises = []; for (let floID in senders) promises.push(getUTXOs(floID)); Promise.all(promises).then(results => { var trx = bitjs.transaction(); for (let floID in senders) { let utxos = results.shift(); let sendAmt; if (preserveRatio) { let ratio = (balance[floID] / totalBalance); sendAmt = totalSendAmt * ratio; } else sendAmt = senders[floID].coins + dividedFee; let utxoAmt = 0.0; for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < sendAmt); i--) { if (utxos[i].confirmations) { trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); utxoAmt += utxos[i].amount; } } if (utxoAmt < sendAmt) return reject("Insufficient balance:" + floID); let change = (utxoAmt - sendAmt); if (change > 0) trx.addoutput(floID, change); } for (let floID in receivers) trx.addoutput(floID, receivers[floID]); trx.addflodata(floData.replace(/\n/g, ' ')); for (let floID in senders) trx.sign(senders[floID].wif, 1); var signedTxHash = trx.serialize(); broadcastTx(signedTxHash) .then(txid => resolve(txid)) .catch(error => reject(error)) }).catch(error => reject(error)) }).catch(error => reject(error)) }) } //Create a multisig transaction const createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { var multisig = floCrypto.decodeRedeemScript(redeemScript); //validate multisig script and flodata if (!multisig) return reject(`Invalid redeemScript`); var senderAddr = multisig.address; if (!floCrypto.validateFloID(senderAddr)) return reject(`Invalid multisig : ${senderAddr}`); else if (!floCrypto.validateASCII(floData)) return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); //validate receiver addresses if (!Array.isArray(receivers)) receivers = [receivers]; for (let r of receivers) if (!floCrypto.validateFloID(r)) return reject(`Invalid address : ${r}`); //validate amounts if (!Array.isArray(amounts)) amounts = [amounts]; if (amounts.length != receivers.length) return reject("Receivers and amounts have different length"); var sendAmt = 0; for (let a of amounts) { if (typeof a !== 'number' || a <= 0) return reject(`Invalid amount : ${a}`); sendAmt += a; } getBalance(senderAddr).then(balance => { var fee = DEFAULT.fee; if (balance < sendAmt + fee) return reject("Insufficient FLO balance!"); getUTXOs(senderAddr).then(utxos => { //form/construct the transaction data var trx = bitjs.transaction(); var utxoAmt = 0.0; for (var i = utxos.length - 1; (i >= 0) && (utxoAmt < sendAmt + fee); i--) { //use only utxos with confirmations (strict_utxo mode) if (utxos[i].confirmations || !strict_utxo) { trx.addinput(utxos[i].txid, utxos[i].vout, redeemScript); //for multisig, script=redeemScript utxoAmt += utxos[i].amount; }; } if (utxoAmt < sendAmt + fee) reject("Insufficient FLO: Some UTXOs are unconfirmed"); else { for (let i in receivers) trx.addoutput(receivers[i], amounts[i]); var change = utxoAmt - sendAmt - fee; if (change > DEFAULT.minChangeAmt) trx.addoutput(senderAddr, change); trx.addflodata(floData.replace(/\n/g, ' ')); resolve(trx); } }).catch(error => reject(error)) }).catch(error => reject(error)) }); } //Same as above, but explict call should return serialized tx-hex floBlockchainAPI.createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo) .then(trx => resolve(trx.serialize())) .catch(error => reject(error)) }) } //Create and send multisig transaction const sendMultisigTx = floBlockchainAPI.sendMultisigTx = function (redeemScript, privateKeys, receivers, amounts, floData = '', strict_utxo = true) { return new Promise((resolve, reject) => { var multisig = floCrypto.decodeRedeemScript(redeemScript); if (!multisig) return reject(`Invalid redeemScript`); if (privateKeys.length < multisig.required) return reject(`Insufficient privateKeys (required ${multisig.required})`); for (let pk of privateKeys) { var flag = false; for (let pub of multisig.pubkeys) if (floCrypto.verifyPrivKey(pk, pub, false)) flag = true; if (!flag) return reject(`Invalid Private key`); } createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo).then(trx => { for (let pk of privateKeys) trx.sign(pk, 1); var signedTxHash = trx.serialize(); broadcastTx(signedTxHash) .then(txid => resolve(txid)) .catch(error => reject(error)) }).catch(error => reject(error)) }) } floBlockchainAPI.writeMultisigData = function (redeemScript, data, privatekeys, receiverAddr = DEFAULT.receiverID, options = {}) { let strict_utxo = options.strict_utxo === false ? false : true, sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt; return new Promise((resolve, reject) => { if (!floCrypto.validateFloID(receiverAddr)) return reject(`Invalid receiver: ${receiverAddr}`); sendMultisigTx(redeemScript, privatekeys, receiverAddr, sendAmt, data, strict_utxo) .then(txid => resolve(txid)) .catch(error => reject(error)) }) } function deserializeTx(tx) { if (typeof tx === 'string' || Array.isArray(tx)) { try { tx = bitjs.transaction(tx); } catch { throw "Invalid transaction hex"; } } else if (typeof tx !== 'object' || typeof tx.sign !== 'function') throw "Invalid transaction object"; return tx; } floBlockchainAPI.signTx = function (tx, privateKey, sighashtype = 1) { if (!floCrypto.getFloID(privateKey)) throw "Invalid Private key"; //deserialize if needed tx = deserializeTx(tx); var signedTxHex = tx.sign(privateKey, sighashtype); return signedTxHex; } const checkSigned = floBlockchainAPI.checkSigned = function (tx, bool = true) { tx = deserializeTx(tx); let n = []; for (let i = 0; i < tx.inputs.length; i++) { var s = tx.scriptDecode(i); if (s['type'] === 'scriptpubkey') n.push(s.signed); else if (s['type'] === 'multisig') { var rs = tx.decodeRedeemScript(s['rs']); let x = { s: 0, r: rs['required'], t: rs['pubkeys'].length }; //check input script for signatures var script = Array.from(tx.inputs[i].script); if (script[0] == 0) { //script with signatures script = tx.parseScript(script); for (var k = 0; k < script.length; k++) if (Array.isArray(script[k]) && script[k][0] == 48) //0x30 DERSequence x.s++; } //validate counts if (x.r > x.t) throw "signaturesRequired is more than publicKeys"; else if (x.s < x.r) n.push(x); else n.push(true); } } return bool ? !(n.filter(x => x !== true).length) : n; } floBlockchainAPI.checkIfSameTx = function (tx1, tx2) { tx1 = deserializeTx(tx1); tx2 = deserializeTx(tx2); //compare input and output length if (tx1.inputs.length !== tx2.inputs.length || tx1.outputs.length !== tx2.outputs.length) return false; //compare flodata if (tx1.floData !== tx2.floData) return false //compare inputs for (let i = 0; i < tx1.inputs.length; i++) if (tx1.inputs[i].outpoint.hash !== tx2.inputs[i].outpoint.hash || tx1.inputs[i].outpoint.index !== tx2.inputs[i].outpoint.index) return false; //compare outputs for (let i = 0; i < tx1.outputs.length; i++) if (tx1.outputs[i].value !== tx2.outputs[i].value || Crypto.util.bytesToHex(tx1.outputs[i].script) !== Crypto.util.bytesToHex(tx2.outputs[i].script)) return false; return true; } floBlockchainAPI.transactionID = function (tx) { tx = deserializeTx(tx); let clone = bitjs.clone(tx); let raw_bytes = Crypto.util.hexToBytes(clone.serialize()); let txid = Crypto.SHA256(Crypto.SHA256(raw_bytes, { asBytes: true }), { asBytes: true }).reverse(); return Crypto.util.bytesToHex(txid); } const getTxOutput = (txid, i) => new Promise((resolve, reject) => { promisedAPI(`api/tx/${txid}`) .then(result => resolve(result.vout[i])) .catch(error => reject(error)) }); function getOutputAddress(outscript) { var bytes, version; switch (outscript[0]) { case 118: //legacy bytes = outscript.slice(3, outscript.length - 2); version = bitjs.pub; break case 169: //multisig bytes = outscript.slice(2, outscript.length - 1); version = bitjs.multisig; break; default: return; //unknown } bytes.unshift(version); var hash = Crypto.SHA256(Crypto.SHA256(bytes, { asBytes: true }), { asBytes: true }); var checksum = hash.slice(0, 4); return bitjs.Base58.encode(bytes.concat(checksum)); } floBlockchainAPI.parseTransaction = function (tx) { return new Promise((resolve, reject) => { tx = deserializeTx(tx); let result = {}; let promises = []; //Parse Inputs for (let i = 0; i < tx.inputs.length; i++) promises.push(getTxOutput(tx.inputs[i].outpoint.hash, tx.inputs[i].outpoint.index)); Promise.all(promises).then(inputs => { result.inputs = inputs.map(inp => Object({ address: inp.scriptPubKey.addresses[0], value: parseFloat(inp.value) })); let signed = checkSigned(tx, false); result.inputs.forEach((inp, i) => inp.signed = signed[i]); //Parse Outputs result.outputs = tx.outputs.map(out => Object({ address: getOutputAddress(out.script), value: util.Sat_to_FLO(out.value) })) //Parse Totals result.total_input = parseFloat(result.inputs.reduce((a, inp) => a += inp.value, 0).toFixed(8)); result.total_output = parseFloat(result.outputs.reduce((a, out) => a += out.value, 0).toFixed(8)); result.fee = parseFloat((result.total_input - result.total_output).toFixed(8)); result.floData = tx.floData; resolve(result); }).catch(error => reject(error)) }) } //Broadcast signed Tx in blockchain using API const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) { return new Promise((resolve, reject) => { if (signedTxHash.length < 1) return reject("Empty Transaction Data"); promisedAPI('/api/sendtx/' + signedTxHash) .then(response => resolve(response["result"])) .catch(error => reject(error)) }) } const getTx = floBlockchainAPI.getTx = function (txid) { return new Promise((resolve, reject) => { promisedAPI(`api/tx/${txid}`) .then(response => resolve(response)) .catch(error => reject(error)) }) } /**Wait for the given txid to get confirmation in blockchain * @param {string} txid of the transaction to wait for * @param {int} max_retry: maximum number of retries before exiting wait. negative number = Infinite retries (DEFAULT: -1 ie, infinite retries) * @param {Array} retry_timeout: time (seconds) between retries (DEFAULT: 20 seconds) * @return {Promise} resolves when tx gets confirmation */ const waitForConfirmation = floBlockchainAPI.waitForConfirmation = function (txid, max_retry = -1, retry_timeout = 20) { return new Promise((resolve, reject) => { setTimeout(function () { getTx(txid).then(tx => { if (!tx) return reject("Transaction not found"); if (tx.confirmations) return resolve(tx); else if (max_retry === 0) //no more retries return reject("Waiting timeout: tx still not confirmed"); else { max_retry = max_retry < 0 ? -1 : max_retry - 1; //decrease retry count (unless infinite retries) waitForConfirmation(txid, max_retry, retry_timeout) .then(result => resolve(result)) .catch(error => reject(error)) } }).catch(error => reject(error)) }, retry_timeout * 1000) }) } //Read Txs of Address const readTxs = floBlockchainAPI.readTxs = function (addr, options = {}) { return new Promise((resolve, reject) => { //API options let query_params = { details: 'txs' }; //page options if (!isUndefined(options.page) && Number.isInteger(options.page)) query_params.page = options.page; if (!isUndefined(options.pageSize) && Number.isInteger(options.pageSize)) query_params.pageSize = options.pageSize; //only confirmed tx if (options.confirmed) //Default is false in server, so only add confirmed filter if confirmed has a true value query_params.confirmed = true; promisedAPI(`api/address/${addr}`, query_params).then(response => { if (!Array.isArray(response.txs)) //set empty array if address doesnt have any tx response.txs = []; resolve(response) }).catch(error => reject(error)) }); } //backward support (floBlockchainAPI < v2.5.6) function readAllTxs_oldSupport(addr, options, ignoreOld = 0, cacheTotal = 0) { return new Promise((resolve, reject) => { readTxs(addr, options).then(response => { cacheTotal += response.txs.length; let n_remaining = response.txApperances - cacheTotal if (n_remaining < ignoreOld) { // must remove tx that would have been fetch during prev call let n_remove = ignoreOld - n_remaining; resolve(response.txs.slice(0, -n_remove)); } else if (response.page == response.totalPages) //last page reached resolve(response.txs); else { options.page = response.page + 1; readAllTxs_oldSupport(addr, options, ignoreOld, cacheTotal) .then(result => resolve(response.txs.concat(result))) .catch(error => reject(error)) } }).catch(error => reject(error)) }) } function readAllTxs_new(addr, options, lastItem) { return new Promise((resolve, reject) => { readTxs(addr, options).then(response => { let i = response.txs.findIndex(t => t.txid === lastItem); if (i != -1) //found lastItem resolve(response.txs.slice(0, i)) else if (response.page == response.totalPages) //last page reached resolve(response.txs); else { options.page = response.page + 1; readAllTxs_new(addr, options, lastItem) .then(result => resolve(response.txs.concat(result))) .catch(error => reject(error)) } }).catch(error => reject(error)) }) } //Read All Txs of Address (newest first) const readAllTxs = floBlockchainAPI.readAllTxs = function (addr, options = {}) { return new Promise((resolve, reject) => { if (Number.isInteger(options.ignoreOld)) //backward support: data from floBlockchainAPI < v2.5.6 readAllTxs_oldSupport(addr, options, options.ignoreOld).then(txs => { let last_tx = txs.find(t => t.confirmations > 0); let new_lastItem = last_tx ? last_tx.txid : options.ignoreOld; resolve({ lastItem: new_lastItem, items: txs }) }).catch(error => reject(error)) else //New format for floBlockchainAPI >= v2.5.6 readAllTxs_new(addr, options, options.after).then(txs => { let last_tx = txs.find(t => t.confirmations > 0); let new_lastItem = last_tx ? last_tx.txid : options.after; resolve({ lastItem: new_lastItem, items: txs }) }).catch(error => reject(error)) }) } /*Read flo Data from txs of given Address options can be used to filter data after : query after the given txid confirmed : query only confirmed tx or not (options same as readAllTx, DEFAULT=true: only_confirmed_tx) ignoreOld : ignore old txs (deprecated: support for backward compatibility only, cannot be used with 'after') sentOnly : filters only sent data receivedOnly: filters only received data pattern : filters data that with JSON pattern filter : custom filter funtion for floData (eg . filter: d => {return d[0] == '$'}) tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details) sender : flo-id(s) of sender receiver : flo-id(s) of receiver */ floBlockchainAPI.readData = function (addr, options = {}) { return new Promise((resolve, reject) => { //fetch options let query_options = {}; query_options.confirmed = isUndefined(options.confirmed) ? true : options.confirmed; //DEFAULT: ignore unconfirmed tx if (!isUndefined(options.after)) query_options.after = options.after; else if (!isUndefined(options.ignoreOld)) query_options.ignoreOld = options.ignoreOld; readAllTxs(addr, query_options).then(response => { if (typeof options.senders === "string") options.senders = [options.senders]; if (typeof options.receivers === "string") options.receivers = [options.receivers]; //filter the txs based on options const filteredData = response.items.filter(tx => { if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query return false; if (options.sentOnly && !tx.vin.some(vin => vin.addresses[0] === addr)) return false; else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addresses[0]))) return false; if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr)) return false; else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0]))) return false; if (options.pattern) { try { let jsonContent = JSON.parse(tx.floData); if (!Object.keys(jsonContent).includes(options.pattern)) return false; } catch { return false; } } if (options.filter && !options.filter(tx.floData)) return false; return true; }).map(tx => options.tx ? { txid: tx.txid, time: tx.time, blockheight: tx.blockheight, senders: new Set(tx.vin.map(v => v.addresses[0])), receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])), data: tx.floData } : tx.floData); const result = { lastItem: response.lastItem }; if (options.tx) result.items = filteredData; else result.data = filteredData resolve(result); }).catch(error => reject(error)) }) } /*Get the latest flo Data that match the caseFn from txs of given Address caseFn: (function) flodata => return bool value options can be used to filter data after : query after the given txid confirmed : query only confirmed tx or not (options same as readAllTx, DEFAULT=true: only_confirmed_tx) sentOnly : filters only sent data receivedOnly: filters only received data tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details) sender : flo-id(s) of sender receiver : flo-id(s) of receiver */ const getLatestData = floBlockchainAPI.getLatestData = function (addr, caseFn, options = {}) { return new Promise((resolve, reject) => { //fetch options let query_options = {}; query_options.confirmed = isUndefined(options.confirmed) ? true : options.confirmed; //DEFAULT: confirmed tx only if (!isUndefined(options.page)) query_options.page = options.page; //if (!isUndefined(options.after)) query_options.after = options.after; let new_lastItem; readTxs(addr, query_options).then(response => { //lastItem confirmed tx checked if (!new_lastItem) { let last_tx = response.items.find(t => t.confirmations > 0); if (last_tx) new_lastItem = last_tx.txid; } if (typeof options.senders === "string") options.senders = [options.senders]; if (typeof options.receivers === "string") options.receivers = [options.receivers]; //check if `after` txid is in the response let i_after = response.txs.findIndex(t => t.txid === options.after); if (i_after != -1) //found lastItem, hence remove it and all txs before that response.items.splice(i_after); var item = response.items.find(tx => { if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query return false; if (options.sentOnly && !tx.vin.some(vin => vin.addresses[0] === addr)) return false; else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addresses[0]))) return false; if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr)) return false; else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0]))) return false; return caseFn(tx.floData) ? true : false; //return only bool for find fn }); //if item found, then resolve the result if (!isUndefined(item)) { const result = { lastItem: new_lastItem || item.txid }; if (options.tx) { result.item = { txid: item.txid, time: item.time, blockheight: item.blockheight, senders: new Set(item.vin.map(v => v.addresses[0])), receivers: new Set(item.vout.map(v => v.scriptPubKey.addresses[0])), data: item.floData } } else result.data = item.floData; return resolve(result); } if (response.page == response.totalPages || i_after != -1) //reached last page to check resolve({ lastItem: new_lastItem || options.after }); //no data match the caseFn, resolve just the lastItem //else if address needs chain query else { options.page = response.page + 1; getLatestData(addr, caseFn, options) .then(result => resolve(result)) .catch(error => reject(error)) } }).catch(error => reject(error)) }) } })('object' === typeof module ? module.exports : window.floBlockchainAPI = {});