function createPaymentTransaction(remote, paymentData, orderRequestId, srcIssuer, amount){ var transaction = remote.createTransaction('Payment', paymentData); transaction.lastLedger(remote.getLedgerSequence() + 10); // Wait at most 10 ledger sequences // Save order info on the blockchain if (orderRequestId) { transaction.tx_json.Memos = [ { Memo: { MemoType: RippleUtils.stringToHex('OrderRequestId'), MemoData: RippleUtils.stringToHex(orderRequestId) } } ]; } // Append it if you got it if (srcIssuer) { var maxValue = amount.toString(); // Send all; original code from ripple-rest is: // new BigNumber(payment.source_amount.value).plus(payment.source_slippage || 0).toString(); transaction.sendMax({ value: maxValue, currency: 'EUR', // EUR foreveeer issuer: srcIssuer // Gotcha! }); } transaction.on('resubmit', function () { debug('resubmitting ', transaction); }); return transaction; }
}, function( err, res ) { if ( err ) { winston.error( "Error getting ledger from rippled:", err ); callback( err ); return; } // add/edit fields that aren't in rippled's json format var ledger = res.ledger; ledger.close_time_rpepoch = ledger.close_time; ledger.close_time_timestamp = ripple.utils.toTimestamp( ledger.close_time ); ledger.close_time_human = moment( ripple.utils.toTimestamp( ledger.close_time ) ).utc( ).format( "YYYY-MM-DD HH:mm:ss Z" ); ledger.from_rippled_api = true; // remove fields that do not appear in format defined above in parseLedger delete ledger.close_time; delete ledger.hash; delete ledger.accepted; delete ledger.totalCoins; delete ledger.closed; delete ledger.seqNum; // parse ints from strings ledger.ledger_index = parseInt(ledger.ledger_index, 10); ledger.total_coins = parseInt(ledger.total_coins, 10); // add exchange rate field to metadata entries ledger.transactions.forEach( function( transaction ) { transaction.metaData.AffectedNodes.forEach( function( affNode ) { var node = affNode.CreatedNode || affNode.ModifiedNode || affNode.DeletedNode; if ( node.LedgerEntryType === "Offer" ) { var fields = node.FinalFields || node.NewFields; if ( typeof fields.BookDirectory === "string" ) { node.exchange_rate = ripple.Amount.from_quality( fields.BookDirectory ).to_json( ).value; } } } ); } ); // check the transaction hash of the ledger we got from the api call var ledgerJsonTxHash = Ledger.from_json( ledger ).calc_tx_hash( ).to_hex( ); if ( ledgerJsonTxHash === ledger.transaction_hash ) { callback( null, ledger ); } else { callback( new Error( "Error with ledger from rippled api call, transactions do not hash to expected value" + "\n Actual: " + ledgerJsonTxHash + "\n Expected: " + ledger.transaction_hash + "\n\n Ledger: " + JSON.stringify( ledger ) + "\n\n" ) ); } } );
// parseRawLedgerHeader renames ledger header field names function parseRawLedgerHeader( rawHeader ) { return { account_hash: rawHeader.AccountSetHash, close_time_rpepoch: rawHeader.ClosingTime, close_time_timestamp: ripple.utils.toTimestamp( rawHeader.ClosingTime ), close_time_human: moment( ripple.utils.toTimestamp( rawHeader.ClosingTime ) ).utc( ).format( "YYYY-MM-DD HH:mm:ss Z" ), close_time_resolution: rawHeader.CloseTimeRes, ledger_hash: rawHeader.LedgerHash, ledger_index: rawHeader.LedgerSeq, parent_hash: rawHeader.PrevHash, total_coins: rawHeader.TotalCoins, transaction_hash: rawHeader.TransSetHash }; }
var formatRemoteLedger = function(ledger) { ledger.close_time_rpepoch = ledger.close_time; ledger.close_time_timestamp = ripple.utils.toTimestamp(ledger.close_time); ledger.close_time_human = moment(ripple.utils.toTimestamp(ledger.close_time)) .utc().format('YYYY-MM-DD HH:mm:ss Z'); ledger.from_rippled_api = true; delete ledger.close_time; delete ledger.hash; delete ledger.accepted; delete ledger.totalCoins; delete ledger.closed; delete ledger.seqNum; // parse ints from strings ledger.ledger_index = parseInt(ledger.ledger_index, 10); ledger.total_coins = parseInt(ledger.total_coins, 10); // add exchange rate field to metadata entries ledger.transactions.forEach(function(transaction) { if(!transaction.metaData || !transaction.metaData.AffectedNodes) { log.error('transaction in ledger: ' + ledger.ledger_index + ' does not have metaData'); return; } transaction.metaData.AffectedNodes.forEach(function(affNode) { var node = affNode.CreatedNode || affNode.ModifiedNode || affNode.DeletedNode; if (node.LedgerEntryType !== 'Offer') { return; } var fields = node.FinalFields || node.NewFields; if (typeof fields.BookDirectory === 'string') { node.exchange_rate = ripple.Amount.from_quality(fields.BookDirectory).to_json().value; } }); }); ledger._id = Client.addLeadingZeros(ledger.ledger_index); return ledger; }
engine.processor.getLedger(ledger_index, function (err, e) { if (err) winston.error("Error loading ledger: " + err.message); else { var ledger_time = new Date(utils.toTimestamp(e.ledger.close_time)); engine.aggregator.process(ledger_time, null, function (err) { if (err) winston.error("Error processing aggregate: " + err.message); engine.shutdown(); }); } });
remote.requestLedger(transaction.ledger_index, function(err, res) { if (err) { return res.json(404, { success: false, message: 'Transaction ledger not found' }); } if (typeof res.ledger.close_time === 'number') { transaction.date = ripple.utils.toTimestamp(res.ledger.close_time); } callback(null, transaction); });
function outgoingTransactionEntryToTransaction(db_entry) { var values = db_entry.values || db_entry; // Convert to format similar to getTx call var transaction = JSON.parse(values.tx_json); transaction.meta = transaction.meta || {}; transaction.meta.TransactionResult = values.rippled_result; transaction.ledger_index = values.ledger; transaction.hash = values.hash; transaction.finalized = values.finalized; transaction.date = ripple.utils.fromTimestamp(new Date(values.updated_at || db_entry.updated_at)); transaction.client_resource_id = values.client_resource_id; // Note that this value is used by notifications.js transaction.from_local_db = true; return transaction; };
function txToNotification(opts){ var address = opts.address, tx, meta, prev_tx_hash; if (opts.tx_entry || opts.tx) { tx = (opts.tx_entry ? opts.tx_entry.tx : opts.tx_entry) || opts.tx; meta = (opts.tx_entry ? opts.tx_entry.meta : tx.meta); } else { prev_tx_hash = opts.prev_tx_hash; } var notification = { address: address || '', type: (tx && tx.TransactionType ? tx.TransactionType.toLowerCase() : ''), tx_direction: '', tx_state: '', tx_result: (meta ? meta.TransactionResult : ''), tx_ledger: (tx && tx.ledger_index ? tx.ledger_index : ''), tx_hash: (tx && tx.hash ? tx.hash : ''), tx_timestamp: (tx && tx.date ? ripple.utils.toTimestamp(tx.date) : ''), tx_url: '', next_notification_url: '', confirmation_token: '' }; if (tx) { // Determine direction if (tx.Account) { if (address === tx.Account) { notification.tx_direction = 'outgoing'; } else if (tx.TransactionType === 'Payment' && tx.Destination !== address) { notification.tx_direction = 'passthrough'; } else { notification.tx_direction = 'incoming'; } } // State if (meta) { if (meta.TransactionResult === 'tesSUCCESS') { notification.tx_state = 'confirmed'; } else { notification.tx_state = 'failed'; } } // Add URLs if (notification.tx_hash) { // Add next_notification URL notification.next_notification_url = '/addresses/' + notification.address + '/next_notification/' + (notification.tx_hash ? notification.tx_hash : ''); if (notification.type) { // Add resource URL if (notification.type === 'payment') { notification.tx_url = '/addresses/' + notification.address + '/payments/' + notification.tx_hash; } else { notification.tx_url = '/addresses/' + notification.address + '/txs/' + notification.tx_hash; } } } } else { notification.type = 'none'; notification.tx_state = 'empty'; // Add next_notification URL notification.next_notification_url = '/addresses/' + notification.address + '/next_notification/' + (prev_tx_hash ? prev_tx_hash : ''); } return notification; }
function parsePaymentFromTx(tx, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } if (!opts.account) { callback(new Error('Internal Error. must supply opts.account')); return; } if (tx.TransactionType !== 'Payment') { callback(new Error('Not a payment. The transaction corresponding to the given identifier is not a payment.')); return; } var payment = { // User supplied source_account: tx.Account, source_tag: (tx.SourceTag ? '' + tx.SourceTag : ''), source_amount: (tx.SendMax ? (typeof tx.SendMax === 'object' ? tx.SendMax : { value: utils.dropsToXrp(tx.SendMax), currency: 'XRP', issuer: '' }) : (typeof tx.Amount === 'string' ? { value: utils.dropsToXrp(tx.Amount), currency: 'XRP', issuer: '' } : tx.Amount)), source_slippage: '0', destination_account: tx.Destination, destination_tag: (tx.DestinationTag ? '' + tx.DestinationTag : ''), destination_amount: (typeof tx.Amount === 'object' ? tx.Amount : { value: utils.dropsToXrp(tx.Amount), currency: 'XRP', issuer: '' }), // Advanced options invoice_id: tx.InvoiceID || '', paths: JSON.stringify(tx.Paths || []), no_direct_ripple: (tx.Flags & 0x00010000 ? true : false), partial_payment: (tx.Flags & 0x00020000 ? true : false), // Generated after validation direction: (opts.account ? (opts.account === tx.Account ? 'outgoing' : (opts.account === tx.Destination ? 'incoming' : 'passthrough')) : ''), state: tx.state || (tx.meta.TransactionResult === 'tesSUCCESS' ? 'validated' : 'failed'), result: tx.meta.TransactionResult || '', ledger: '' + (tx.inLedger || tx.ledger_index), hash: tx.hash || '', timestamp: (tx.date ? new Date(ripple.utils.toTimestamp(tx.date)).toISOString() : ''), fee: utils.dropsToXrp(tx.Fee) || '', source_balance_changes: [], destination_balance_changes: [] }; // Add source_balance_changes utils.parseBalanceChanges(tx, tx.Account).forEach(function(amount){ if (amount.value < 0) { payment.source_balance_changes.push(amount); } }); // Add destination_balance_changes utils.parseBalanceChanges(tx, tx.Destination).forEach(function(amount){ if (amount.value > 0) { payment.destination_balance_changes.push(amount); } }); callback(null, payment); }
function parsePaymentFromTx(tx, options, callback) { if (typeof options === 'function') { callback = options; options = {}; } if (!options.account) { if (callback !== void(0)) { callback(new Error('Internal Error. must supply options.account')); } return; } if (tx.TransactionType !== 'Payment') { if (callback !== void(0)) { callback(new Error('Not a payment. The transaction corresponding to the given identifier is not a payment.')); } return; } if (tx.meta !== void(0) && tx.meta.TransactionResult !== void(0)) { if (tx.meta.TransactionResult === 'tejSecretInvalid') { if (callback !== void(0)) { callback(new Error('Invalid secret provided.')); } return; } } var Amount; var isPartialPayment = tx.Flags & 0x00020000 ? true : false; // if there is a DeliveredAmount we should use it over Amount // there should always be a DeliveredAmount if the partial payment flag is set // also there shouldn't be a DeliveredAmount if there's no partial payment flag if(isPartialPayment && tx.meta && tx.meta.DeliveredAmount) { Amount = tx.meta.DeliveredAmount; } else { Amount = tx.Amount; } var payment = { // User supplied source_account: tx.Account, source_tag: (tx.SourceTag ? '' + tx.SourceTag : ''), source_amount: (tx.SendMax ? (typeof tx.SendMax === 'object' ? tx.SendMax : { value: utils.dropsToXrp(tx.SendMax), currency: 'XRP', issuer: '' }) : (typeof Amount === 'string' ? { value: utils.dropsToXrp(tx.Amount), currency: 'XRP', issuer: '' } : Amount)), source_slippage: '0', destination_account: tx.Destination, destination_tag: (tx.DestinationTag ? '' + tx.DestinationTag : ''), destination_amount: (typeof Amount === 'object' ? Amount : { value: utils.dropsToXrp(Amount), currency: 'XRP', issuer: '' }), // Advanced options invoice_id: tx.InvoiceID || '', paths: JSON.stringify(tx.Paths || []), no_direct_ripple: (tx.Flags & 0x00010000 ? true : false), partial_payment: isPartialPayment, // Generated after validation direction: (options.account ? (options.account === tx.Account ? 'outgoing' : (options.account === tx.Destination ? 'incoming' : 'passthrough')) : ''), state: tx.state || tx.meta ? (tx.meta.TransactionResult === 'tesSUCCESS' ? 'validated' : 'failed') : '', result: tx.meta ? tx.meta.TransactionResult : '', ledger: '' + (tx.inLedger || tx.ledger_index), hash: tx.hash || '', timestamp: (tx.date ? new Date(ripple.utils.toTimestamp(tx.date)).toISOString() : ''), fee: utils.dropsToXrp(tx.Fee) || '', source_balance_changes: [], destination_balance_changes: [] }; // Add source_balance_changes utils.parseBalanceChanges(tx, tx.Account).forEach(function(amount){ if (amount.value < 0) { payment.source_balance_changes.push(amount); } }); // Add destination_balance_changes utils.parseBalanceChanges(tx, tx.Destination).forEach(function(amount){ if (amount.value > 0) { payment.destination_balance_changes.push(amount); } }); if (Array.isArray(tx.Memos) && tx.Memos.length > 0) { payment.memos = []; for(var m=0; m<tx.Memos.length; m++) { payment.memos.push(tx.Memos[m].Memo); } } if (isPartialPayment && tx.meta && tx.meta.DeliveredAmount) { payment.destination_amount_submitted = (typeof tx.Amount === 'object' ? tx.Amount : { value: utils.dropsToXrp(tx.Amount), currency: 'XRP', issuer: '' }); payment.source_amount_submitted = (tx.SendMax ? (typeof tx.SendMax === 'object' ? tx.SendMax : { value: utils.dropsToXrp(tx.SendMax), currency: 'XRP', issuer: '' }) : (typeof tx.Amount === 'string' ? { value: utils.dropsToXrp(tx.Amount), currency: 'XRP', issuer: '' } : tx.Amount)); } return payment; };