Workflow updating files of flopay
21
flopay/LICENCE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Sai Raj
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
73
flopay/README.md
Normal file
@ -0,0 +1,73 @@
|
||||
# FLO Pay
|
||||
FLO Pay simplifies the process of verifying payments with third parties. This platform departs from the conventional monthly-based system and instead adopts a task-based approach. To maintain exceptional output quality, all performance evaluations are recorded on the blockchain, guaranteeing transparency and reliability throughout the internship journey.
|
||||
|
||||
In addition to making payments in Rupees, users can utilize their Rupee tokens to purchase or engage with RanchiMall's financial products, including the Exchange Market and Blockchain Bonds. It's important to note that 1 Rupee token always equals 1 INR.
|
||||
|
||||
Individuals who possess Rupee tokens in their FLO blockchain address or FLO ID have the ability to convert them into Rupee cash. This conversion process is facilitated by an open and authorized network of cashiers who possess their own FLO ID.
|
||||
|
||||
### Live URL for FLO Pay:
|
||||
*https://ranchimall.github.io/flopay/*
|
||||
|
||||
## Instructions to use
|
||||
Note: FLO Pay uses IndedxedDB for storing data, which means data is stored in the respective browser you use to open the web wallet. Data stored by one browser can't be accessed by other browsers.
|
||||
|
||||
### Sign in
|
||||
Sign in using a FLO or BTC private key. Upon signing in, the FLO, BTC, and, Rupee balance of the FLO ID will reflect on the dashboard. To add FLO or BTC to your FLO Pay wallet, a top-up (deposit) of Rupee tokens is required. Deposited Rupee tokens can be further used to buy FLO or BTC from the Exchange market.
|
||||
|
||||
### Send
|
||||
i) Select Rupee to send/transfer Rupee tokens
|
||||
ii) Click on the "Send" button
|
||||
iii) Enter the receiver's FLO address
|
||||
iv) Enter the amount to be transferred
|
||||
v) An additional message can be added to the transfer
|
||||
vi) Click on "Send" to confirm the transfer
|
||||
vii) Select BTC for the same operations for a Bitcoin transfer
|
||||
|
||||
### Request
|
||||
i) Rupee tokens can be requested from other FLO addresses
|
||||
ii) Click on the "Request" button
|
||||
iii) Enter the FLO address from which the request has to be made
|
||||
iv) Enter the amount
|
||||
v) Add an additional message with the request
|
||||
vi) Click on "Request" for the request to be sent
|
||||
|
||||
### Top-up wallet
|
||||
i) Top-up is used to deposit Rupee tokens to the FLO Pay wallet for further usages like buying other digital assets or Bitcoin, and also to participate in RanchiMall financial products such as Exchange Market, Blockchain Bonds, Bitcoin Bonds, etc
|
||||
ii) Click on the "Top-up" button
|
||||
iii) Enter the amount of the top-up to be done on the signed-in FLO ID
|
||||
iv) Upon continuing, an authorized cashier payment detail that accepts Rupee cash will appear
|
||||
v) Follow the steps to make the payment and the equivalent rupee tokens will be added to the FLO Pay wallet of the FLO ID
|
||||
|
||||
### Withdraw
|
||||
i) Rupee tokens in the FLO Pay wallet can be withdrawn to the user's bank account
|
||||
ii) Click on the "Withdraw' button
|
||||
iii) Enter the amount to be withdrawn
|
||||
iv) Setup your receiving bank details by adding a UPI address
|
||||
v) Click on Withdraw
|
||||
vi) The withdrawal amount will be sent to the UPI ID from an authorized Cashier
|
||||
|
||||
### Convert to BTC
|
||||
Rupee tokens in the FLO Pay wallet can be converted to BTC
|
||||
|
||||
### Convert to Rupee
|
||||
Available BTC in the FLO Pay wallet can be converted to Rupee tokens
|
||||
|
||||
### View top-up and withdrawal history
|
||||
Click to view all the top-ups and withdrawals done from the FLO address
|
||||
|
||||
### Send Rupee tokens to other RanchiMall products
|
||||
i) Products like Exchange Market, Blockchain Bonds, and Bob's Fund require Rupee tokens to participate
|
||||
ii) Exchange Market enables trading of FLO and BTC. To buy FLO or BTC at the market rate, Rupee tokens are required
|
||||
iii) To send Rupee tokens to these products, click on the product name, enter the amount, and click on the "Transfer" button
|
||||
|
||||
### Contacts
|
||||
i) FLO addresses or BTC addresses can be added as contacts for future ease of use
|
||||
|
||||
#### Note:
|
||||
**For security, data are not stored in any server but rather stored in the IndexDB (the web browser's storage)
|
||||
Contacts saved in a web browser cannot be accessed through another web browser**
|
||||
|
||||
ii) Click on the "Add FLO address" button
|
||||
iii) Enter the FLO or BTC address
|
||||
iv) Add a name for the contact
|
||||
v) Click on "Save"
|
||||
1
flopay/css/Asset 1.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="807.02" height="429.87" viewBox="0 0 807.02 429.87"><defs><style>.a{fill:#e6e6e6;}</style></defs><title>Asset 1</title><path class="a" d="M298.86,193c-.48-12.19,59.59-31,100.69-42.4A16.91,16.91,0,0,0,404,120L234.06,13.35,0,140.73s190.43,1,118.74,1.5L104.46,400.35c51.53-8.08,112-22.6,115.56-49.68,3.07-23.38-38.57-38.65-34.56-59.4C193.05,252,350.84,261.79,355,233,357.05,219,299.57,211,298.86,193Z"/><path class="a" d="M699.14,106.56H807L637.16,0,462.82,94.88A27.08,27.08,0,0,0,449,118.35a.49.49,0,0,0,0,.12c-1,14.78,42.47,27.38,40,47.52-2.85,22.87-61.39,26.9-61.56,42.12-.23,20.48,105.68,27.94,108,55.08,1.84,21.5-63.59,28.79-67,59.4-4.27,38.75,94.82,79.49,148,95,37.17,10.88,55.76,16.32,71.28,8.64,60.74-30.06,36.62-191.63,30.24-234.36A643.24,643.24,0,0,0,699.14,106.56Z"/></svg>
|
||||
|
After Width: | Height: | Size: 828 B |
1
flopay/css/back.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"><defs><style>.a{fill:#ffcdab;}.b{fill:#fff4c5;}</style></defs><title>back</title><path class="a" d="M1343.5-.5c33.54,48.75,50.34,166,277.18,193.18C1808.39,215.21,1899.21,313.38,1920,364c-.33-146.23-.17-218.31-.5-364.54Z"/><path class="b" d="M1343.5-.5a288.26,288.26,0,0,0,159,88.78c48.38,9.32,76.65,13.77,132.58,22.68,93.5,14.9,141.37,56.9,170.42,80.54a466.36,466.36,0,0,1,115,139q-.49-165.49-1-331Z"/></svg>
|
||||
|
After Width: | Height: | Size: 499 B |
1
flopay/css/bg-art-5.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 240 240"><defs><style>.a{fill:url(#a);}.b{fill:url(#b);}.c{fill:url(#c);}</style><linearGradient id="a" x1="144.49" y1="221.31" x2="241.6" y2="157.88" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#fff"/></linearGradient><linearGradient id="b" x1="90.96" y1="190.69" x2="180.52" y2="132.19" gradientTransform="matrix(-0.36, 0.73, -0.7, -0.35, 326.09, 97.01)" xlink:href="#a"/><linearGradient id="c" x1="32.7" y1="395.41" x2="125.65" y2="334.7" gradientTransform="matrix(0.86, -0.58, 0.56, 0.83, -241, -210.66)" xlink:href="#a"/></defs><circle class="a" cx="192.35" cy="190.05" r="57.99"/><circle class="b" cx="163.17" cy="139.1" r="43.16"/><circle class="c" cx="30.93" cy="46.87" r="57.09"/></svg>
|
||||
|
After Width: | Height: | Size: 860 B |
1
flopay/css/bg-art1.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080"><defs><style>.a{fill:#1b1464;}.b{fill:#09083f;}.c{fill:#29abe2;}</style></defs><title>bg-art1</title><polygon class="a" points="0 957.24 232 957.24 357 828 434 899 485 867 594 959 843 957 889 925 938 953 1301 953 1447 807 1555 915 1590 880 1623 913 1673 856 1744 957 1920 957.24 1920 1080 0 1080 0 957.24"/><polygon class="b" points="495 959 594 959 485 867 434 899 495 959"/><polygon class="c" points="232 957 357 828 247 957 232 957"/><polygon class="c" points="1301 953 1447 807 1318.32 953 1301 953"/></svg>
|
||||
|
After Width: | Height: | Size: 602 B |
33
flopay/css/bg-art2.svg
Normal file
@ -0,0 +1,33 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<g filter="url(#filter0_d)">
|
||||
<rect x="312" y="323" width="240" height="140" rx="18" transform="rotate(30 312 323)" fill="#1C783B"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d)">
|
||||
<rect x="431.283" y="302" width="240" height="140" rx="18" transform="rotate(50.0235 431.283 302)" fill="#2DBD5E"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="225" y="320" width="301.846" height="265.244" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-5" dy="9"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d" x="307" y="299" width="285.477" height="297.86" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-5" dy="9"/>
|
||||
<feGaussianBlur stdDeviation="6"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
33
flopay/css/bg-art3.svg
Normal file
@ -0,0 +1,33 @@
|
||||
<svg width="1920" height="1080" viewBox="0 0 1920 1080" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<g filter="url(#filter0_d)">
|
||||
<circle cx="1814.5" cy="977.5" r="437.5" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d)">
|
||||
<circle cx="1814.5" cy="977.5" r="243.5" fill="#1B0980"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="1344" y="515" width="913" height="913" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-14" dy="-6"/>
|
||||
<feGaussianBlur stdDeviation="9.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d" x="1538" y="709" width="525" height="525" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-14" dy="-6"/>
|
||||
<feGaussianBlur stdDeviation="9.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0">
|
||||
<rect width="1920" height="1080" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
34
flopay/css/bg-art4.svg
Normal file
@ -0,0 +1,34 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<rect width="512" height="512" fill="#DE7EFF"/>
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M221.779 309.294C48.3794 380.575 -58.7347 516.954 -91.1719 575.626L-100 0.193375L564.422 -10L566.853 148.481C506.54 258.92 398.685 236.571 221.779 309.294Z" fill="#F1BAFF"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d)">
|
||||
<path d="M155.507 228.837C0.0457989 333.625 -77.3623 488.805 -97.3001 552.812L-222 -9.0155L426.713 -153L461.057 1.73443C424.257 122.067 314.11 121.931 155.507 228.837Z" fill="#FAE6FF"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="-100" y="-26" width="715.25" height="625.626" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="22" dy="4"/>
|
||||
<feGaussianBlur stdDeviation="10"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_d" x="-222" y="-156" width="815.413" height="743.812" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="23" dy="16"/>
|
||||
<feGaussianBlur stdDeviation="9.5"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||
</filter>
|
||||
<clipPath id="clip0">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
1
flopay/css/card-art1.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><defs><style>.a,.b{fill:none;stroke-miterlimit:10;}.a{stroke:#fff;stroke-width:2px;opacity:0.8;}.b{stroke:#c1272d;opacity:0.6;}</style></defs><title>card-art1</title><circle class="a" cx="64" cy="64" r="19"/><path class="b" d="M9.32,53.55c1,.55,2.43,1.2,3.16.63,1.18-.9-.57-4,.64-5.07s5,1.52,5.7.64c.47-.62-1.18-2.18-.64-3.17.4-.73,1.79-.78,3.17-.63"/></svg>
|
||||
|
After Width: | Height: | Size: 441 B |
1
flopay/css/card-back.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512 512"><defs><style>.a{fill:url(#a);}.b{fill:#e6e6e6;}.c{fill:#ccc;}</style><radialGradient id="a" cx="348.29" cy="74.1" fx="157.24155640757456" fy="29.04343660679026" r="476.25" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4a9ac6"/><stop offset="1" stop-color="#f2f2f2"/></radialGradient></defs><title>card-back</title><path class="a" d="M-107.16,293.52C45.19,273,156.39,282.59,232,296.31c18,3.27,67,12.77,130.64,5.53C397,297.93,509.84,285,538.18,216c6-14.53,4.59-37,1.58-81.25-1.53-22.54-7-103.17-17.28-132.84C483.09-111.8,116.85-217.84-46.68-32.64-152.11,86.76-113.71,265.09-107.16,293.52Z"/><path class="b" d="M261.88,223.61C314.62,260.23,369,281.1,417.63,293.05c-16.36,3.16-41.33,7.51-72,10.84-49.24,5.34-68.61,3.94-126.62,5.83-68.51,2.23-141.69,10-141.84,18.48C77.08,332.63,97,335,96.6,338.52c-.94,7.9-99.11,17.06-122.64,25-.16.06-8.08,5.28-8.4,5.28-1.21,0-2.28-2.28-3.33-6.15L49.08,193.13,70.32,170l25.14-49.93a14.19,14.19,0,0,1,8.19,2.15l139,87.18Q251.87,216.63,261.88,223.61Z"/><path class="c" d="M95.46,120.11,70.32,170,49.08,193.13-37.77,362.61C-44.5,337.73-50.1,245.78-82.25,245.55c-14.7-.1-19.62-19.7-6.71-26.73l178.25-97A14,14,0,0,1,95.46,120.11Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
flopay/css/favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><title>favicon</title><path d="M112,0H16A16,16,0,0,0,0,16v96a16,16,0,0,0,16,16h96a16,16,0,0,0,16-16V16A16,16,0,0,0,112,0ZM64.65,79.32H44.06L57.94,62.16h4.68c9.28,0,15.64-4.83,15.64-12.33S71.9,37.5,62.62,37.5H49.11V67.26L37.54,82l11.57,0V98.83l-18.69,8.84V20.33H64.65c18.95,0,32.93,12.21,32.93,29.5S83.6,79.32,64.65,79.32Z"/></svg>
|
||||
|
After Width: | Height: | Size: 417 B |
1
flopay/css/focus.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><title>focus</title><g style="opacity:0.5"><path d="M64,0a64,64,0,1,0,64,64A64,64,0,0,0,64,0Zm0,89A25,25,0,1,1,89,64,25,25,0,0,1,64,89Z" style="fill:#562787"/></g><g style="opacity:0.5"><path d="M64,20a44,44,0,1,0,44,44A44,44,0,0,0,64,20Zm0,69A25,25,0,1,1,89,64,25,25,0,0,1,64,89Z" style="fill:#6b31a8"/></g></svg>
|
||||
|
After Width: | Height: | Size: 401 B |
1863
flopay/css/main.css
Normal file
1
flopay/css/main.min.css
vendored
Normal file
1756
flopay/css/main.scss
Normal file
1
flopay/css/sign-in-bg.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1920" height="1080" viewBox="0 0 1920 1080"><defs><style>.a,.b,.c{opacity:0.6;}.a{fill:url(#a);}.b{fill:url(#b);}.c{fill:url(#c);}</style><linearGradient id="a" x1="222.28" y1="490.07" x2="392.81" y2="490.07" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#662d91"/><stop offset="1" stop-color="#ff7bac"/></linearGradient><linearGradient id="b" x1="-116.86" y1="38.37" x2="869.09" y2="38.37" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b1464"/><stop offset="1" stop-color="#ed1e79"/></linearGradient><linearGradient id="c" x1="1406.14" y1="835.71" x2="1784.51" y2="1294.07" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#d4145a"/><stop offset="0.99" stop-color="#f7931e"/></linearGradient></defs><title>sign-in-bg</title><path class="a" d="M226.53,423.18c-19.65,20.72,31.9,140.63,92.68,136.47,43.87-3,84.77-70.12,70.82-94.66-5.28-9.29-16.61-9.31-44.3-14.22C260.17,435.57,236.83,412.31,226.53,423.18Z"/><path class="b" d="M-110.68-19.48c28.79-111.07,152-161.1,230.4-193,285.16-115.83,551.79,33.94,599,60.48,63.85,35.86,158.51,89,149.76,135.36-16.21,85.87-359.81.52-576,210.24-75.17,72.92-83,131.12-144,138.24C27.63,346-152.55,142-110.68-19.48Z"/><path class="c" d="M717.8,1107.08c-17.52-81.33,84.59-196.52,201.6-239,242.11-88,432.84,177,601.92,83.52C1634.54,889,1583.59,751.06,1757.48,580c43.66-42.94,163.34-160.65,241.92-129.6,116.44,46,165.06,427.55-5.76,650.88-204.9,267.88-643.48,200.88-921.6,158.4C945.66,1240.41,739.74,1209,717.8,1107.08Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
flopay/css/success-art.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128"><defs><style>.a{fill:#d4145a;}.b{fill:#2e3192;}.c{fill:#fff;}.d{fill:#662d91;}.e{fill:#9e005d;}.f{fill:#00a99d;}</style></defs><title>success-art</title><polygon class="a" points="87.96 46.76 59.68 69.04 48.54 54.9 43.83 58.62 54.97 72.75 58.68 77.47 91.67 51.47 87.96 46.76"/><polygon class="b" points="84.33 42.6 61.5 70.43 47.58 59.02 43.78 63.66 57.7 75.07 62.33 78.88 88.97 46.41 84.33 42.6"/><polygon class="c" points="86.09 44.39 60.64 69.85 47.91 57.12 43.66 61.36 56.39 74.09 60.64 78.33 90.33 48.64 86.09 44.39"/><circle class="a" cx="27" cy="33" r="2"/><circle class="a" cx="111.5" cy="93.5" r="5.5"/><circle class="d" cx="47" cy="88" r="2"/><circle class="d" cx="22.5" cy="82.5" r="4.5"/><circle class="d" cx="45.5" cy="114.5" r="3.5"/><circle class="d" cx="82" cy="18" r="5"/><circle class="e" cx="102.5" cy="51.5" r="1.5"/><circle class="e" cx="60.5" cy="37.5" r="2.5"/><circle class="e" cx="103" cy="77" r="3"/><circle class="e" cx="87.5" cy="108.5" r="3.5"/><circle class="f" cx="39" cy="20" r="3"/><circle class="f" cx="85" cy="83" r="2"/><circle class="f" cx="32.5" cy="101.5" r="3.5"/><circle class="f" cx="112" cy="113" r="1"/><circle class="f" cx="115" cy="29" r="2"/><circle class="f" cx="18" cy="53" r="1"/><circle class="f" cx="11" cy="5" r="1"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
flopay/css/welcome.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><title>welcome</title><path d="M31.64,126S61,90.77,61,90.61a44,44,0,0,0,2-13.27V3.65A1.65,1.65,0,0,0,61.41,2h0A5.66,5.66,0,0,0,55.85,6.5L46.71,43.13,45.8,72.32A10.83,10.83,0,0,1,42,79.71L13.93,104.42" style="fill:#ffd2a1"/><path d="M62.6,4V81.45a17.45,17.45,0,0,1-2.38,8.8L56,97.52a6.76,6.76,0,0,1-.67.92L31.14,126.05l-7.55-10L52.9,85.94a12.61,12.61,0,0,0,3.56-8.38l-.77-34.82a5.3,5.3,0,0,1,0-.69l3.74-40,.67.2A1.88,1.88,0,0,1,62.6,4Z" style="fill:#e8a380"/><path d="M61.41,0a7.66,7.66,0,0,0-7.5,6L44.77,42.65a2,2,0,0,0-.05.42l-.91,29.09a8.83,8.83,0,0,1-3.09,6L12.61,102.92a2,2,0,1,0,2.64,3L43.34,81.23a12.82,12.82,0,0,0,4.46-8.85l.91-29L57.81,6.9A3.67,3.67,0,0,1,61.07,4V31.43a2.2,2.2,0,0,0-.27.26C53.3,40.25,53.3,55.75,53.3,65a2,2,0,0,0,4,0c0-7.25,0-18.54,3.77-26.41V77.4a35.07,35.07,0,0,1-8.91,22.49l-22,24.78a2,2,0,0,0,.17,2.82,2,2,0,0,0,2.82-.17l22-24.77a39.12,39.12,0,0,0,9.92-25V3.65A3.65,3.65,0,0,0,61.41,0Z"/><path d="M57.68,109.08h0c-2.37,2.11-6.31,1.58-8.79-1.16L29.72,86.76C27.23,84,27.13,80.08,29.5,78h0c2.37-2.1,6.31-1.58,8.8,1.17L57.47,100.3C60,103.05,60.05,107,57.68,109.08Z" style="fill:#fbb03b"/><path d="M58.72,101.69,48.83,90.77c1.8,2,1.39,5.24-.9,7.27l-.28.25c-2.29,2-5.6,2.08-7.4.1l9.89,10.92c1.8,2,5.11,1.93,7.4-.1l.28-.25C60.12,106.92,60.52,103.67,58.72,101.69Z" style="fill:#f7931e"/><path d="M50.68,118.08h0c-2.37,2.11-6.31,1.58-8.79-1.16L22.72,95.76C20.23,93,20.13,89.08,22.5,87h0c2.37-2.1,6.31-1.58,8.8,1.17L50.47,109.3C53,112.05,53.05,116,50.68,118.08Z" style="fill:#fbb03b"/><path d="M51.72,110.69,41.83,99.77c1.8,2,1.39,5.24-.9,7.27l-.28.25c-2.29,2-5.6,2.08-7.4.1l9.89,10.92c1.8,2,5.11,1.93,7.4-.1l.28-.25C53.12,115.92,53.52,112.67,51.72,110.69Z" style="fill:#f7931e"/><path d="M60,99,40.78,77.8c-3.22-3.55-8.42-4.14-11.6-1.31a7.16,7.16,0,0,0-2.38,5.05A8.34,8.34,0,0,0,27,83.76a7.33,7.33,0,0,0-5.81,1.73,7.16,7.16,0,0,0-2.38,5.05,9.15,9.15,0,0,0,2.44,6.57L40.4,118.26a9.23,9.23,0,0,0,6.76,3.1,7.08,7.08,0,0,0,7.23-6.83,8.43,8.43,0,0,0-.2-2.23,8,8,0,0,0,1,.06,7.08,7.08,0,0,0,7.23-6.83A9.22,9.22,0,0,0,60,99Zm-10.6,17.63c-1.55,1.37-4.23.92-6-1L24.2,94.42a5.16,5.16,0,0,1-1.41-3.67,3.23,3.23,0,0,1,1-2.27,3.27,3.27,0,0,1,2.21-.8,5.25,5.25,0,0,1,3.78,1.81L49,110.64a5.13,5.13,0,0,1,1.41,3.67A3.19,3.19,0,0,1,49.35,116.59Zm8-9c-1.55,1.37-4.23.92-6-1L32.2,85.42a5.16,5.16,0,0,1-1.41-3.67,3.23,3.23,0,0,1,1-2.27,3.27,3.27,0,0,1,2.21-.8,5.25,5.25,0,0,1,3.78,1.81L57,101.64a5.13,5.13,0,0,1,1.41,3.67A3.19,3.19,0,0,1,57.35,107.59Z"/><path d="M96.36,126S67,90.77,67,90.61a44,44,0,0,1-2-13.27V3.65A1.65,1.65,0,0,1,66.59,2h0A5.66,5.66,0,0,1,72.15,6.5l9.14,36.63.91,29.19A10.83,10.83,0,0,0,86,79.71l28.11,24.71" style="fill:#ffd2a1"/><path d="M65.4,4V81.45a17.45,17.45,0,0,0,2.38,8.8L72,97.52a6.76,6.76,0,0,0,.67.92l24.16,27.61,7.55-10L75.1,85.94a12.61,12.61,0,0,1-3.56-8.38l.77-34.82a5.3,5.3,0,0,0,0-.69l-3.74-40-.67.2A1.88,1.88,0,0,0,65.4,4Z" style="fill:#e8a380"/><path d="M66.59,0a7.66,7.66,0,0,1,7.5,6l9.14,36.64a2,2,0,0,1,0,.42l.91,29.09a8.83,8.83,0,0,0,3.09,6l28.11,24.71a2,2,0,1,1-2.64,3L84.66,81.23a12.82,12.82,0,0,1-4.46-8.85l-.91-29L70.19,6.9A3.67,3.67,0,0,0,66.93,4V31.43a2.2,2.2,0,0,1,.27.26c7.5,8.56,7.5,24.06,7.5,33.32a2,2,0,0,1-4,0c0-7.25,0-18.54-3.77-26.41V77.4a35.07,35.07,0,0,0,8.91,22.49l22,24.78a2,2,0,0,1-.17,2.82,2,2,0,0,1-2.82-.17l-22-24.77a39.12,39.12,0,0,1-9.92-25V3.65A3.65,3.65,0,0,1,66.59,0Z"/><path d="M70.32,109.08h0c2.37,2.11,6.31,1.58,8.79-1.16L98.28,86.76c2.49-2.74,2.59-6.68.22-8.78h0c-2.37-2.1-6.31-1.58-8.8,1.17L70.53,100.3C68.05,103.05,68,107,70.32,109.08Z" style="fill:#fbb03b"/><path d="M69.28,101.69l9.89-10.92c-1.8,2-1.39,5.24.9,7.27l.28.25c2.29,2,5.6,2.08,7.4.1l-9.89,10.92c-1.8,2-5.11,1.93-7.4-.1l-.28-.25C67.88,106.92,67.48,103.67,69.28,101.69Z" style="fill:#f7931e"/><path d="M77.32,118.08h0c2.37,2.11,6.31,1.58,8.79-1.16l19.17-21.16c2.49-2.74,2.59-6.68.22-8.78h0c-2.37-2.1-6.31-1.58-8.8,1.17L77.53,109.3C75.05,112.05,75,116,77.32,118.08Z" style="fill:#fbb03b"/><path d="M76.28,110.69l9.89-10.92c-1.8,2-1.39,5.24.9,7.27l.28.25c2.29,2,5.6,2.08,7.4.1l-9.89,10.92c-1.8,2-5.11,1.93-7.4-.1l-.28-.25C74.88,115.92,74.48,112.67,76.28,110.69Z" style="fill:#f7931e"/><path d="M68.05,99,87.22,77.8c3.22-3.55,8.42-4.14,11.6-1.31a7.16,7.16,0,0,1,2.38,5.05,8.34,8.34,0,0,1-.19,2.22,7.33,7.33,0,0,1,5.81,1.73,7.16,7.16,0,0,1,2.38,5.05,9.15,9.15,0,0,1-2.44,6.57L87.6,118.26a9.23,9.23,0,0,1-6.76,3.1,7.08,7.08,0,0,1-7.23-6.83,8.43,8.43,0,0,1,.2-2.23,8,8,0,0,1-1,.06,7.08,7.08,0,0,1-7.23-6.83A9.22,9.22,0,0,1,68.05,99Zm10.6,17.63c1.55,1.37,4.23.92,6-1L103.8,94.42a5.16,5.16,0,0,0,1.41-3.67,3.23,3.23,0,0,0-1-2.27,3.27,3.27,0,0,0-2.21-.8,5.25,5.25,0,0,0-3.78,1.81L79,110.64a5.13,5.13,0,0,0-1.41,3.67A3.19,3.19,0,0,0,78.65,116.59Zm-8-9c1.55,1.37,4.23.92,6-1L95.8,85.42a5.16,5.16,0,0,0,1.41-3.67,3.23,3.23,0,0,0-1-2.27,3.27,3.27,0,0,0-2.21-.8,5.25,5.25,0,0,0-3.78,1.81L71,101.64a5.13,5.13,0,0,0-1.41,3.67A3.19,3.19,0,0,0,70.65,107.59Z"/></svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
BIN
flopay/flo-favicon.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
4487
flopay/index.html
Normal file
1020
flopay/scripts/btcOperator.js
Normal file
257
flopay/scripts/compactIDB.js
Normal file
@ -0,0 +1,257 @@
|
||||
(function (EXPORTS) { //compactIDB v2.1.2
|
||||
/* Compact IndexedDB operations */
|
||||
'use strict';
|
||||
const compactIDB = EXPORTS;
|
||||
|
||||
var defaultDB;
|
||||
|
||||
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
const IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
|
||||
const IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
|
||||
|
||||
if (!indexedDB) {
|
||||
console.error("Your browser doesn't support a stable version of IndexedDB.");
|
||||
return;
|
||||
}
|
||||
|
||||
compactIDB.setDefaultDB = dbName => defaultDB = dbName;
|
||||
|
||||
Object.defineProperty(compactIDB, 'default', {
|
||||
get: () => defaultDB,
|
||||
set: dbName => defaultDB = dbName
|
||||
});
|
||||
|
||||
function getDBversion(dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
resolve(db.version)
|
||||
db.close()
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function upgradeDB(dbName, createList = null, deleteList = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getDBversion(dbName).then(version => {
|
||||
var idb = indexedDB.open(dbName, version + 1);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onupgradeneeded = (event) => {
|
||||
let db = event.target.result;
|
||||
if (createList instanceof Object) {
|
||||
if (Array.isArray(createList)) {
|
||||
let tmp = {}
|
||||
createList.forEach(o => tmp[o] = {})
|
||||
createList = tmp
|
||||
}
|
||||
for (let o in createList) {
|
||||
let obs = db.createObjectStore(o, createList[o].options || {});
|
||||
if (createList[o].indexes instanceof Object)
|
||||
for (let i in createList[o].indexes)
|
||||
obs.createIndex(i, i, createList[o].indexes || {});
|
||||
}
|
||||
}
|
||||
if (Array.isArray(deleteList))
|
||||
deleteList.forEach(o => db.deleteObjectStore(o));
|
||||
resolve('Database upgraded')
|
||||
}
|
||||
idb.onsuccess = (event) => event.target.result.close();
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
compactIDB.initDB = function (dbName, objectStores = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!(objectStores instanceof Object))
|
||||
return reject('ObjectStores must be an object or array')
|
||||
defaultDB = defaultDB || dbName;
|
||||
var idb = indexedDB.open(dbName);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onsuccess = (event) => {
|
||||
var db = event.target.result;
|
||||
let cList = Object.values(db.objectStoreNames);
|
||||
var obs = {},
|
||||
a_obs = {},
|
||||
d_obs = [];
|
||||
if (!Array.isArray(objectStores))
|
||||
var obs = objectStores
|
||||
else
|
||||
objectStores.forEach(o => obs[o] = {})
|
||||
let nList = Object.keys(obs)
|
||||
for (let o of nList)
|
||||
if (!cList.includes(o))
|
||||
a_obs[o] = obs[o]
|
||||
for (let o of cList)
|
||||
if (!nList.includes(o))
|
||||
d_obs.push(o)
|
||||
if (!Object.keys(a_obs).length && !d_obs.length)
|
||||
resolve("Initiated IndexedDB");
|
||||
else
|
||||
upgradeDB(dbName, a_obs, d_obs)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const openDB = compactIDB.openDB = function (dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var idb = indexedDB.open(dbName);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onupgradeneeded = (event) => {
|
||||
event.target.result.close();
|
||||
deleteDB(dbName).then(_ => null).catch(_ => null).finally(_ => reject("Datebase not found"))
|
||||
}
|
||||
idb.onsuccess = (event) => resolve(event.target.result);
|
||||
});
|
||||
}
|
||||
|
||||
const deleteDB = compactIDB.deleteDB = function (dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var deleteReq = indexedDB.deleteDatabase(dbName);;
|
||||
deleteReq.onerror = (event) => reject("Error deleting database!");
|
||||
deleteReq.onsuccess = (event) => resolve("Database deleted successfully");
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.writeData = function (obsName, data, key = false, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let writeReq = (key ? obs.put(data, key) : obs.put(data));
|
||||
writeReq.onsuccess = (evt) => resolve(`Write data Successful`);
|
||||
writeReq.onerror = (evt) => reject(
|
||||
`Write data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.addData = function (obsName, data, key = false, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let addReq = (key ? obs.add(data, key) : obs.add(data));
|
||||
addReq.onsuccess = (evt) => resolve(`Add data successful`);
|
||||
addReq.onerror = (evt) => reject(
|
||||
`Add data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.removeData = function (obsName, key, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let delReq = obs.delete(key);
|
||||
delReq.onsuccess = (evt) => resolve(`Removed Data ${key}`);
|
||||
delReq.onerror = (evt) => reject(
|
||||
`Remove data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.clearData = function (obsName, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let clearReq = obs.clear();
|
||||
clearReq.onsuccess = (evt) => resolve(`Clear data Successful`);
|
||||
clearReq.onerror = (evt) => reject(`Clear data Unsuccessful`);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.readData = function (obsName, key, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
let getReq = obs.get(key);
|
||||
getReq.onsuccess = (evt) => resolve(evt.target.result);
|
||||
getReq.onerror = (evt) => reject(
|
||||
`Read data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.readAllData = function (obsName, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var tmpResult = {}
|
||||
let curReq = obs.openCursor();
|
||||
curReq.onsuccess = (evt) => {
|
||||
var cursor = evt.target.result;
|
||||
if (cursor) {
|
||||
tmpResult[cursor.primaryKey] = cursor.value;
|
||||
cursor.continue();
|
||||
} else
|
||||
resolve(tmpResult);
|
||||
}
|
||||
curReq.onerror = (evt) => reject(
|
||||
`Read-All data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/* compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var filteredResult = {}
|
||||
let keyRange;
|
||||
if(options.lowerKey!==null && options.upperKey!==null)
|
||||
keyRange = IDBKeyRange.bound(options.lowerKey, options.upperKey);
|
||||
else if(options.lowerKey!==null)
|
||||
keyRange = IDBKeyRange.lowerBound(options.lowerKey);
|
||||
else if (options.upperKey!==null)
|
||||
keyRange = IDBKeyRange.upperBound(options.upperBound);
|
||||
else if (options.atKey)
|
||||
let curReq = obs.openCursor(keyRange, )
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}*/
|
||||
|
||||
compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||
options.lowerKey = options.atKey || options.lowerKey || 0
|
||||
options.upperKey = options.atKey || options.upperKey || false
|
||||
options.patternEval = options.patternEval || ((k, v) => true);
|
||||
options.limit = options.limit || false;
|
||||
options.reverse = options.reverse || false;
|
||||
options.lastOnly = options.lastOnly || false
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var filteredResult = {}
|
||||
let curReq = obs.openCursor(
|
||||
options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey),
|
||||
options.lastOnly || options.reverse ? "prev" : "next");
|
||||
curReq.onsuccess = (evt) => {
|
||||
var cursor = evt.target.result;
|
||||
if (!cursor || (options.limit && options.limit <= Object.keys(filteredResult).length))
|
||||
return resolve(filteredResult); //reached end of key list or limit reached
|
||||
else if (options.patternEval(cursor.primaryKey, cursor.value)) {
|
||||
filteredResult[cursor.primaryKey] = cursor.value;
|
||||
options.lastOnly ? resolve(filteredResult) : cursor.continue();
|
||||
} else
|
||||
cursor.continue();
|
||||
}
|
||||
curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
})(window.compactIDB = {});
|
||||
150
flopay/scripts/components.js
Normal file
1063
flopay/scripts/floBlockchainAPI.js
Normal file
1
flopay/scripts/floBlockchainAPI.min.js
vendored
Normal file
1117
flopay/scripts/floCloudAPI.js
Normal file
530
flopay/scripts/floCrypto.js
Normal file
@ -0,0 +1,530 @@
|
||||
(function (EXPORTS) { //floCrypto v2.3.6a
|
||||
/* FLO Crypto Operators */
|
||||
'use strict';
|
||||
const floCrypto = EXPORTS;
|
||||
|
||||
const p = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
|
||||
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
||||
const ascii_alternatives = `‘ '\n’ '\n“ "\n” "\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
|
||||
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
|
||||
coinjs.compressed = true; //defaulting coinjs compressed to true;
|
||||
|
||||
function calculateY(x) {
|
||||
let exp = exponent1();
|
||||
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
|
||||
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
|
||||
}
|
||||
|
||||
function getUncompressedPublicKey(compressedPublicKey) {
|
||||
// Fetch x from compressedPublicKey
|
||||
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
|
||||
const prefix = pubKeyBytes.shift() // remove prefix
|
||||
let prefix_modulus = prefix % 2;
|
||||
pubKeyBytes.unshift(0) // add prefix 0
|
||||
let x = new BigInteger(pubKeyBytes)
|
||||
let xDecimalValue = x.toString()
|
||||
// Fetch y
|
||||
let y = calculateY(x);
|
||||
let yDecimalValue = y.toString();
|
||||
// verify y value
|
||||
let resultBigInt = y.mod(BigInteger("2"));
|
||||
let check = resultBigInt.toString() % 2;
|
||||
if (prefix_modulus !== check)
|
||||
yDecimalValue = y.negate().mod(p).toString();
|
||||
return {
|
||||
x: xDecimalValue,
|
||||
y: yDecimalValue
|
||||
};
|
||||
}
|
||||
|
||||
function getSenderPublicKeyString() {
|
||||
let privateKey = ellipticCurveEncryption.senderRandom();
|
||||
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
|
||||
return {
|
||||
privateKey: privateKey,
|
||||
senderPublicKeyString: senderPublicKeyString
|
||||
}
|
||||
}
|
||||
|
||||
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
|
||||
let receiverPublicKeyString = getUncompressedPublicKey(receiverPublicKeyHex);
|
||||
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
|
||||
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
|
||||
return senderDerivedKey;
|
||||
}
|
||||
|
||||
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
|
||||
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
|
||||
}
|
||||
|
||||
function getReceiverPublicKeyString(privateKey) {
|
||||
return ellipticCurveEncryption.receiverPublicString(privateKey);
|
||||
}
|
||||
|
||||
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
|
||||
let pk = Bitcoin.Base58.decode(pk_wif)
|
||||
pk.shift()
|
||||
pk.splice(-4, 4)
|
||||
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
|
||||
if (isPubKeyCompressed == true) pk.pop()
|
||||
pk.unshift(0)
|
||||
let privateKeyDecimal = BigInteger(pk).toString()
|
||||
let privateKeyHex = Crypto.util.bytesToHex(pk)
|
||||
return {
|
||||
privateKeyDecimal: privateKeyDecimal,
|
||||
privateKeyHex: privateKeyHex
|
||||
}
|
||||
}
|
||||
|
||||
//generate a random Interger within range
|
||||
floCrypto.randInt = function (min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
//generate a random String within length (options : alphaNumeric chars only)
|
||||
floCrypto.randString = function (length, alphaNumeric = true) {
|
||||
var result = '';
|
||||
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||
for (var i = 0; i < length; i++)
|
||||
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
//Encrypt Data using public-key
|
||||
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
|
||||
var senderECKeyData = getSenderPublicKeyString();
|
||||
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
|
||||
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||
let secret = Crypto.AES.encrypt(data, senderKey);
|
||||
return {
|
||||
secret: secret,
|
||||
senderPublicKeyString: senderECKeyData.senderPublicKeyString
|
||||
};
|
||||
}
|
||||
|
||||
//Decrypt Data using private-key
|
||||
floCrypto.decryptData = function (data, privateKeyHex) {
|
||||
var receiverECKeyData = {};
|
||||
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
|
||||
let privateKey = wifToDecimal(privateKeyHex, true);
|
||||
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error("Failed to detremine your private key.");
|
||||
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
|
||||
var receiverDerivedKey = deriveSharedKeyReceiver(data.senderPublicKeyString, receiverECKeyData.privateKey);
|
||||
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
|
||||
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
|
||||
return decryptMsg;
|
||||
}
|
||||
|
||||
//Sign data using private-key
|
||||
floCrypto.signData = function (data, privateKeyHex) {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
var messageHash = Crypto.SHA256(data);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
|
||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||
return sighex;
|
||||
}
|
||||
|
||||
//Verify signatue of the data using public-key
|
||||
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
|
||||
var msgHash = Crypto.SHA256(data);
|
||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
|
||||
return verify;
|
||||
}
|
||||
|
||||
//Generates a new flo ID and returns private-key, public-key and floID
|
||||
const generateNewID = floCrypto.generateNewID = function () {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat()
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(floCrypto, {
|
||||
newID: {
|
||||
get: () => generateNewID()
|
||||
},
|
||||
hashID: {
|
||||
value: (str) => {
|
||||
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true });
|
||||
bytes.unshift(bitjs.pub);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||
}
|
||||
},
|
||||
tmpID: {
|
||||
get: () => {
|
||||
let bytes = Crypto.util.randomBytes(20);
|
||||
bytes.unshift(bitjs.pub);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Returns public-key from private-key
|
||||
floCrypto.getPubKeyHex = function (privateKeyHex) {
|
||||
if (!privateKeyHex)
|
||||
return null;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
return key.getPubKeyHex();
|
||||
}
|
||||
|
||||
//Returns flo-ID from public-key or private-key
|
||||
floCrypto.getFloID = function (keyHex) {
|
||||
if (!keyHex)
|
||||
return null;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(keyHex);
|
||||
if (key.priv == null)
|
||||
key.setPub(keyHex);
|
||||
return key.getBitcoinAddress();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getAddress = function (privateKeyHex, strict = false) {
|
||||
if (!privateKeyHex)
|
||||
return;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
let pubKey = key.getPubKeyHex(),
|
||||
version = bitjs.Base58.decode(privateKeyHex)[0];
|
||||
switch (version) {
|
||||
case coinjs.priv: //BTC
|
||||
return coinjs.bech32Address(pubKey).address;
|
||||
case bitjs.priv: //FLO
|
||||
return bitjs.pubkey2address(pubKey);
|
||||
default:
|
||||
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
|
||||
}
|
||||
}
|
||||
|
||||
//Verify the private-key for the given public-key or flo-ID
|
||||
floCrypto.verifyPrivKey = function (privateKeyHex, pubKey_floID, isfloID = true) {
|
||||
if (!privateKeyHex || !pubKey_floID)
|
||||
return false;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return false;
|
||||
key.setCompressed(true);
|
||||
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
||||
return true;
|
||||
else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
|
||||
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
|
||||
return null;
|
||||
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length)
|
||||
return null;
|
||||
try {
|
||||
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
|
||||
return multisig;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.decodeRedeemScript = function (redeemScript) {
|
||||
try {
|
||||
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
|
||||
return decoded;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given flo-id is valid or not
|
||||
floCrypto.validateFloID = function (floID, regularOnly = false) {
|
||||
if (!floID)
|
||||
return false;
|
||||
try {
|
||||
let addr = new Bitcoin.Address(floID);
|
||||
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
|
||||
return false;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given address (any blockchain) is valid or not
|
||||
floCrypto.validateAddr = function (address, std = true, bech = true) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return false;
|
||||
if (typeof raw.version !== 'undefined') { //legacy or segwit
|
||||
if (std == false)
|
||||
return false;
|
||||
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else if (typeof raw.bech_version !== 'undefined') { //bech32
|
||||
if (bech === false)
|
||||
return false;
|
||||
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else //unknown
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check the public-key (or redeem-script) for the address (any blockchain)
|
||||
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })));
|
||||
if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig
|
||||
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
|
||||
return pub_hash === raw.hex;
|
||||
}
|
||||
|
||||
//Convert the given address (any blockchain) to equivalent floID
|
||||
floCrypto.toFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) { //if (optional) version check is passed
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
return;
|
||||
}
|
||||
raw.bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Convert raw address bytes to floID
|
||||
floCrypto.rawToFloID = function (raw_bytes) {
|
||||
if (typeof raw_bytes === 'string')
|
||||
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
|
||||
if (raw_bytes.length != 20)
|
||||
return null;
|
||||
raw_bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw_bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw_bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Convert the given multisig address (any blockchain) to equivalent multisig floID
|
||||
floCrypto.toMultisigFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) { //if (optional) version check is passed
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
return;
|
||||
}
|
||||
if (typeof raw.bech_version !== 'undefined') {
|
||||
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
|
||||
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
|
||||
raw.bytes = ripemd160(raw.bytes, {
|
||||
asBytes: true
|
||||
});
|
||||
}
|
||||
raw.bytes.unshift(bitjs.multisig);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
||||
floCrypto.isSameAddr = function (addr1, addr2) {
|
||||
if (!addr1 || !addr2)
|
||||
return;
|
||||
let raw1 = decodeAddress(addr1),
|
||||
raw2 = decodeAddress(addr2);
|
||||
if (!raw1 || !raw2)
|
||||
return false;
|
||||
else {
|
||||
if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig
|
||||
raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true }));
|
||||
if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig
|
||||
raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true }));
|
||||
return raw1.hex === raw2.hex;
|
||||
}
|
||||
}
|
||||
|
||||
const decodeAddress = floCrypto.decodeAddr = function (address) {
|
||||
if (!address)
|
||||
return;
|
||||
else if (address.length == 33 || address.length == 34) { //legacy encoding
|
||||
let decode = bitjs.Base58.decode(address);
|
||||
let bytes = decode.slice(0, decode.length - 4);
|
||||
let checksum = decode.slice(decode.length - 4),
|
||||
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
|
||||
version: bytes.shift(),
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else if (address.length == 42 || address.length == 62) { //bech encoding
|
||||
let decode = coinjs.bech32_decode(address);
|
||||
if (decode) {
|
||||
let bytes = decode.data;
|
||||
let bech_version = bytes.shift();
|
||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
||||
return {
|
||||
bech_version,
|
||||
hrp: decode.hrp,
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Split the str using shamir's Secret and Returns the shares
|
||||
floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
|
||||
try {
|
||||
if (str.length > 0) {
|
||||
var strHex = shamirSecretShare.str2hex(str);
|
||||
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
|
||||
return shares;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//Returns the retrived secret by combining the shamirs shares
|
||||
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
|
||||
try {
|
||||
if (sharesArray.length > 0) {
|
||||
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
||||
comb = shamirSecretShare.hex2str(comb);
|
||||
return comb;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Verifies the shares and str
|
||||
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
|
||||
if (!str)
|
||||
return null;
|
||||
else if (retrieveShamirSecret(sharesArray) === str)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
|
||||
if (typeof string !== "string")
|
||||
return null;
|
||||
if (bool) {
|
||||
let x;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
let x, invalids = {};
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
if (x in invalids)
|
||||
invalids[string[i]].push(i)
|
||||
else
|
||||
invalids[string[i]] = [i];
|
||||
}
|
||||
if (Object.keys(invalids).length)
|
||||
return invalids;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
|
||||
let chars = validateASCII(string, false);
|
||||
if (chars === true)
|
||||
return string;
|
||||
else if (chars === null)
|
||||
return null;
|
||||
let convertor, result = string,
|
||||
refAlt = {};
|
||||
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
||||
mode = mode.toLowerCase();
|
||||
if (mode === "hard-unicode")
|
||||
convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "soft-unicode")
|
||||
convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "hard-remove")
|
||||
convertor = c => "";
|
||||
else if (mode === "soft-remove")
|
||||
convertor = c => refAlt[c] || "";
|
||||
else
|
||||
return null;
|
||||
for (let c in chars)
|
||||
result = result.replaceAll(c, convertor(c));
|
||||
return result;
|
||||
}
|
||||
|
||||
floCrypto.revertUnicode = function (string) {
|
||||
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
|
||||
}
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floCrypto = {});
|
||||
843
flopay/scripts/floDapps.js
Normal file
@ -0,0 +1,843 @@
|
||||
(function (EXPORTS) { //floDapps v2.4.1
|
||||
/* General functions for FLO Dapps*/
|
||||
'use strict';
|
||||
const floDapps = EXPORTS;
|
||||
|
||||
const DEFAULT = {
|
||||
root: "floDapps",
|
||||
application: floGlobals.application,
|
||||
adminID: floGlobals.adminID
|
||||
};
|
||||
|
||||
Object.defineProperties(floDapps, {
|
||||
application: {
|
||||
get: () => DEFAULT.application
|
||||
},
|
||||
adminID: {
|
||||
get: () => DEFAULT.adminID
|
||||
},
|
||||
root: {
|
||||
get: () => DEFAULT.root
|
||||
}
|
||||
});
|
||||
|
||||
var user_priv_raw, aes_key, user_priv_wrap; //private variable inside capsule
|
||||
const raw_user = {
|
||||
get private() {
|
||||
if (!user_priv_raw)
|
||||
throw "User not logged in";
|
||||
return Crypto.AES.decrypt(user_priv_raw, aes_key);
|
||||
}
|
||||
}
|
||||
|
||||
var user_id, user_public, user_private;
|
||||
const user = floDapps.user = {
|
||||
get id() {
|
||||
if (!user_id)
|
||||
throw "User not logged in";
|
||||
return user_id;
|
||||
},
|
||||
get public() {
|
||||
if (!user_public)
|
||||
throw "User not logged in";
|
||||
return user_public;
|
||||
},
|
||||
get private() {
|
||||
if (!user_private)
|
||||
throw "User not logged in";
|
||||
else if (user_private instanceof Function)
|
||||
return user_private();
|
||||
else
|
||||
return Crypto.AES.decrypt(user_private, aes_key);
|
||||
},
|
||||
sign(message) {
|
||||
return floCrypto.signData(message, raw_user.private);
|
||||
},
|
||||
decrypt(data) {
|
||||
return floCrypto.decryptData(data, raw_user.private);
|
||||
},
|
||||
encipher(message) {
|
||||
return Crypto.AES.encrypt(message, raw_user.private);
|
||||
},
|
||||
decipher(data) {
|
||||
return Crypto.AES.decrypt(data, raw_user.private);
|
||||
},
|
||||
get db_name() {
|
||||
return "floDapps#" + floCrypto.toFloID(user.id);
|
||||
},
|
||||
lock() {
|
||||
user_private = user_priv_wrap;
|
||||
},
|
||||
async unlock() {
|
||||
if (await user.private === raw_user.private)
|
||||
user_private = user_priv_raw;
|
||||
},
|
||||
get_contact(id) {
|
||||
if (!user.contacts)
|
||||
throw "Contacts not available";
|
||||
else if (user.contacts[id])
|
||||
return user.contacts[id];
|
||||
else {
|
||||
let id_raw = floCrypto.decodeAddr(id).hex;
|
||||
for (let i in user.contacts)
|
||||
if (floCrypto.decodeAddr(i).hex == id_raw)
|
||||
return user.contacts[i];
|
||||
}
|
||||
},
|
||||
get_pubKey(id) {
|
||||
if (!user.pubKeys)
|
||||
throw "Contacts not available";
|
||||
else if (user.pubKeys[id])
|
||||
return user.pubKeys[id];
|
||||
else {
|
||||
let id_raw = floCrypto.decodeAddr(id).hex;
|
||||
for (let i in user.pubKeys)
|
||||
if (floCrypto.decodeAddr(i).hex == id_raw)
|
||||
return user.pubKeys[i];
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
user_id = user_public = user_private = undefined;
|
||||
user_priv_raw = aes_key = undefined;
|
||||
delete user.contacts;
|
||||
delete user.pubKeys;
|
||||
delete user.messages;
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperties(window, {
|
||||
myFloID: {
|
||||
get: () => {
|
||||
try {
|
||||
return user.id;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
myUserID: {
|
||||
get: () => {
|
||||
try {
|
||||
return user.id;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
myPubKey: {
|
||||
get: () => {
|
||||
try {
|
||||
return user.public;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
myPrivKey: {
|
||||
get: () => {
|
||||
try {
|
||||
return user.private;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var subAdmins, trustedIDs, settings;
|
||||
Object.defineProperties(floGlobals, {
|
||||
subAdmins: {
|
||||
get: () => subAdmins
|
||||
},
|
||||
trustedIDs: {
|
||||
get: () => trustedIDs
|
||||
},
|
||||
settings: {
|
||||
get: () => settings
|
||||
},
|
||||
contacts: {
|
||||
get: () => user.contacts
|
||||
},
|
||||
pubKeys: {
|
||||
get: () => user.pubKeys
|
||||
},
|
||||
messages: {
|
||||
get: () => user.messages
|
||||
}
|
||||
})
|
||||
|
||||
function initIndexedDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var obs_g = {
|
||||
//general
|
||||
lastTx: {},
|
||||
//supernode (cloud list)
|
||||
supernodes: {}
|
||||
}
|
||||
var obs_a = {
|
||||
//login credentials
|
||||
credentials: {},
|
||||
//for Dapps
|
||||
subAdmins: {},
|
||||
trustedIDs: {},
|
||||
settings: {},
|
||||
appObjects: {},
|
||||
generalData: {},
|
||||
lastVC: {}
|
||||
}
|
||||
//add other given objectStores
|
||||
initIndexedDB.appObs = initIndexedDB.appObs || {}
|
||||
for (let o in initIndexedDB.appObs)
|
||||
if (!(o in obs_a))
|
||||
obs_a[o] = initIndexedDB.appObs[o]
|
||||
Promise.all([
|
||||
compactIDB.initDB(DEFAULT.application, obs_a),
|
||||
compactIDB.initDB(DEFAULT.root, obs_g)
|
||||
]).then(result => {
|
||||
compactIDB.setDefaultDB(DEFAULT.application)
|
||||
resolve("IndexedDB App Storage Initated Successfully")
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
function initUserDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var obs = {
|
||||
contacts: {},
|
||||
pubKeys: {},
|
||||
messages: {}
|
||||
}
|
||||
compactIDB.initDB(user.db_name, obs).then(result => {
|
||||
resolve("UserDB Initated Successfully")
|
||||
}).catch(error => reject('Init userDB failed'));
|
||||
})
|
||||
}
|
||||
|
||||
function loadUserDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var loadData = ["contacts", "pubKeys", "messages"]
|
||||
var promises = []
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
promises[i] = compactIDB.readAllData(loadData[i], user.db_name)
|
||||
Promise.all(promises).then(results => {
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
user[loadData[i]] = results[i]
|
||||
resolve("Loaded Data from userDB")
|
||||
}).catch(error => reject('Load userDB failed'))
|
||||
})
|
||||
}
|
||||
|
||||
const startUpOptions = {
|
||||
cloud: true,
|
||||
app_config: true,
|
||||
}
|
||||
|
||||
floDapps.startUpOptions = {
|
||||
set app_config(val) {
|
||||
if (val === true || val === false)
|
||||
startUpOptions.app_config = val;
|
||||
},
|
||||
get app_config() { return startUpOptions.app_config },
|
||||
|
||||
set cloud(val) {
|
||||
if (val === true || val === false)
|
||||
startUpOptions.cloud = val;
|
||||
},
|
||||
get cloud() { return startUpOptions.cloud },
|
||||
}
|
||||
|
||||
const startUpFunctions = [];
|
||||
|
||||
startUpFunctions.push(function readSupernodeListFromAPI() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.cloud)
|
||||
return resolve("No cloud for this app");
|
||||
const CLOUD_KEY = "floCloudAPI#" + floCloudAPI.SNStorageID;
|
||||
compactIDB.readData("lastTx", CLOUD_KEY, DEFAULT.root).then(lastTx => {
|
||||
var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName };
|
||||
if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
|
||||
query_options.ignoreOld = lastTx;
|
||||
else if (typeof lastTx == 'string') //lastTx is txid of last tx
|
||||
query_options.after = lastTx;
|
||||
//fetch data from flosight
|
||||
floBlockchainAPI.readData(floCloudAPI.SNStorageID, query_options).then(result => {
|
||||
compactIDB.readData("supernodes", CLOUD_KEY, DEFAULT.root).then(nodes => {
|
||||
nodes = nodes || {};
|
||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||
var content = JSON.parse(result.data[i])[floCloudAPI.SNStorageName];
|
||||
for (let sn in content.removeNodes)
|
||||
delete nodes[sn];
|
||||
for (let sn in content.newNodes)
|
||||
nodes[sn] = content.newNodes[sn];
|
||||
for (let sn in content.updateNodes)
|
||||
if (sn in nodes) //check if node is listed
|
||||
nodes[sn].uri = content.updateNodes[sn];
|
||||
}
|
||||
Promise.all([
|
||||
compactIDB.writeData("lastTx", result.lastItem, CLOUD_KEY, DEFAULT.root),
|
||||
compactIDB.writeData("supernodes", nodes, CLOUD_KEY, DEFAULT.root)
|
||||
]).then(_ => {
|
||||
floCloudAPI.init(nodes)
|
||||
.then(result => resolve("Loaded Supernode list\n" + result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
});
|
||||
|
||||
startUpFunctions.push(function readAppConfigFromAPI() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.app_config)
|
||||
return resolve("No configs for this app");
|
||||
compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => {
|
||||
var query_options = { sentOnly: true, pattern: DEFAULT.application };
|
||||
if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
|
||||
query_options.ignoreOld = lastTx;
|
||||
else if (typeof lastTx == 'string') //lastTx is txid of last tx
|
||||
query_options.after = lastTx;
|
||||
//fetch data from flosight
|
||||
floBlockchainAPI.readData(DEFAULT.adminID, query_options).then(result => {
|
||||
for (var i = result.data.length - 1; i >= 0; i--) {
|
||||
var content = JSON.parse(result.data[i])[DEFAULT.application];
|
||||
if (!content || typeof content !== "object")
|
||||
continue;
|
||||
if (Array.isArray(content.removeSubAdmin))
|
||||
for (var j = 0; j < content.removeSubAdmin.length; j++)
|
||||
compactIDB.removeData("subAdmins", content.removeSubAdmin[j]);
|
||||
if (Array.isArray(content.addSubAdmin))
|
||||
for (var k = 0; k < content.addSubAdmin.length; k++)
|
||||
compactIDB.writeData("subAdmins", true, content.addSubAdmin[k]);
|
||||
if (Array.isArray(content.removeTrustedID))
|
||||
for (var j = 0; j < content.removeTrustedID.length; j++)
|
||||
compactIDB.removeData("trustedIDs", content.removeTrustedID[j]);
|
||||
if (Array.isArray(content.addTrustedID))
|
||||
for (var k = 0; k < content.addTrustedID.length; k++)
|
||||
compactIDB.writeData("trustedIDs", true, content.addTrustedID[k]);
|
||||
if (content.settings)
|
||||
for (let l in content.settings)
|
||||
compactIDB.writeData("settings", content.settings[l], l)
|
||||
}
|
||||
compactIDB.writeData("lastTx", result.lastItem, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root);
|
||||
compactIDB.readAllData("subAdmins").then(result => {
|
||||
subAdmins = Object.keys(result);
|
||||
compactIDB.readAllData("trustedIDs").then(result => {
|
||||
trustedIDs = Object.keys(result);
|
||||
compactIDB.readAllData("settings").then(result => {
|
||||
settings = result;
|
||||
resolve("Read app configuration from blockchain");
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
});
|
||||
|
||||
startUpFunctions.push(function loadDataFromAppIDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.cloud)
|
||||
return resolve("No cloud for this app");
|
||||
var loadData = ["appObjects", "generalData", "lastVC"]
|
||||
var promises = []
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
promises[i] = compactIDB.readAllData(loadData[i])
|
||||
Promise.all(promises).then(results => {
|
||||
for (var i = 0; i < loadData.length; i++)
|
||||
floGlobals[loadData[i]] = results[i]
|
||||
resolve("Loaded Data from app IDB")
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
});
|
||||
|
||||
var keyInput = type => new Promise((resolve, reject) => {
|
||||
let inputVal = prompt(`Enter ${type}: `)
|
||||
if (inputVal === null)
|
||||
reject(null)
|
||||
else
|
||||
resolve(inputVal)
|
||||
});
|
||||
|
||||
function getCredentials() {
|
||||
|
||||
const readSharesFromIDB = indexArr => new Promise((resolve, reject) => {
|
||||
var promises = []
|
||||
for (var i = 0; i < indexArr.length; i++)
|
||||
promises.push(compactIDB.readData('credentials', indexArr[i]))
|
||||
Promise.all(promises).then(shares => {
|
||||
var secret = floCrypto.retrieveShamirSecret(shares)
|
||||
if (secret)
|
||||
resolve(secret)
|
||||
else
|
||||
reject("Shares are insufficient or incorrect")
|
||||
}).catch(error => {
|
||||
clearCredentials();
|
||||
location.reload();
|
||||
})
|
||||
});
|
||||
|
||||
const writeSharesToIDB = (shares, i = 0, resultIndexes = []) => new Promise(resolve => {
|
||||
if (i >= shares.length)
|
||||
return resolve(resultIndexes)
|
||||
var n = floCrypto.randInt(0, 100000)
|
||||
compactIDB.addData("credentials", shares[i], n).then(res => {
|
||||
resultIndexes.push(n)
|
||||
writeSharesToIDB(shares, i + 1, resultIndexes)
|
||||
.then(result => resolve(result))
|
||||
}).catch(error => {
|
||||
writeSharesToIDB(shares, i, resultIndexes)
|
||||
.then(result => resolve(result))
|
||||
})
|
||||
});
|
||||
|
||||
const getPrivateKeyCredentials = () => new Promise((resolve, reject) => {
|
||||
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||
if (indexArr) {
|
||||
readSharesFromIDB(JSON.parse(indexArr))
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
} else {
|
||||
var privKey;
|
||||
keyInput("PRIVATE_KEY").then(result => {
|
||||
if (!result)
|
||||
return reject("Empty Private Key")
|
||||
var floID = floCrypto.getFloID(result)
|
||||
if (!floID || !floCrypto.validateFloID(floID))
|
||||
return reject("Invalid Private Key")
|
||||
privKey = result;
|
||||
}).catch(error => {
|
||||
console.log(error, "Generating Random Keys")
|
||||
privKey = floCrypto.generateNewID().privKey
|
||||
}).finally(_ => {
|
||||
if (!privKey)
|
||||
return;
|
||||
var threshold = floCrypto.randInt(10, 20)
|
||||
var shares = floCrypto.createShamirsSecretShares(privKey, threshold, threshold)
|
||||
writeSharesToIDB(shares).then(resultIndexes => {
|
||||
//store index keys in localStorage
|
||||
localStorage.setItem(`${DEFAULT.application}#privKey`, JSON.stringify(resultIndexes))
|
||||
//also add a dummy privatekey to the IDB
|
||||
var randomPrivKey = floCrypto.generateNewID().privKey
|
||||
var randomThreshold = floCrypto.randInt(10, 20)
|
||||
var randomShares = floCrypto.createShamirsSecretShares(randomPrivKey, randomThreshold, randomThreshold)
|
||||
writeSharesToIDB(randomShares)
|
||||
//resolve private Key
|
||||
resolve(privKey)
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const checkIfPinRequired = key => new Promise((resolve, reject) => {
|
||||
if (key.length == 52)
|
||||
resolve(key)
|
||||
else {
|
||||
keyInput("PIN/Password").then(pwd => {
|
||||
try {
|
||||
let privKey = Crypto.AES.decrypt(key, pwd);
|
||||
resolve(privKey)
|
||||
} catch (error) {
|
||||
reject("Access Denied: Incorrect PIN/Password")
|
||||
}
|
||||
}).catch(error => reject("Access Denied: PIN/Password required"))
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
getPrivateKeyCredentials().then(key => {
|
||||
checkIfPinRequired(key).then(privKey => {
|
||||
try {
|
||||
user_public = floCrypto.getPubKeyHex(privKey);
|
||||
user_id = floCrypto.getAddress(privKey);
|
||||
if (startUpOptions.cloud)
|
||||
floCloudAPI.user(user_id, privKey); //Set user for floCloudAPI
|
||||
user_priv_wrap = () => checkIfPinRequired(key);
|
||||
let n = floCrypto.randInt(12, 20);
|
||||
aes_key = floCrypto.randString(n);
|
||||
user_priv_raw = Crypto.AES.encrypt(privKey, aes_key);
|
||||
user_private = user_priv_wrap;
|
||||
resolve('Login Credentials loaded successful')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
reject("Corrupted Private Key")
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
var startUpLog = (status, log) => status ? console.log(log) : console.error(log);
|
||||
|
||||
const callStartUpFunction = i => new Promise((resolve, reject) => {
|
||||
startUpFunctions[i]().then(result => {
|
||||
callStartUpFunction.completed += 1;
|
||||
startUpLog(true, `${result}\nCompleted ${callStartUpFunction.completed}/${callStartUpFunction.total} Startup functions`)
|
||||
resolve(true)
|
||||
}).catch(error => {
|
||||
callStartUpFunction.failed += 1;
|
||||
startUpLog(false, `${error}\nFailed ${callStartUpFunction.failed}/${callStartUpFunction.total} Startup functions`)
|
||||
reject(false)
|
||||
})
|
||||
});
|
||||
|
||||
var _midFunction;
|
||||
const midStartUp = () => new Promise((res, rej) => {
|
||||
if (_midFunction instanceof Function) {
|
||||
_midFunction()
|
||||
.then(r => res("Mid startup function completed"))
|
||||
.catch(e => rej("Mid startup function failed"))
|
||||
} else
|
||||
res("No mid startup function")
|
||||
});
|
||||
|
||||
const callAndLog = p => new Promise((res, rej) => {
|
||||
p.then(r => {
|
||||
startUpLog(true, r)
|
||||
res(r)
|
||||
}).catch(e => {
|
||||
startUpLog(false, e)
|
||||
rej(e)
|
||||
})
|
||||
});
|
||||
|
||||
floDapps.launchStartUp = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
initIndexedDB().then(log => {
|
||||
console.log(log)
|
||||
callStartUpFunction.total = startUpFunctions.length;
|
||||
callStartUpFunction.completed = 0;
|
||||
callStartUpFunction.failed = 0;
|
||||
let p1 = new Promise((res, rej) => {
|
||||
Promise.all(startUpFunctions.map((f, i) => callStartUpFunction(i))).then(r => {
|
||||
callAndLog(midStartUp())
|
||||
.then(r => res(true))
|
||||
.catch(e => rej(false))
|
||||
})
|
||||
});
|
||||
let p2 = new Promise((res, rej) => {
|
||||
callAndLog(getCredentials()).then(r => {
|
||||
callAndLog(initUserDB()).then(r => {
|
||||
callAndLog(loadUserDB())
|
||||
.then(r => res(true))
|
||||
.catch(e => rej(false))
|
||||
}).catch(e => rej(false))
|
||||
}).catch(e => rej(false))
|
||||
})
|
||||
Promise.all([p1, p2])
|
||||
.then(r => resolve('App Startup finished successful'))
|
||||
.catch(e => reject('App Startup failed'))
|
||||
}).catch(error => {
|
||||
startUpLog(false, error);
|
||||
reject("App database initiation failed")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.addStartUpFunction = fn => fn instanceof Function && !startUpFunctions.includes(fn) ? startUpFunctions.push(fn) : false;
|
||||
|
||||
floDapps.setMidStartup = fn => fn instanceof Function ? _midFunction = fn : false;
|
||||
|
||||
floDapps.setCustomStartupLogger = fn => fn instanceof Function ? startUpLog = fn : false;
|
||||
|
||||
floDapps.setCustomPrivKeyInput = fn => fn instanceof Function ? keyInput = fn : false;
|
||||
|
||||
floDapps.setAppObjectStores = appObs => initIndexedDB.appObs = appObs;
|
||||
|
||||
floDapps.storeContact = function (floID, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject("Invalid floID!")
|
||||
compactIDB.writeData("contacts", name, floID, user.db_name).then(result => {
|
||||
user.contacts[floID] = name;
|
||||
resolve("Contact stored")
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
floDapps.storePubKey = function (floID, pubKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (floID in user.pubKeys)
|
||||
return resolve("pubKey already stored")
|
||||
if (!floCrypto.validateAddr(floID))
|
||||
return reject("Invalid floID!")
|
||||
if (!floCrypto.verifyPubKey(pubKey, floID))
|
||||
return reject("Incorrect pubKey")
|
||||
compactIDB.writeData("pubKeys", pubKey, floID, user.db_name).then(result => {
|
||||
user.pubKeys[floID] = pubKey;
|
||||
resolve("pubKey stored")
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
floDapps.sendMessage = function (floID, message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let options = {
|
||||
receiverID: floID,
|
||||
application: DEFAULT.root,
|
||||
comment: DEFAULT.application
|
||||
}
|
||||
if (floID in user.pubKeys)
|
||||
message = floCrypto.encryptData(JSON.stringify(message), user.pubKeys[floID])
|
||||
floCloudAPI.sendApplicationData(message, "Message", options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.requestInbox = function (callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let lastVC = Object.keys(user.messages).sort().pop()
|
||||
let options = {
|
||||
receiverID: user.id,
|
||||
application: DEFAULT.root,
|
||||
lowerVectorClock: lastVC + 1
|
||||
}
|
||||
let privKey = raw_user.private;
|
||||
options.callback = (d, e) => {
|
||||
for (let v in d) {
|
||||
try {
|
||||
if (d[v].message instanceof Object && "secret" in d[v].message)
|
||||
d[v].message = floCrypto.decryptData(d[v].message, privKey)
|
||||
} catch (error) { }
|
||||
compactIDB.writeData("messages", d[v], v, user.db_name)
|
||||
user.messages[v] = d[v]
|
||||
}
|
||||
if (callback instanceof Function)
|
||||
callback(d, e)
|
||||
}
|
||||
floCloudAPI.requestApplicationData("Message", options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.manageAppConfig = function (adminPrivKey, addList, rmList, settings) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.app_config)
|
||||
return reject("No configs for this app");
|
||||
if (!Array.isArray(addList) || !addList.length) addList = undefined;
|
||||
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
||||
if (!settings || typeof settings !== "object" || !Object.keys(settings).length) settings = undefined;
|
||||
if (!addList && !rmList && !settings)
|
||||
return reject("No configuration change")
|
||||
var floData = {
|
||||
[DEFAULT.application]: {
|
||||
addSubAdmin: addList,
|
||||
removeSubAdmin: rmList,
|
||||
settings: settings
|
||||
}
|
||||
}
|
||||
var floID = floCrypto.getFloID(adminPrivKey)
|
||||
if (floID != DEFAULT.adminID)
|
||||
reject('Access Denied for Admin privilege')
|
||||
else
|
||||
floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey)
|
||||
.then(result => resolve(['Updated App Configuration', result]))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.manageAppTrustedIDs = function (adminPrivKey, addList, rmList) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!startUpOptions.app_config)
|
||||
return reject("No configs for this app");
|
||||
if (!Array.isArray(addList) || !addList.length) addList = undefined;
|
||||
if (!Array.isArray(rmList) || !rmList.length) rmList = undefined;
|
||||
if (!addList && !rmList)
|
||||
return reject("No change in list")
|
||||
var floData = {
|
||||
[DEFAULT.application]: {
|
||||
addTrustedID: addList,
|
||||
removeTrustedID: rmList
|
||||
}
|
||||
}
|
||||
var floID = floCrypto.getFloID(adminPrivKey)
|
||||
if (floID != DEFAULT.adminID)
|
||||
reject('Access Denied for Admin privilege')
|
||||
else
|
||||
floBlockchainAPI.writeData(floID, JSON.stringify(floData), adminPrivKey)
|
||||
.then(result => resolve(['Updated App Configuration', result]))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const clearCredentials = floDapps.clearCredentials = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
compactIDB.clearData('credentials', DEFAULT.application).then(result => {
|
||||
localStorage.removeItem(`${DEFAULT.application}#privKey`);
|
||||
user.clear();
|
||||
resolve("privKey credentials deleted!")
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.deleteUserData = function (credentials = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let p = []
|
||||
p.push(compactIDB.deleteDB(user.db_name))
|
||||
if (credentials)
|
||||
p.push(clearCredentials())
|
||||
Promise.all(p)
|
||||
.then(result => resolve('User database(local) deleted'))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.deleteAppData = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
compactIDB.deleteDB(DEFAULT.application).then(result => {
|
||||
localStorage.removeItem(`${DEFAULT.application}#privKey`)
|
||||
user.clear();
|
||||
compactIDB.removeData('lastTx', `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root)
|
||||
.then(result => resolve("App database(local) deleted"))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.securePrivKey = function (pwd) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||
if (!indexArr)
|
||||
return reject("PrivKey not found");
|
||||
indexArr = JSON.parse(indexArr)
|
||||
let encryptedKey = Crypto.AES.encrypt(await user.private, pwd);
|
||||
let threshold = indexArr.length;
|
||||
let shares = floCrypto.createShamirsSecretShares(encryptedKey, threshold, threshold)
|
||||
let promises = [];
|
||||
let overwriteFn = (share, index) =>
|
||||
compactIDB.writeData("credentials", share, index, DEFAULT.application);
|
||||
for (var i = 0; i < threshold; i++)
|
||||
promises.push(overwriteFn(shares[i], indexArr[i]));
|
||||
Promise.all(promises)
|
||||
.then(results => resolve("Private Key Secured"))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
floDapps.verifyPin = function (pin = null) {
|
||||
const readSharesFromIDB = function (indexArr) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var promises = []
|
||||
for (var i = 0; i < indexArr.length; i++)
|
||||
promises.push(compactIDB.readData('credentials', indexArr[i]))
|
||||
Promise.all(promises).then(shares => {
|
||||
var secret = floCrypto.retrieveShamirSecret(shares)
|
||||
console.info(shares, secret)
|
||||
if (secret)
|
||||
resolve(secret)
|
||||
else
|
||||
reject("Shares are insufficient or incorrect")
|
||||
}).catch(error => {
|
||||
clearCredentials();
|
||||
location.reload();
|
||||
})
|
||||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
var indexArr = localStorage.getItem(`${DEFAULT.application}#privKey`)
|
||||
console.info(indexArr)
|
||||
if (!indexArr)
|
||||
reject('No login credentials found')
|
||||
readSharesFromIDB(JSON.parse(indexArr)).then(key => {
|
||||
if (key.length == 52) {
|
||||
if (pin === null)
|
||||
resolve("Private key not secured")
|
||||
else
|
||||
reject("Private key not secured")
|
||||
} else {
|
||||
if (pin === null)
|
||||
return reject("PIN/Password required")
|
||||
try {
|
||||
let privKey = Crypto.AES.decrypt(key, pin);
|
||||
resolve("PIN/Password verified")
|
||||
} catch (error) {
|
||||
reject("Incorrect PIN/Password")
|
||||
}
|
||||
}
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const getNextGeneralData = floDapps.getNextGeneralData = function (type, vectorClock = null, options = {}) {
|
||||
var fk = floCloudAPI.util.filterKey(type, options)
|
||||
vectorClock = vectorClock || getNextGeneralData[fk] || '0';
|
||||
var filteredResult = {}
|
||||
if (floGlobals.generalData[fk]) {
|
||||
for (let d in floGlobals.generalData[fk])
|
||||
if (d > vectorClock)
|
||||
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
|
||||
} else if (options.comment) {
|
||||
let comment = options.comment;
|
||||
delete options.comment;
|
||||
let fk = floCloudAPI.util.filterKey(type, options);
|
||||
for (let d in floGlobals.generalData[fk])
|
||||
if (d > vectorClock && floGlobals.generalData[fk][d].comment == comment)
|
||||
filteredResult[d] = JSON.parse(JSON.stringify(floGlobals.generalData[fk][d]))
|
||||
}
|
||||
if (options.decrypt) {
|
||||
let decryptionKey = (options.decrypt === true) ? raw_user.private : options.decrypt;
|
||||
if (!Array.isArray(decryptionKey))
|
||||
decryptionKey = [decryptionKey];
|
||||
for (let f in filteredResult) {
|
||||
let data = filteredResult[f]
|
||||
try {
|
||||
if (data.message instanceof Object && "secret" in data.message) {
|
||||
for (let key of decryptionKey) {
|
||||
try {
|
||||
let tmp = floCrypto.decryptData(data.message, key)
|
||||
data.message = JSON.parse(tmp)
|
||||
break;
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
getNextGeneralData[fk] = Object.keys(filteredResult).sort().pop();
|
||||
return filteredResult;
|
||||
}
|
||||
|
||||
const syncData = floDapps.syncData = {};
|
||||
|
||||
syncData.oldDevice = () => new Promise((resolve, reject) => {
|
||||
let sync = {
|
||||
contacts: user.contacts,
|
||||
pubKeys: user.pubKeys,
|
||||
messages: user.messages
|
||||
}
|
||||
let message = Crypto.AES.encrypt(JSON.stringify(sync), raw_user.private)
|
||||
let options = {
|
||||
receiverID: user.id,
|
||||
application: DEFAULT.root
|
||||
}
|
||||
floCloudAPI.sendApplicationData(message, "syncData", options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
});
|
||||
|
||||
syncData.newDevice = () => new Promise((resolve, reject) => {
|
||||
var options = {
|
||||
receiverID: user.id,
|
||||
senderID: user.id,
|
||||
application: DEFAULT.root,
|
||||
mostRecent: true,
|
||||
}
|
||||
floCloudAPI.requestApplicationData("syncData", options).then(response => {
|
||||
let vc = Object.keys(response).sort().pop()
|
||||
let sync = JSON.parse(Crypto.AES.decrypt(response[vc].message, raw_user.private))
|
||||
let promises = []
|
||||
let store = (key, val, obs) => promises.push(compactIDB.writeData(obs, val, key, user.db_name));
|
||||
["contacts", "pubKeys", "messages"].forEach(c => {
|
||||
for (let i in sync[c]) {
|
||||
store(i, sync[c][i], c)
|
||||
user[c][i] = sync[c][i]
|
||||
}
|
||||
})
|
||||
Promise.all(promises)
|
||||
.then(results => resolve("Sync data successful"))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
})('object' === typeof module ? module.exports : window.floDapps = {});
|
||||
1914
flopay/scripts/floExchangeAPI.js
Normal file
193
flopay/scripts/floTokenAPI.js
Normal file
1
flopay/scripts/floTokenAPI.min.js
vendored
Normal file
308
flopay/scripts/fn_pay.js
Normal file
@ -0,0 +1,308 @@
|
||||
/*jshint esversion: 6 */
|
||||
const TYPE_MONEY_REQUEST = "MoneyRequests",
|
||||
TYPE_CASHIER_REQUEST = "CashierRequests",
|
||||
TYPE_CASHIER_UPI = "CashierUPI";
|
||||
|
||||
const cashierUPI = {};
|
||||
const cashierPubKeys = {};
|
||||
|
||||
//For regular users
|
||||
const User = {};
|
||||
const cashierStatus = {};
|
||||
|
||||
User.init = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let promises;
|
||||
//Request cashier for token-cash exchange
|
||||
promises = floGlobals.subAdmins.map(cashierID => floCloudAPI.requestGeneralData(TYPE_CASHIER_REQUEST, {
|
||||
senderID: myFloID,
|
||||
receiverID: cashierID,
|
||||
group: "Cashiers",
|
||||
callback: userUI.renderCashierRequests //UI_fn
|
||||
}));
|
||||
//Request received from other Users for token
|
||||
promises.push(floCloudAPI.requestGeneralData(TYPE_MONEY_REQUEST, {
|
||||
receiverID: myFloID,
|
||||
callback: userUI.renderMoneyRequests //UI_fn
|
||||
}));
|
||||
//Check online status of cashiers
|
||||
promises.push(floCloudAPI.requestStatus(Array.from(floGlobals.subAdmins), {
|
||||
callback: (d, e) => {
|
||||
if (e) return console.error(e);
|
||||
for (let i in d)
|
||||
cashierStatus[i] = d[i];
|
||||
//Add any UI_fn if any
|
||||
if (User.findCashier()) {
|
||||
getRef('topup_wallet_process').classList.remove('hide')
|
||||
getRef('withdraw_wallet_process').classList.remove('hide')
|
||||
document.querySelectorAll('.cashier-status').forEach(elem => elem.classList.add('hide'))
|
||||
} else {
|
||||
getRef('topup_wallet_process').classList.add('hide')
|
||||
getRef('withdraw_wallet_process').classList.add('hide')
|
||||
document.querySelectorAll('.cashier-status').forEach(elem => elem.classList.remove('hide'))
|
||||
}
|
||||
}
|
||||
}))
|
||||
/*
|
||||
promises.push(floCloudAPI.requestObjectData("UPI", { //Is this needed?
|
||||
callback: UI_RENDER_FN
|
||||
}));
|
||||
*/
|
||||
promises.push(User.getCashierUPI());
|
||||
promises.push(organizeSyncedData('savedUserData'));
|
||||
Promise.all(promises)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
User.getCashierUPI = function () {
|
||||
return new Promise((resolve) => {
|
||||
Promise.allSettled(floGlobals.subAdmins.map(cashierID => floCloudAPI.requestApplicationData(TYPE_CASHIER_UPI, {
|
||||
senderID: cashierID,
|
||||
mostRecent: true
|
||||
}))).then(result => {
|
||||
for (let r of result)
|
||||
if (r.status === "fulfilled" && Object.keys(r.value).length) {
|
||||
let vc = Object.keys(r.value).sort().pop()
|
||||
cashierUPI[r.value[vc].senderID] = r.value[vc].message.upi;
|
||||
cashierPubKeys[r.value[vc].senderID] = r.value[vc].pubKey; //get pubKey of cashier from messages for encryption
|
||||
}
|
||||
resolve(cashierUPI);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Object.defineProperty(User, 'cashierRequests', {
|
||||
get: function () {
|
||||
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
|
||||
senderID: myFloID,
|
||||
group: "Cashiers",
|
||||
});
|
||||
return floGlobals.generalData[fk];
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(User, 'moneyRequests', {
|
||||
get: function () {
|
||||
let fk = floCloudAPI.util.filterKey(TYPE_MONEY_REQUEST, {
|
||||
receiverID: myFloID,
|
||||
});
|
||||
return floGlobals.generalData[fk];
|
||||
}
|
||||
});
|
||||
|
||||
User.findCashier = function () {
|
||||
let online = [];
|
||||
for (let c in cashierStatus)
|
||||
if (cashierStatus[c] && cashierUPI[c])
|
||||
online.push(c);
|
||||
if (!online.length) {
|
||||
if (floGlobals.settings.default_cashier && floGlobals.settings.default_cashier in cashierUPI)
|
||||
return floGlobals.settings.default_cashier;
|
||||
else
|
||||
return null;
|
||||
} else {
|
||||
const random = floCrypto.randInt(0, online.length - 1)
|
||||
return online[random];
|
||||
}
|
||||
}
|
||||
|
||||
User.cashToToken = function (cashier, amount, txCode, upiID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floGlobals.subAdmins.includes(cashier))
|
||||
return reject("Invalid cashier");
|
||||
floCloudAPI.sendGeneralData({
|
||||
mode: "cash-to-token",
|
||||
amount: amount,
|
||||
// upi_txid: upiTxID,
|
||||
upiID,
|
||||
txCode
|
||||
}, TYPE_CASHIER_REQUEST, {
|
||||
receiverID: cashier
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
User.tokenToCash = function (cashier, amount, blkTxID, upiID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floGlobals.subAdmins.includes(cashier))
|
||||
return reject("Invalid cashier");
|
||||
floCloudAPI.sendGeneralData({
|
||||
mode: "token-to-cash",
|
||||
amount: amount,
|
||||
token_txid: blkTxID,
|
||||
upi_id: floCrypto.encryptData(upiID, cashierPubKeys[cashier])
|
||||
}, TYPE_CASHIER_REQUEST, {
|
||||
receiverID: cashier
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
User.sendToken = function (receiverID, amount, remark = '', options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floDapps.user.private.then(privateKey => {
|
||||
floTokenAPI.sendToken(privateKey, amount, receiverID, remark, floTokenAPI.currency, options)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
notify('Invalid password', 'error');
|
||||
reject(error);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
User.requestToken = function (floID, amount, remark = '') {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.sendGeneralData({
|
||||
amount: amount,
|
||||
remark: remark
|
||||
}, TYPE_MONEY_REQUEST, {
|
||||
receiverID: floID
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
User.decideRequest = function (request, note) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.noteApplicationData(request.vectorClock, note, {
|
||||
receiverID: myFloID
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
const Cashier = {};
|
||||
|
||||
var status_conn_id = null;
|
||||
|
||||
function statusReconnect() {
|
||||
if (status_conn_id) {
|
||||
floCloudAPI.closeRequest(status_conn_id)
|
||||
status_conn_id = null;
|
||||
}
|
||||
floCloudAPI.setStatus()
|
||||
.then(result => status_conn_id = result)
|
||||
.catch(error => console.error(error))
|
||||
}
|
||||
|
||||
var status_interval_instance = null;
|
||||
function startStatusInterval() {
|
||||
if (status_interval_instance) {
|
||||
clearInterval(status_interval_instance);
|
||||
status_interval_instance = null;
|
||||
}
|
||||
statusReconnect();
|
||||
status_interval_instance = setInterval(statusReconnect, 15 * 60 * 1000);
|
||||
}
|
||||
|
||||
Cashier.init = function () {
|
||||
delegate(getRef('cashier_pending_request_list'), 'click', '.process-cashier-request', e => {
|
||||
const requestID = e.delegateTarget.closest('.cashier-request').dataset.vc;
|
||||
cashierUI.completeRequest(requestID)
|
||||
})
|
||||
getRef('cashier_requests_selector').addEventListener('change', e => {
|
||||
showChildElement('cashier_requests_wrapper', e.target.value === 'pending' ? 0 : 1)
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
let promises = [];
|
||||
//Requests from user to cashier(self) for token-cash exchange
|
||||
promises.push(floCloudAPI.requestGeneralData(TYPE_CASHIER_REQUEST, {
|
||||
receiverID: myFloID,
|
||||
callback: cashierUI.renderRequests //UI_fn
|
||||
}));
|
||||
//Set online status of cashier(self)
|
||||
//promises.push(floCloudAPI.setStatus());
|
||||
/*
|
||||
promises.push(floCloudAPI.requestObjectData("UPI", { //Is this needed?
|
||||
callback: UI_RENDER_FN
|
||||
}));
|
||||
*/
|
||||
promises.push(User.getCashierUPI());
|
||||
Promise.all(promises).then(result => {
|
||||
startStatusInterval(); //Set online status of cashier(self) [connection refreshes on interval]
|
||||
resolve(result)
|
||||
}).catch(error => reject(error));
|
||||
})
|
||||
}
|
||||
|
||||
Cashier.updateUPI = function (upi_id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.sendApplicationData({
|
||||
upi: upi_id
|
||||
}, TYPE_CASHIER_UPI)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
Object.defineProperty(Cashier, 'Requests', {
|
||||
get: function () {
|
||||
let fk = floCloudAPI.util.filterKey(TYPE_CASHIER_REQUEST, {
|
||||
receiverID: myFloID
|
||||
});
|
||||
console.debug(fk, floGlobals.generalData[fk]);
|
||||
return floGlobals.generalData[fk];
|
||||
}
|
||||
});
|
||||
|
||||
Cashier.finishRequest = function (request, txid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.tagApplicationData(request.vectorClock, 'COMPLETED', {
|
||||
receiverID: myFloID
|
||||
}).then(result => {
|
||||
floCloudAPI.noteApplicationData(request.vectorClock, txid, {
|
||||
receiverID: myFloID
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
Cashier.rejectRequest = function (request, reason) {
|
||||
return new Promise((resolve, reject) => {
|
||||
floCloudAPI.noteApplicationData(request.vectorClock, "REJECTED:" + reason, {
|
||||
receiverID: myFloID
|
||||
}).then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
Cashier.checkIfUpiTxIsValid = function (upiTxID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let requests = Cashier.Requests;
|
||||
for (let r in requests)
|
||||
if (requests[r].message.mode === "cash-to-token" && requests[r].note)
|
||||
if (requests[r].message.upi_txid === upiTxID)
|
||||
return reject([true, "UPI transaction is already used for another request"]);
|
||||
return resolve(true);
|
||||
})
|
||||
}
|
||||
|
||||
Cashier.checkIfTokenTxIsValid = function (tokenTxID, sender, amount) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let requests = Cashier.Requests;
|
||||
for (let r in requests)
|
||||
if (requests[r].message.mode === "token-to-cash" && requests[r].note)
|
||||
if (requests[r].message.token_txid === tokenTxID)
|
||||
return reject([true, "Token transaction is already used for another request"]);
|
||||
floTokenAPI.getTx(tokenTxID).then(tx => {
|
||||
let parsedTxData = floTokenAPI.util.parseTxData(tx);
|
||||
console.debug(parsedTxData);
|
||||
if (parsedTxData.type !== "transfer" || parsedTxData.transferType !== "token")
|
||||
reject([true, "Invalid token transfer type"]);
|
||||
else if (parsedTxData.tokenAmount !== amount)
|
||||
reject([true, "Incorrect token amount: " + parsedTxData.tokenAmount]);
|
||||
else if (parsedTxData.tokenIdentification !== floGlobals.currency)
|
||||
reject([true, "Incorrect token: " + parsedTxData.tokenIdentification]);
|
||||
else if (parsedTxData.sender !== sender)
|
||||
reject([true, "Incorrect senderID: " + parsedTxData.sender]);
|
||||
else if (parsedTxData.receiver !== myFloID)
|
||||
reject([true, "Incorrect receiverID: " + parsedTxData.receive])
|
||||
else resolve(true);
|
||||
}).catch(error => reject([null, error]))
|
||||
})
|
||||
}
|
||||