securewalletbundle
Version:
Un wallet 100% sécurisé, sécurisé par vous. Pour XRP et XLM pour l'instant. Vous pouvez télécharger et vérifier/valider le code grâce à ce package.
1,260 lines (1,233 loc) • 69.1 kB
HTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en_US">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="author" content="Decrypto"/>
<meta name="description" content="Préparation d'une transation - ONLINE"/>
<meta name="version" content="2.0"/>
<title>2.1 - ONLINE - Prepare Transaction</title>
<!-- ////////////////////////////////////////////////////// LIBS PART ////////////////////////////////////////////////////// -->
<!-- UI BOOTSTRAP CSS LIBS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
<!-- JQUERY LIBS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- SCRIPT FOR XRP & XLM BLOCKCHAIN -->
<script src="https://unpkg.com/securewalletbundle@2.5.3/dist/SecureWalletBundle.browser.min.js" integrity="sha256-2ZubSXQsoaoPU0YsTc6BkLBXVfEHjBysA4v/e+HDwHw=" crossorigin="anonymous"></script>
<!-- //////////////////////////////////////////////////// END LIBS PART //////////////////////////////////////////////////// -->
<!-- BLOCKCHAIN NETWORK MANAGEMENT -->
<script>
const {xrpl,StellarSdk} = SecureWalletBundle;
const CLASSES = {
//------------------------------------- XRPL -------------------------------------------
"XRPL":class XRPL {
static serverURL = { "main":"wss://xrplcluster.com", "test":"wss://s.altnet.rippletest.net:51233"};
static isLibLoaded(){
return !(typeof xrpl === "undefined");
}
static curMaxLen = 6;
static LLIAdd = 180;//amount to add to last ledger index to ensure we have time to do the transaction with USB key
symbol = "XRP";
constructor(mt) {
this.main = (mt || false) && mt=="main";
this.client = new xrpl.Client(XRPL.serverURL[this.main?"main":"test"]);
}
isMain(){
return this.main;
}
async connect(){
if (this.isConnected !== true){
await this.client.connect();
this.isConnected = true;
}
}
switchServer(mt){
if (!mt){
mt = this.main?"test":"main";
}
mt = mt=="main";
if (this.main && !mt){//we are main, we go test
if (this.isConnected === true){
this.isConnected = false;
try { this.client.disconnect(); } catch(err){}
}
this.client = new xrpl.Client(XRPL.serverURL["test"]);
this.main = false;
} else if (!this.main && mt){//we are test, we go main
if (this.isConnected === true){
this.isConnected = false;
try { this.client.disconnect(); } catch(err){}
}
this.client = new xrpl.Client(XRPL.serverURL["main"]);
this.main = true;
}
}
tryReadError(err){
var errRet = {};
if (err.message == "Account not found."){
errRet.accountNotFound = true;
errRet.errorMsg = err.message;
} if (err.toString() == "[()]") {
errRet.errorMsg = "Le serveur semble s'être déconnecté. Rechargez la page ou appuyez sur le bouton 'vérifiez l'adresse'.";
this.isConnected = false;
} else {
errRet.errorMsg = err.toString();
}
return errRet;
}
isValidAddress(addr){
return (addr || false) && xrpl.isValidAddress(addr);
}
isValidTag(tag){
return (tag || true) && (tag.length == 0 || tag.match(/^[1-9][0-9]*$/));
}
#round(n){
return Number(n.toFixed(6));
}
#cur40ToCur(text){
if (text.length < XRPL.curMaxLen){ return text; }
return text.match(/.{1,2}/g).map(function(v){
return String.fromCharCode(parseInt(v, 16));
}).join('').replace(/\0/g, '').toUpperCase();
}
#curToCur40(text){
if (text.length == 40 || text.length == 3){ return text; }
return text.split('').map(char_ => {
return char_.charCodeAt(0).toString(16).padStart(2, '0');
}).join('').padEnd(40, '0').toUpperCase();
}
hrCurrency(cur) {//human readable currency
return this.#cur40ToCur(cur);
}
async getAccountInfo(addr){
var account_info = await this.client.request({
"command": "account_info",
"account": addr,
"ledger_index": "validated"
});
var server_info = await this.client.request({
command: 'server_info',
});
var ownerCount = account_info.result.account_data.ownerCount || 0;
var reserveBaseXRP = server_info.result.info.validated_ledger.reserve_base_xrp;
var reserveIncXRP = server_info.result.info.validated_ledger.reserve_inc_xrp;
var accountReserves = reserveBaseXRP + ownerCount * reserveIncXRP;
var balance = xrpl.dropsToXrp(account_info.result.account_data.Balance);
return {
balance : balance,
reserve : this.#round(accountReserves),
available : this.#round(balance-accountReserves)
};
}
async getNonNativeBalances(addr, refresh){
if (refresh || !this.assets){
var balances = await this.client.getBalances(addr);
this.assets = [];
this.native = {};
for (var bal of balances){
if (bal.currency === this.symbol){
this.native = {currency:bal.currency, balance:bal.value}
} else {
this.assets.push({currency:bal.currency, balance:bal.value, issuer:bal.issuer});
}
}
}
return this.assets;
}
hasFundsLink(){
return false;
}
async addFunds(addr){
var newWallet = await this.client.fundWallet();
var transaction = {
TransactionType: 'Payment',
Account: newWallet.wallet.classicAddress,
Destination: addr,
Amount: xrpl.xrpToDrops(90)
};
var trx = await this.client.autofill(transaction);
var signedTrx = newWallet.wallet.sign(trx);
var resp = await this.client.submitAndWait(signedTrx.tx_blob);
}
isValidAmount(trx){
if (trx.asset.issuer){//non-native asset
if (trx.amount.match(/^[1-9][0-9]*(\.[0-9]+)?$/) == null){
return "Le montant est mal formé";
}
} else {//native asset limited to 6 decimals
if (trx.amount.match(/^[1-9][0-9]*(\.[0-9]{1,6})?$/) == null){
return "Il y a trop de décimales ou le montant est mal formé";
}
}
if (trx.amount <= 0){
return "Le montant doit être supérieur à 0";
}
if(trx.amount > Number(trx.asset.balance)){
return "Le montant est supérieur au solde disponible";
}
return true;
}
async prepareSendTransaction(trx){
var transaction = {
TransactionType: 'Payment',
Account: trx.from,
Destination: trx.to
}
if (trx.asset.issuer){//case asset currency
transaction.Amount = {
currency: this.#curToCur40(trx.asset.currency),
value: trx.amount.replace(/\.?0+$/,''),
issuer: trx.asset.issuer
};
transaction.Flags = xrpl.PaymentFlags.tfPartialPayment;
} else {//case native
transaction.Amount = xrpl.xrpToDrops(trx.amount);
}
if (trx.tag){
transaction.DestinationTag = Number(trx.tag);
}
var preparedTrx = await this.client.autofill(transaction);
preparedTrx.LastLedgerSequence = preparedTrx.LastLedgerSequence+XRPL.LLIAdd;
return {
from : preparedTrx.Account,
to : preparedTrx.Destination,
currency : preparedTrx.Amount.currency?this.#cur40ToCur(preparedTrx.Amount.currency):this.symbol,
amount : preparedTrx.Amount.value?preparedTrx.Amount.value:xrpl.dropsToXrp(preparedTrx.Amount),
fee : xrpl.dropsToXrp(preparedTrx.Fee),
tag : preparedTrx.DestinationTag,
preparedTransaction : preparedTrx
}
}
checkCurrency(cur){
if (cur === this.symbol){
return { error : "La devise renseignée ne peut pas être "+this.symbol };
}
if (cur.length != 3 && cur.length != 40){
if (cur.length < 2){
return { error : "La devise renseignée doit avoir au moins 2 caractères" };
} else if (cur.length > XRPL.curMaxLen && cur.length != 40){
return { error : "La devise renseignée doit être un code court ou 40 caractères en hexadecimal" };
} else {
return { currency : this.#curToCur40(cur) };
}
}
return { currency : cur };
}
isValidMax(max){
return (max || false) && max.match(/^[1-9][0-9]+$/);
}
async prepareTrustSet(trx){
var transaction = {
TransactionType: "TrustSet",
Account: trx.from,
LimitAmount : {
currency: trx.currency,
issuer: trx.issuer,
value: trx.max
}
}
if (trx.type == "revoke"){
transaction.Flags = xrpl.TrustSetFlags.tfClearFreeze | xrpl.TrustSetFlags.tfSetNoRipple;
}
var preparedTrx = await this.client.autofill(transaction);
preparedTrx.LastLedgerSequence = preparedTrx.LastLedgerSequence+XRPL.LLIAdd;
return {
from : preparedTrx.Account,
issuer : preparedTrx.LimitAmount.issuer,
currency : this.#cur40ToCur(preparedTrx.LimitAmount.currency),
max : preparedTrx.LimitAmount.value,
fee : xrpl.dropsToXrp(preparedTrx.Fee),
preparedTransaction : preparedTrx
}
}
async prepareDeleteAccount(trx){
var transaction = {
TransactionType: 'AccountDelete',
Account: trx.from,
Destination: trx.to
}
if (trx.tag){
transaction.DestinationTag = parseInt(trx.tag);
}
var preparedTrx = await this.client.autofill(transaction);
preparedTrx.LastLedgerSequence = preparedTrx.LastLedgerSequence+XRPL.LLIAdd;
return {
from : preparedTrx.Account,
to : preparedTrx.Destination,
fee : xrpl.dropsToXrp(preparedTrx.Fee),
tag : preparedTrx.DestinationTag,
preparedTransaction : preparedTrx
}
}
async getSignersList(addr){
var account_info = await this.client.request({
"command": "account_info",
"account": addr,
"ledger_index": "validated",
"signer_lists":true
});
return {
signers : account_info.result.signer_lists.length==0?[]:[account_info.result.signer_lists[0].SignerEntries[0].SignerEntry.Account],
masterDisabled : account_info.result.account_flags.disableMasterKey
};
}
async prepareSigners(action, addr, signer){
var transaction;
switch(action){
case "set":
transaction = {
TransactionType: 'SignerListSet',
Account: addr,
SignerQuorum: 1,
SignerEntries: [
{SignerEntry:{Account:signer,SignerWeight:1}}//,...
]
};
break;
case "del":
transaction = {
TransactionType: 'SignerListSet',
Account: addr,
SignerQuorum: 0
};
break;
case "enable":
transaction = {
TransactionType: 'AccountSet',
Account: addr,
ClearFlag:xrpl.AccountSetAsfFlags.asfDisableMaster
};
break;
case "disable":
transaction = {
TransactionType: 'AccountSet',
Account: addr,
SetFlag:xrpl.AccountSetAsfFlags.asfDisableMaster
};
break;
default:
return false;
}
var preparedTrx = await this.client.autofill(transaction);
preparedTrx.LastLedgerSequence = preparedTrx.LastLedgerSequence+XRPL.LLIAdd;
return {
from : preparedTrx.Account,
fee : xrpl.dropsToXrp(preparedTrx.Fee),
signer : signer,
preparedTransaction : preparedTrx
}
}
},
//------------------------------------- XLM -------------------------------------------
"XLM":class XLM {
static serverURL = { "main":"https://horizon.stellar.org", "test":"https://horizon-testnet.stellar.org"};
static isLibLoaded(){
return !(typeof StellarSdk === "undefined");
}
static TRXTimeout = 240;//delay in second to add as a timeout of the transaction to ensure we have time to do the transaction with USB key
symbol = "XLM";
constructor(mt) {
this.main = (mt || false) && mt=="main";
this.server = new StellarSdk.Horizon.Server(XLM.serverURL[this.main?"main":"test"]);
}
isMain(){
return this.main;
}
async connect(){
this.isConnected = true;
}
switchServer(mt){
if (!mt){
mt = this.main?"test":"main";
}
mt = mt=="main";
if (this.main && !mt){//we are main, we go test
this.server = new StellarSdk.Horizon.Server(XLM.serverURL["test"]);
this.main = false;
} else if (!this.main && mt){//we are test, we go main
this.server = new StellarSdk.Horizon.Server(XLM.serverURL["main"]);
this.main = true;
}
}
tryReadError(err){
var errRet = {};
if (err.message == "Not Found"){
errRet.accountNotFound = true;
errRet.errorMsg = "Account not found";
} else if (err.response && err.response.data && err.response.data.extras && err.response.data.extras.result_codes && err.response.data.extras.result_codes){
errRet.errorMsg = err.toString()+" - Reason : <br/>"+JSON.stringify(err.response.data.extras.result_codes,null, 3);
} else {
errRet.errorMsg = err.toString();
}
return errRet;
}
isValidAddress(addr){
return (addr || false) && StellarSdk.StrKey.isValidEd25519PublicKey(addr);
}
isValidTag(tag){
return (tag || true) && tag.match(/^[A-Za-z0-9]*$/);
}
hrCurrency(cur) {//human readable currency
return cur;
}
#round(n){
return Number(n.toFixed(7));
}
async getAccountInfo(addr){
this.accountAddr = addr;
var account_info = await this.server.loadAccount(addr);
this.assets = [];
this.native = {};
for (var asset of account_info.balances){
if (asset.asset_type === "native"){
this.native = {currency:"XLM", balance:asset.balance};
} else {
this.assets.push({currency:asset.asset_code, balance:asset.balance, issuer:asset.asset_issuer});
}
}
var baseReserve = 0.5;//TODO get from blockchain, but where ?
var reserve = baseReserve*(2+account_info.subentry_count);
return {
balance : this.native.balance,
reserve : this.#round(reserve),
available : this.#round(this.native.balance-reserve)
};
}
async getNonNativeBalances(addr, refresh){
if (refresh){
await this.getAccountInfo(addr);
}
return this.assets;
}
hasFundsLink(){
return "https://lab.stellar.org/account/fund";
}
async addFunds(addr){}
isValidAmount(trx){
if (trx.asset.issuer){//non-native asset
if (trx.amount.match(/^[1-9][0-9]*(\.[0-9]+)?$/) == null){
return "Le montant est mal formé";
}
} else {//native asset limited to 7 decimals
if (trx.amount.match(/^[1-9][0-9]*(\.[0-9]{1,7})?$/) == null){
return "Il y a trop de décimales ou le montant est mal formé";
}
}
if (trx.amount <= 0){
return "Le montant doit être supérieur à 0";
}
if(trx.amount > Number(trx.asset.balance)){
return "Le montant est supérieur au solde disponible";
}
return true;
}
async prepareSendTransaction(trx){
var destAccountExists = false;
try {
await this.server.loadAccount(trx.to);
destAccountExists = true;
} catch(err){
if (err.message!="Not Found"){
console.error(err);
}
}
const account = await this.server.loadAccount(trx.from);
var ope;
if (destAccountExists){
ope = StellarSdk.Operation.payment({
destination: trx.to,
asset: trx.asset.issuer?new StellarSdk.Asset(trx.asset.currency,trx.asset.issuer):StellarSdk.Asset.native(),
amount : trx.amount,
});
} else {
ope = StellarSdk.Operation.createAccount({
destination: trx.to,
startingBalance: trx.amount,
});
}
const ntws = this.isMain()?StellarSdk.Networks.PUBLIC:StellarSdk.Networks.TESTNET;
const fee = await this.server.fetchBaseFee();
var preparedTrx = new StellarSdk.TransactionBuilder(account, { fee, networkPassphrase: ntws })
.addOperation(ope)
.setTimeout(XLM.TRXTimeout);
if (trx.tag){
preparedTrx = preparedTrx.addMemo(StellarSdk.Memo.text(trx.tag));
}
preparedTrx = preparedTrx.build().toXDR();
var check = StellarSdk.TransactionBuilder.fromXDR(preparedTrx,ntws);
return {
from : check._source,
to : check._operations[0].destination,
currency : destAccountExists?check._operations[0].asset.code:this.symbol,
amount : destAccountExists?check._operations[0].amount.replace(/\.?0+$/,''):check._operations[0].startingBalance.replace(/\.?0+$/,''),
fee : (check._fee/Math.pow(10,7)).toFixed(7).replace(/0+$/,''),
tag : check._memo._value?(new TextDecoder().decode(check._memo._value)):check._memo._value,
preparedTransaction : {
"XDR" : preparedTrx,
"Network" : ntws
}
}
}
checkCurrency(cur){
if (cur === this.symbol){
return { error : "La devise renseignée ne peut pas être "+this.symbol };
}
if (cur.length < 2){
return { error : "La devise renseignée doit avoir au moins 2 caractères" };
}
return { currency : cur };
}
isValidMax(max){
return (max || false) && max.match(/^[1-9][0-9]+$/);
}
async prepareTrustSet(trx){
const account = await this.server.loadAccount(trx.from);
const ntws = this.isMain()?StellarSdk.Networks.PUBLIC:StellarSdk.Networks.TESTNET;
const fee = await this.server.fetchBaseFee();
var preparedTrx = new StellarSdk.TransactionBuilder(account, { fee, networkPassphrase: ntws })
.addOperation(StellarSdk.Operation.changeTrust({
asset: new StellarSdk.Asset(trx.currency,trx.issuer),
limit: trx.type=="revoke"?"0":trx.max
}))
.setTimeout(XLM.TRXTimeout);
preparedTrx = preparedTrx.build().toXDR();
var check = StellarSdk.TransactionBuilder.fromXDR(preparedTrx,ntws);
return {
from : check._source,
issuer : check._operations[0].line.issuer,
currency : check._operations[0].line.code,
max : check._operations[0].limit.replace(/\.?0+$/,''),
fee : (check._fee/Math.pow(10,7)).toFixed(7).replace(/0+$/,''),
preparedTransaction : {
"XDR" : preparedTrx,
"Network" : ntws
}
}
}
async prepareDeleteAccount(trx){
const account = await this.server.loadAccount(trx.from);
const ntws = this.isMain()?StellarSdk.Networks.PUBLIC:StellarSdk.Networks.TESTNET;
const fee = await this.server.fetchBaseFee();
var preparedTrx = new StellarSdk.TransactionBuilder(account, { fee, networkPassphrase: ntws })
.addOperation(StellarSdk.Operation.accountMerge({
destination: trx.to
}))
.setTimeout(XLM.TRXTimeout);
if (trx.tag){
preparedTrx = preparedTrx.addMemo(StellarSdk.Memo.text(trx.tag));
}
preparedTrx = preparedTrx.build().toXDR();
var check = StellarSdk.TransactionBuilder.fromXDR(preparedTrx,ntws);
return {
from : check._source,
to : check._operations[0].destination,
fee : (check._fee/Math.pow(10,7)).toFixed(7).replace(/0+$/,''),
tag : check._memo._value?(new TextDecoder().decode(check._memo._value)):check._memo._value,
preparedTransaction : {
"XDR" : preparedTrx,
"Network" : ntws
}
}
}
async getSignersList(addr){
var account_info = await this.server.loadAccount(addr);
var masterSigner,signers=[];
for (var i=0;i<account_info.signers.length;i++){
if (account_info.signers[i].key === this.accountAddr){
masterSigner = account_info.signers[i];
} else {
signers.push(account_info.signers[i]);
}
}
return {
signers : signers.length==0?[]:signers.map(function(s){return s.key;}),
masterDisabled : masterSigner.weight==0
};
}
async prepareSigners(action, addr, signer){
const account = await this.server.loadAccount(addr);
const ntws = this.isMain()?StellarSdk.Networks.PUBLIC:StellarSdk.Networks.TESTNET;
const fee = await this.server.fetchBaseFee();
var options;
switch(action){
case "set":
case "del":
options = {
signer : {
ed25519PublicKey:signer,
weight:action==="set"?1:0
}
};
break;
case "enable":
options = {
masterWeight:1
};
break;
case "disable":
if (account.signers.length > 1){
options = {
masterWeight:0
};
} else {
return false;//security : do not del master if no signer exists
}
break;
default:
return false;
}
var preparedTrx = new StellarSdk.TransactionBuilder(account, { fee, networkPassphrase: ntws })
.addOperation(StellarSdk.Operation.setOptions(options))
.setTimeout(XLM.TRXTimeout);
preparedTrx = preparedTrx.build().toXDR();
var check = StellarSdk.TransactionBuilder.fromXDR(preparedTrx,ntws);
return {
from : check._source,
fee : (check._fee/Math.pow(10,7)).toFixed(7).replace(/0+$/,''),
signer : signer,
preparedTransaction : {
"XDR" : preparedTrx,
"Network" : ntws
}
}
}
}
};
var NETWORK;
$(document).on('NETWORK_CHANGED', (e,n,s) => {
var _class_ = CLASSES[n];
if (_class_.isLibLoaded()){
NETWORK = new _class_(s);
$('#addFundsBtn').hide();
$('#d_nowWhat').hide();
$('#d_accountValid').css('visibility', 'hidden');
$('#libCompromised').hide();
$('#bg-cur span.cur').text(NETWORK.symbol);
$('#amount').attr('placeholder',"Montant en "+NETWORK.symbol);
$('span.cur').text(NETWORK.symbol);
$('.container').show();
} else {
$('#libCompromised span').text(n);
$('#libCompromised').show();
$('.container').hide();
}
});
</script>
<!-- MAIN -->
<style type="text/css">
.tab-pane {
margin-top:5px;
background-color:#f0f8ffcc;
border-radius:10px;
padding-bottom:10px;
}
.fade:not(.show) {
display: none;
}
li.list-group-item {
padding:5px;
}
.h_signers {
border-bottom: 2px #333 solid;
}
.help_signer {
display:none;
}
</style>
<script>
const _online_ = {};
$(function(){
$('input[type="radio"][name="ServerRadio"]').on("change",function() {
NETWORK.switchServer(this.value);
$('#addFundsBtn').hide();
$('#d_nowWhat').hide();
$('#d_accountValid').css('visibility', 'hidden');
if (this.value === "main"){
$('#bg-cur').removeClass('bg-danger').addClass('bg-success');
} else {
$('#bg-cur').removeClass('bg-success').addClass('bg-danger');
}
});
$('input[type="radio"][name="ServerRadio"]:checked').trigger("change");
});
function setErr($e, msg, $parentIfNotStandard){
$e.addClass("is-invalid");
$('.invalid-feedback',$parentIfNotStandard?$parentIfNotStandard:$e.parent()).html(msg).show();
}
function clearErr($e, $parentIfNotStandard){
$e.removeClass("is-invalid");
$('.invalid-feedback',$parentIfNotStandard?$parentIfNotStandard:$e.parent()).hide();
}
function shortenAddr(addr){
return addr.substr(0,4)+"..."+addr.substr(addr.length - 4);
}
$(function(){
if (window.navigator && !window.navigator.onLine){
$('#nav-state-offline').show();
}
$('#asset_currency').on("input", function(){
var checkCur = NETWORK.checkCurrency($(this).val());
if (checkCur.currency){
$('.nCurrency').text(checkCur.currency);
} else {
$('.nCurrency').text("");
}
});
});
_online_.viewDetails = async function(refresh){
var $checkAddrBtn = $("#checkAddrBtn");
var $fromAddr = $('#accountAddr');
clearErr($fromAddr);
var addr = $('#accountAddr').val().trim();
if (!NETWORK.isValidAddress(addr)){
setErr($fromAddr,"L'adresse fournie semble être mal formée");
return;
}
if (!refresh){
$('.nav-link').removeClass('active');
$('.tab-pane').removeClass("active show");
}
$('#addFundsBtn').hide();
try {
$checkAddrBtn.prepend('<span class="spinner-grow spinner-grow-sm"></span> ').attr('disabled','true');
await NETWORK.connect();
var accountInfo = await NETWORK.getAccountInfo(addr);
$('#s_balance').text(accountInfo.balance);
$('#s_reserves').text(accountInfo.reserve);
$('#s_available').text(accountInfo.available);
$('#d_accountValid').css('visibility', 'visible');
$('#d_nowWhat').show();
$.get("https://api.bitget.com/api/v2/spot/market/tickers?symbol="+NETWORK.symbol+"USDT",function(data){
$('#dollarAmount').html(" / <b>( "+(data.data[0].lastPr*Number(accountInfo.balance)).toFixed(2)+"$ )</b>");
});
try {
$('#d_accountValid .alert ul.list-group').remove();
var $newUL = $('<ul class="list-group mt-1" style="padding-left:2rem;width:60%;"><b><a href="javascript:;" onClick="$(\'#d_accountValid .alert ul.list-group div\').toggle()">Autres assets</a> :</b><div style="display:none;"></div></ul>');
var balances = await NETWORK.getNonNativeBalances(addr);
for (var bal of balances){
$('div',$newUL).append('<li class="list-group-item list-group-item-success d-flex justify-content-between align-items-center">'+NETWORK.hrCurrency(bal.currency)+'<span class="badge text-bg-primary rounded-pill">'+bal.balance+'</span></li>');
}
if (balances.length > 0){
$('#d_accountValid .alert').append($newUL);
}
} catch (error){
console.error(NETWORK.tryReadError(error));
}
} catch (error){
var err = NETWORK.tryReadError(error);
if (err.accountNotFound){
setErr($fromAddr,"Ce compte n'existe pas encore sur la blockchain. Ajouter des fonds d'abord.");
if (!NETWORK.isMain()){
$('#addFundsBtn').show();
}
} else {
console.error(err);
setErr($fromAddr,"Il y a surement une erreur dans l'adresse de votre compte : <b>"+err.errorMsg+"</b>");
}
$('#d_accountValid').css('visibility', 'hidden');
$('#d_nowWhat').hide();
} finally {
$("span.spinner-grow",$checkAddrBtn.removeAttr('disabled')).remove();
}
};
_online_.addFunds = async function(){
if (NETWORK.hasFundsLink()){
window.open(NETWORK.hasFundsLink(), '_blank').focus();
} else {
var $fromAddr = $('#accountAddr');
var addr = $fromAddr.val().trim();
try {
setErr($fromAddr,"Envoi de fonds vers votre wallet, veuillez patienter...");
$('#addFundsBtn').prepend('<span class="spinner-grow spinner-grow-sm"></span> ').attr('disabled','true');
await NETWORK.addFunds(addr);
setErr($fromAddr,"Envoi effectué avec succès...");
await _online_.viewDetails();
$('#addFundsBtn').hide();
} catch (err){
err = NETWORK.tryReadError(err);
console.error(err);
setErr($fromAddr,"Erreur lors de l'ajout des fonds : <b>"+err.errorMsg+"</b>");
} finally {
$("span.spinner-grow",$('#addFundsBtn').removeAttr('disabled')).remove();
}
}
};
_online_.sendFundsPrepare = async function(refresh){
if (refresh){
await _online_.viewDetails(refresh);
}
$sendSelectCur = $('#sendSelectCur');
var addr = $('#accountAddr').val().trim();
$sendSelectCur.empty().append('<option value="'+NETWORK.symbol+'" selected>'+NETWORK.symbol+' (max:'+$('#s_available').text()+')</option>');
try {
var assets = await NETWORK.getNonNativeBalances(addr,refresh);
_online_.sendCurList = {};
for (var a of assets){
if (a.balance > 0){
var cur = NETWORK.hrCurrency(a.currency);
_online_.sendCurList[cur] = a;
$sendSelectCur.append('<option value="'+cur+'">'+cur+' (max:'+a.balance+')</option>');
}
}
$sendSelectCur.off('change').on('change',function(){
$('#amount').attr('placeholder',"Montant en "+this.value);
});
} catch (error){
console.error(NETWORK.tryReadError(error));
}
};
_online_.max = function(){
if (_online_.sendCurList && _online_.sendCurList[$('#sendSelectCur').val()]){
$('#amount').val(_online_.sendCurList[$('#sendSelectCur').val()].balance);
} else {
$('#amount').val($('#s_available').text());
}
};
_online_.prepareSend = async function(){
var $destAddr = $('#destAddr');
clearErr($destAddr);
var $famount = $('#amount');
clearErr($famount,$famount.parent().parent());
var $ftag = $('#destTag');
clearErr($ftag);
var hasError = false;
var addr = $('#accountAddr').val().trim();
var dest = $destAddr.val().trim();
if (!NETWORK.isValidAddress(dest)){
setErr($destAddr, "L'adresse de destination fournie semble être mal formée");
hasError = true;
}
var tag = $ftag.val().trim();
if (!NETWORK.isValidTag(tag)){
setErr($ftag, "Le tag/memo est incorrect");
hasError = true;
}
var amount = $famount.val().trim();
var trxDetails = {
from : addr,
to : dest,
amount : amount
};
if (_online_.sendCurList && _online_.sendCurList[$('#sendSelectCur').val()]){
var asset = _online_.sendCurList[$('#sendSelectCur').val()];
trxDetails.asset = _online_.sendCurList[$('#sendSelectCur').val()];
} else {
trxDetails.asset = {currency:NETWORK.symbol, balance:$('#s_available').text()};
}
if (NETWORK.isValidAmount(trxDetails) !== true){
setErr($famount, NETWORK.isValidAmount(trxDetails),$famount.parent().parent());
hasError = true;
}
if (hasError){
return;
}
if (tag.length > 0){
trxDetails.tag = tag;
}
try {
var $prepTrxBut = $("#prepareTrxBtn");
$('.invalid-feedback',$prepTrxBut.parent()).hide();
$prepTrxBut.prepend('<span class="spinner-grow spinner-grow-sm"></span> ').attr('disabled','true');
//prepare send
var prepedTrx = await NETWORK.prepareSendTransaction(trxDetails);
$('#trxResult').val(JSON.stringify(prepedTrx.preparedTransaction,null,3));
$("#recap-trx").html('<b>Transfert:</b> <span class="badge text-bg-secondary">'+shortenAddr(prepedTrx.from)+'</span> ➞ <span class="badge text-bg-secondary">'+shortenAddr(prepedTrx.to)+(prepedTrx.tag?' ('+prepedTrx.tag+')':'')+'</span> '+
'de <span class="badge text-bg-info">'+prepedTrx.amount+' '+prepedTrx.currency+'</span> + Frais : <span class="badge text-bg-warning">'+prepedTrx.fee+' '+NETWORK.symbol+'</span>');
$('#trxResultModal').modal('show');
$('#dwnldButton').off('click').on('click', function(){
_online_.download("Send_"+prepedTrx.amount+"_"+prepedTrx.currency);
});
} catch (error){
error = NETWORK.tryReadError(error);
console.error(error);
$('.invalid-feedback',$prepTrxBut.parent()).html("Une erreur est survenue : <b>"+error.errorMsg+"</b>").show();
} finally {
$("span.spinner-grow",$prepTrxBut.removeAttr('disabled')).remove();
}
};
_online_.trustList = async function(){
var addr = $('#accountAddr').val().trim();
var $trustSelect = $('#trustlist select');
$('#trustlist .input-group').hide();
$('#trustlist .text-danger').hide();
$('#trustlist .text-warning').hide();
$('#trustlist .text-primary').html('<span class="spinner-grow spinner-grow-sm"></span> Recherche des trustlines en cours...').show();
$('#trustlist').show();
try {
var assets = await NETWORK.getNonNativeBalances(addr,true);
if (assets.length == 0){
$('#trustlist .text-danger',).html("Il n'a pas de trustline sur ce compte").show();
$('#trustlist .text-primary').hide();
} else {
$trustSelect.empty().append('<option selected>Selectionnez la trustline à révoquer</option>');
_online_.trustlist = {};
for (var a of assets){
if (a.balance == 0){
_online_.trustlist[a.issuer] = a;
$trustSelect.append('<option value="'+a.issuer+'">'+NETWORK.hrCurrency(a.currency)+' ('+a.issuer+')</option>');
}
}
$trustSelect.off('change').on('change',function(){
if (!_online_.trustlist[this.value]){
$('#prepareTrustBtn').prop('disabled',true);
} else if (_online_.trustlist[this.value].balance > 0){
$('#trustlist .text-warning').show();
$('#prepareTrustBtn').prop('disabled',true);
} else {
$('#trustlist .text-warning').hide();
$('#prepareTrustBtn').prop('disabled',false);
}
});
$('#trustlist .input-group').show();
$('#trustlist .text-primary').html('Veuillez choisir la trustline à révoquer:').show();
}
} catch (error){
error = NETWORK.tryReadError(error);
$('#trustlist .text-primary').hide();
console.error(error);
$('#trustlist .text-danger',).html("Une erreur est survenue : <b>"+error.errorMsg+"</b>").show();
}
};
_online_.prepareTrustSet = async function(){
var trustRBVal = $('input[name="trustSetRB"]:checked').val();
var trxDetails = {
from: $('#accountAddr').val().trim()
}
if (trustRBVal=="set"){
var $fissuer = $('#asset_issuer');
clearErr($fissuer);
var $fcur = $('#asset_currency');
clearErr($fcur);
var $fmax = $('#asset_max');
clearErr($fmax);
var hasError = false;
var issuer = $fissuer.val().trim();
var currency = $fcur.val().trim().toUpperCase();
var max = $fmax.val().trim();
if (!NETWORK.isValidAddress(issuer)){
setErr($fissuer, "L'adresse du fournisseur semble être mal formée");
hasError = true;
}
var curCheck = NETWORK.checkCurrency(currency);
if (curCheck.error){
setErr($fcur, curCheck.error);
hasError = true;
}
if (!NETWORK.isValidMax(max)){
setErr($fmax, "La valeur max renseignée n'est pas correcte");
hasError = true;
}
if (hasError){
return;
}
$.extend(trxDetails, {
type : "add",
issuer: issuer,
currency: curCheck.currency,
max: max
});
} else {//trustRBVal=="rev"
var trustSelectVal = _online_.trustlist[$('#trustlist select').val()];
$.extend(trxDetails, {
type : "revoke",
currency: trustSelectVal.currency,
issuer: trustSelectVal.issuer,
max: "0"
});
}
try {
var $prepareTrustBtn = $("#prepareTrustBtn");
$('.invalid-feedback',$prepareTrustBtn.parent()).hide();
$prepareTrustBtn.prepend('<span class="spinner-grow spinner-grow-sm"></span> ').attr('disabled','true');
//Prepare trust set
var prepedTrx = await NETWORK.prepareTrustSet(trxDetails);
$('#trxResult').val(JSON.stringify(prepedTrx.preparedTransaction,null,3));
$("#recap-trx").html('<b>'+(trustRBVal=="set"?'Ajout de':'Révocation de')+' </b> <span class="badge text-bg-primary">'+prepedTrx.currency+'</span> - Sur : <span class="badge text-bg-secondary">'+shortenAddr(prepedTrx.from)+'</span> - Issuer : <span class="badge text-bg-secondary">'+shortenAddr(prepedTrx.issuer)+'</span> '+
(trustRBVal=="set"?'max : <span class="badge text-bg-info">'+prepedTrx.max+'</span>':'')+' + Frais : <span class="badge text-bg-warning">'+prepedTrx.fee+' '+NETWORK.symbol+'</span>');
$('#trxResultModal').modal('show');
$('#dwnldButton').off('click').on('click', function(){
_online_.download((trustRBVal=="set"?"SetTrust_":"DelTrust_")+prepedTrx.currency);
});
} catch (error){
error = NETWORK.tryReadError(error);
console.error(error);
$('.invalid-feedback',$prepareTrustBtn.parent()).html("Une erreur est survenue : <b>"+error.errorMsg+"</b>").show();
$("span.spinner-grow",$prepareTrustBtn.removeAttr('disabled')).remove();
} finally {
$("span.spinner-grow",$prepareTrustBtn.removeAttr('disabled')).remove();
}
};
_online_.deleteAccoutTabClicked = async function(){
var addr = $('#accountAddr').val().trim();
var assets = await NETWORK.getNonNativeBalances(addr,true);
if (assets.length > 0){
$('#delete-account-assets').show();
$('#deleteAccountBtn').attr('disabled','true');
} else {
$('#delete-account-assets').hide();
$('#deleteAccountBtn').removeAttr('disabled');
}
};
_online_.prepareDeleteAccount = async function(){
var $addr = $('#accountAddr');
var $destRelAddr = $('#destRelAddr');
clearErr($destRelAddr);
var $rtag = $('#destRelTag');
clearErr($rtag);
var hasError = false;
var addr = $addr.val().trim();
var destRelAddr = $destRelAddr.val().trim();
if (!NETWORK.isValidAddress(destRelAddr)){
setErr($destRelAddr, "L'adresse de destination fournie semble être mal formée");
hasError = true;
}
var trxDetails = {
from: addr,
to: destRelAddr
}
var tag = $rtag.val().trim();
if (!NETWORK.isValidTag(tag)){
setErr($rtag, "Le tag/memo est incorrect");
hasError = true;
}
if (hasError){
return;
}
if (tag.length > 0){
trxDetails.tag = tag;
}
try {
var $prepareDelBtn = $("#prepareDelBtn");
$('.invalid-feedback',$prepareDelBtn.parent()).hide();
$prepareDelBtn.prepend('<span class="spinner-grow spinner-grow-sm"></span> ').attr('disabled','true');
//Prepare delete account
var prepedTrx = await NETWORK.prepareDeleteAccount(trxDetails);
$('#trxResult').val(JSON.stringify(prepedTrx.preparedTransaction,null,3));
$("#recap-trx").html('<b>Suppression du compte</b> <span class="badge text-bg-primary">'+shortenAddr(prepedTrx.from)+'</span> - Réliquat de '+NETWORK.symbol+' envoyé à : <span class="badge text-bg-secondary">'+shortenAddr(prepedTrx.to)+(prepedTrx.tag?' ('+prepedTrx.tag+')':'')+'</span>'+
' + Frais : <span class="badge text-bg-warning">'+prepedTrx.fee+' '+NETWORK.symbol+'</span>');
$('#trxResultModal').modal('show');
$('#dwnldButton').off('click').on('click', function(){
_online_.download("Del_"+shortenAddr(prepedTrx.from));
});
} catch (error){
error = NETWORK.tryReadError(error);
console.error(error);
$('.invalid-feedback',$prepareDelBtn.parent()).html("Une erreur est survenue : <b>"+error.errorMsg+"</b>").show();
} finally {
$("span.spinner-grow",$prepareDelBtn.removeAttr('disabled')).remove();
}
};
_online_.setSignersTabClicked = async function(){
var $addr = $('#accountAddr');
var addr = $addr.val().trim();
var $button = $("#b_signers_reload");
$button.prepend('<span class="spinner-grow spinner-grow-sm"></span> ').attr('disabled','true');
var signer_info = await NETWORK.getSignersList(addr);
$("span.spinner-grow",$button.removeAttr('disabled')).remove();
$('#d_signers_masteronoff').show();
if (signer_info.masterDisabled){
$('#b_signers_master_off').hide();
$('#b_signers_master_on').show();
$('#signer_masterActive').attr('class','badge text-bg-danger').text('DÉSACTIVÉ');
} else {
if (signer_info.signers.length > 0){
$('#b_signers_master_off').show();
} else {
$('#b_signers_master_off').hide();
$('#d_signers_masteronoff').hide();
}
$('#b_signers_master_on').hide();
$('#signer_masterActive').attr('class','badge text-bg-success').text('ACTIF');
}
if (signer_info.signers.length > 0){
$('#signer_addr').val(signer_info.signers[0]);
if (!signer_info.masterDisabled){
$('#d_signers_del').show();
} else {
$('#d_signers_del').hide();
}
$('#signer_signerAddr').attr('class','badge text-bg-primary').html(signer_info.signers.map(function(s){
return '<b>'+s.substr(0,4)+'</b><span style="color:#bbb;">'+s.substr(4,s.length-8)+'</span><b>'+s.substr(s.length - 4)+'</b>';
}).join(",<br/>"));
} else {
$('#signer_addr').val("");
$('#d_signers_del').hide();
$('#signer_signerAddr').attr('class','badge text-bg-secondary').text("Indéfini");
}
};
_online_.prepareSigners = async function(action){
var $addr = $('#accountAddr');
var addr = $addr.val().trim();
var $signerAddr = $('#signer_addr');
var signerAddr = $signerAddr.val();
clearErr($signerAddr);
var $button, prepedTrxFund, filenamePrefix, command;
switch(action){
case "set":
if (!NETWORK.isValidAddress(signerAddr)){
setErr($signerAddr, "L'adresse du signataire fournie semble être mal formée");
return;
}
$button = $("#b_signers_set");
filenamePrefix = "SignerSet_";
command = "Définir signataire de ";
prepedTrxFund = async function () {return await NETWORK.prepareSigners(action, addr, signerAddr)};
break;
case "del":
if (!NETWORK.isValidAddress(signerAddr)){
setErr($signerAddr, "L'adresse du signataire fournie semble être mal formée");
return;
}
$button = $("#b_signers_del");
filenamePrefix = "SignerDel_";
command = "Supprimer signataire de ";
prepedTrxFund = async function () {return await NETWORK.prepareSigners(action, addr, signerAddr)};
break;
case "enable":
$button = $("#b_signers_master_on");
filenamePrefix = "MasterOn_";
command = "Activer clé maître sur ";
prepedTrxFund = async function () {return await NETWORK.prepareSigners(action, addr)};
break;
case "disable":
$button = $("#b_signers_master_off");
filenamePrefix = "MasterOff_";
command = "Désactiver clé maître sur ";
prepedTrxFund = async function () {return await NETWORK.prepareSigners(action, addr)};
break;
default:
return;
}
try {
$('.invalid-feedback',$button.parent()).hide();
$button.prepend('<span class="spinner-grow spinner-grow-sm"></span> ').attr('disabled','true');
//Prepare signer update
var prepedTrx = await prepedTrxFund();
if (prepedTrx === false){
$('.invalid-feedback',$button.parent()).html("Impossible de réaliser cette opération, car elle pourrait compromettre l'accès à ce compte").show();
} else {
$('#trxResult').val(JSON.stringify(prepedTrx.preparedTransaction,null,3));
$("#recap-trx").html('<b>'+command+'</b> <span class="badge text-bg-primary">'+shortenAddr(prepedTrx.from)+'</span>'+(action=="set"?' - Signataire : <span class="badge text-bg-secondary">'+shortenAddr(prepedTrx.signer)+'</span>':'')+
' + Frais : <span class="badge text-bg-warning">'+prepedTrx.fee+' '+NETWORK.symbol+'</span>');
$('#trxResultModal').modal('show');
$('#dwnldButton').off('click').on('click', function(){
_online_.download(filenamePrefix+shortenAddr(addr));
});
$('#b_signers_reload').show();
}
} catch (error){
error = NETWORK.tryReadError(error);
console.error(error);
$('.invalid-feedback',$button.parent()).html("Une erreur est survenue : <b>"+error.errorMsg+"</b>").show();
} finally {
$("span.spinner-grow",$button.removeAttr('disabled')).remove();
}
};
_online_.download = function(prefix){
var a = document.createElement('a');
var file = new Blob([$('#trxResult').val()], { type: 'application/json' });
a.href = URL.createObjectURL(file);
a.download = "#"+(new Date().getTime()%1000)+"_"+prefix+"-"+NETWORK.constructor.name+"-"+(NETWORK.isMain()?"main":"test")+".tosign.json";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
_online_.copy = function(){
var textarea = $('#trxResult');
textarea.select();
try {
document.execCommand('copy');
$('#copyBtn .badge').show();
setTimeout(function() { $('#copyBtn .badge').fadeOut(); }, 3000);
} catch (err) {
alert('Erreur lors de la copie dans le presse-papier :'+JSON.stringify(err,null,3));
}
};
</script>
</head>
<body class="pb-5">
<div class="text-danger fw-bold text-center mt-3" style="display:none;" id="libCompromised">
ATTENTION DANGER !<br/>
La librarie du <span>...</span> n'a pas pu être chargée <b>ou est compromise et a été altérée sur le CDN</b>, veuillez ne pas utiliser cette librarie.<br/>
Le hash d'integrité peut ne pas correspondre. (Voir les logs pour plus de détails)
</div>
<div class="container">
<div class="px-4 py-3 my-3 text-center">
<h2 class="display-6 fw-bold">Envoi d'une transaction 1/3<br/><b class="text-success">(Préparation - ONLINE)</b></h2>
<div class="col-lg-8 mx-auto">
<div style="display:none;" class="text-danger" id="nav-state-offline">
<span class="badge text-bg-danger">Vous êtes OFFLINE !</span> Vous devez être connecté à internet pour cette étape.<br/>
<i>Rétablissez la connexion internet et rechargez la page.</i><br/>
<button class="btn btn-danger mt-3" onclick="window.location.reload();">Recharger la page</button>
</div>
<p class="lead mb-4">
Cette étape vous permet de créer une transaction, que ce soit l'envoi de token natif,
la création d'une trustline le cas échéant, ou encore l'envoi de n'importe quel asset sur la blockchain.
</p>
</div>
</div>
<h4>Entrez l'adresse de votre compte</h4>
<div class="row">
<div class="col">
<div class="mb-3">
<label for="accountAddr" class="form-label">C'est l'adresse publique de votre compte, celle depuis laquelle vous souhaitez exécuter la transaction.</label>
<input type="text" class="form-control" value="" placeholder="Adresse de compte" id="accountAddr"/>
<div class="invalid-feedback"></div>
</div>
<button type="button" class="btn btn-info" onclick="_online_.viewDetails()" id="checkAddrBtn">Vérifier l'adresse</button>
<button type="button" class="btn btn-primary" onclick="_online_.addFunds()" id="addFundsBtn" style="display:none;">Mode Testnet - Ajouter des fonds</button>
</div>
<div class="col" id="d_accountValid" style="visibility:hidden;">
<div class="alert alert-success mt-4" role="alert">
<ul style="list-style-type:none;margin-bottom:0;">
<li><b class="text-success">Compte valide !</b></li>
<li>Balance : <span id="s_balance"></span> <span class="cur">XRP</span><span id="dollarAmount"></span></li>
<li><b>Disponible : <span id="s_available"></span> <span class="cur">XRP</span></b> (<span id="s_reserves"></span> <span class="cur">XRP</span> réservés)</li>
</ul>
</div>
</div>
</div>
<div id="d_nowWhat" style="display:none;">
<h4 class="mt-4">Que souhaitez-vous faire maintenant ?</h4>
<nav>
<div class="nav nav-pills" id="nav-tab" role="tablist">
<button class="nav-link" id="nav-send-tab" data-bs-toggle="tab" data-bs-target="#nav-send" onclick="_online_.sendFundsPrepare();" type="button" role="tab">Envoyer des fonds</button>
<button class="nav-link" id="nav-trust-tab" onclick="$('#trustSetCB').click();" data-bs-toggle="tab" data-bs-target="#nav-trust" type="button" role="tab">Ajouter/Révoquer une trustline</button>
<button class="nav-link" id="nav-remove-tab" onclick="_online_.deleteAccoutTabClicked();" data-bs-toggle="tab" data-bs-target="#nav-remove" type="button" role="tab">Supprimer un compte</button>
<button class="nav-link" id="nav-remove-tab" onclick="_online_.setSignersTabClicked();" data-bs-toggle="tab" data-bs-target="#nav-signers" type="button" role="tab">Gérer un signataire</button>
</div>
</nav>
<div class="tab-pane fade" id="nav-send">
<div class="row">
<div class="col-11 offset-1">
<h5 class="mt-3 fw-bold">Entrez maintenant la destination et le montant</h5>
<b class="text-danger" style="font-size:80%;"><span class="badge rounded-pill text-bg-danger">TIPS !</span> N'hésitez pas à tester avec de petits montants d'abord</b>
<div class="row mt-2">
<div class="col-5">
Envoyer des
<div class="input-group mb-3 mt-2 mb-2">
<select class="form-select" id="sendSelectCur"></select>
<button class="btn btn-outline-secondary" onclick="_online_.sendFundsPrepare(true);" type="button">⟳</button>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="mb-3">
<label for="destAddr" class="form-label">C'est l'adresse publique du compte vers lequel vous souhaitez envoyer des fonds :</label>
<input class="form-control" type="text" value="" placeholder="Adresse de destination" id="destAddr"/>
<div class="invalid-feedback"></div>
</div>
</div>
<div class="col-4">
<div class="mb-3">
<label for="destTag" class="form-label">Si nécessaire, entrez un tag/memo de destination :</label>
<input class="form-control" type="string" value="" placeholder="Tag/memo" id="destTag"/>
<div class="invalid-feedback"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-4">
<div class="mb-3">
<label for="amount" class="form-label">Entrez le montant que vous souhaitez envoyer :</label>