mirror of
https://github.com/gosticks/iota.lib.java.git
synced 2026-10-03 21:16:59 +00:00
added documentation
This commit is contained in:
@@ -14,13 +14,13 @@ import java.util.*;
|
||||
|
||||
/**
|
||||
* IotaAPI Builder. Usage:
|
||||
* <p>
|
||||
*
|
||||
* IotaApiProxy api = IotaApiProxy.Builder
|
||||
* .protocol("http")
|
||||
* .nodeAddress("localhost")
|
||||
* .port(12345)
|
||||
* .build();
|
||||
* <p>
|
||||
*
|
||||
* GetNodeInfoResponse response = api.getNodeInfo();
|
||||
*
|
||||
* @author davassi
|
||||
@@ -39,11 +39,11 @@ public class IotaAPI extends IotaAPICore {
|
||||
* Generates a new address from a seed and returns the remainderAddress.
|
||||
* This is either done deterministically, or by providing the index of the new remainderAddress
|
||||
*
|
||||
* @param seed Tryte-encoded seed. It should be noted that this seed is not transferred
|
||||
* @param security Security level to be used for the private key / address. Can be 1, 2 or 3
|
||||
* @param index Optional (default null). Key index to start search from. If the index is provided, the generation of the address is not deterministic.
|
||||
* @param checksum Optional (default false). Adds 9-tryte address checksum
|
||||
* @param total Optional (default 1)Total number of addresses to generate
|
||||
* @param seed Tryte-encoded seed. It should be noted that this seed is not transferred
|
||||
* @param security Security level to be used for the private key / address. Can be 1, 2 or 3
|
||||
* @param index Optional (default null). Key index to start search from. If the index is provided, the generation of the address is not deterministic.
|
||||
* @param checksum Optional (default false). Adds 9-tryte address checksum
|
||||
* @param total Optional (default 1)Total number of addresses to generate
|
||||
* @param returnAll If true, it returns all addresses which were deterministically generated (until findTransactions returns null)
|
||||
* @return an array of strings with the specifed number of addresses
|
||||
*/
|
||||
@@ -88,14 +88,12 @@ public class IotaAPI extends IotaAPICore {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param seed
|
||||
* @param start
|
||||
* @param end
|
||||
* @param inclusionStates
|
||||
* @param security
|
||||
* @param seed
|
||||
* @param seed
|
||||
* @returns Bundle
|
||||
* @param seed tryte-encoded seed. It should be noted that this seed is not transferred
|
||||
* @param security security secuirty level of private key / seed
|
||||
* @param start start Starting key index
|
||||
* @param end end Ending key index
|
||||
* @param inclusionStates Optional (default false). If True, it gets the inclusion states of the transfers.
|
||||
* @return Bundle
|
||||
**/
|
||||
public GetTransferResponse getTransfers(String seed, int security, Integer start, Integer end, Boolean inclusionStates) throws ArgumentException, InvalidBundleException, InvalidSignatureException, NoNodeInfoException, NoInclusionStatesExcpection, InvalidSecurityLevelException {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
@@ -116,7 +114,7 @@ public class IotaAPI extends IotaAPICore {
|
||||
StopWatch sw = new StopWatch();
|
||||
|
||||
System.out.println("GetTransfer started");
|
||||
GetNewAddressResponse gnr = getNewAddress(seed, security, start, false, end == null ? end - start : end, true);
|
||||
GetNewAddressResponse gnr = getNewAddress(seed, security, start, false, end, true);
|
||||
if (gnr != null && gnr.getAddresses() != null) {
|
||||
System.out.println("GetTransfers after getNewAddresses " + sw.getElapsedTimeMili() + " ms");
|
||||
Bundle[] bundles = bundlesFromAddresses(gnr.getAddresses().toArray(new String[gnr.getAddresses().size()]), inclusionStates);
|
||||
@@ -126,6 +124,13 @@ public class IotaAPI extends IotaAPICore {
|
||||
return GetTransferResponse.create(new Bundle[]{}, stopWatch.getElapsedTimeMili());
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function to get the formatted bundles of a list of addresses
|
||||
*
|
||||
* @param addresses List of addresses
|
||||
* @param inclusionStates Optional (default false). If True, it gets the inclusion states of the transfers.
|
||||
* @return bundles Transaction objects
|
||||
**/
|
||||
public Bundle[] bundlesFromAddresses(String[] addresses, final Boolean inclusionStates) throws ArgumentException, InvalidBundleException, InvalidSignatureException, NoNodeInfoException, NoInclusionStatesExcpection {
|
||||
|
||||
List<Transaction> trxs = findTransactionObjects(addresses);
|
||||
@@ -160,7 +165,7 @@ public class IotaAPI extends IotaAPICore {
|
||||
// If inclusionStates, get the confirmation status
|
||||
// of the tail transactions, and thus the bundles
|
||||
GetInclusionStateResponse gisr = null;
|
||||
if (tailTxArray != null && tailTxArray.length != 0 && inclusionStates) {
|
||||
if (tailTxArray.length != 0 && inclusionStates) {
|
||||
try {
|
||||
gisr = getLatestInclusion(tailTxArray);
|
||||
} catch (IllegalAccessError ignored) {
|
||||
@@ -178,9 +183,12 @@ public class IotaAPI extends IotaAPICore {
|
||||
try {
|
||||
GetBundleResponse bundleResponse = getBundle(param);
|
||||
Bundle gbr = new Bundle(bundleResponse.getTransactions(), bundleResponse.getTransactions().size());
|
||||
if (gbr != null && gbr.getTransactions() != null) {
|
||||
if (gbr.getTransactions() != null) {
|
||||
if (inclusionStates) {
|
||||
boolean thisInclusion = finalInclusionStates.getStates()[Arrays.asList(tailTxArray).indexOf(param)];
|
||||
boolean thisInclusion = false;
|
||||
if (finalInclusionStates != null) {
|
||||
thisInclusion = finalInclusionStates.getStates()[Arrays.asList(tailTxArray).indexOf(param)];
|
||||
}
|
||||
for (Transaction t : gbr.getTransactions()) {
|
||||
t.setPersistence(thisInclusion);
|
||||
}
|
||||
@@ -203,7 +211,7 @@ public class IotaAPI extends IotaAPICore {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trytes
|
||||
* @param trytes the trytes
|
||||
* @return a StoreTransactionsResponse
|
||||
*/
|
||||
public StoreTransactionsResponse broadcastAndStore(final String... trytes) throws BroadcastAndStoreException {
|
||||
@@ -220,10 +228,10 @@ public class IotaAPI extends IotaAPICore {
|
||||
/**
|
||||
* Facade method: Gets transactions to approve, attaches to Tangle, broadcasts and stores
|
||||
*
|
||||
* @param {array} trytes
|
||||
* @param {int} depth
|
||||
* @param {int} minWeightMagnitude
|
||||
* @return
|
||||
* @param trytes the trytes
|
||||
* @param depth the depth
|
||||
* @param minWeightMagnitude the minimum weight magnitude
|
||||
* @return Transactions objects
|
||||
*/
|
||||
public List<Transaction> sendTrytes(final String[] trytes, final int depth, final int minWeightMagnitude) throws InvalidTrytesException {
|
||||
final GetTransactionsToApproveResponse txs = getTransactionsToApprove(depth);
|
||||
@@ -249,11 +257,9 @@ public class IotaAPI extends IotaAPICore {
|
||||
* Wrapper function for getTrytes and transactionObjects
|
||||
* gets the trytes and transaction object from a list of transaction hashes
|
||||
*
|
||||
* @param {array} hashes
|
||||
* @return
|
||||
* @method getTransactionsObjects
|
||||
* @returns {function} callback
|
||||
* @returns {object} success
|
||||
* @param hashes the hashes
|
||||
* @return Transaction objects
|
||||
**/
|
||||
public List<Transaction> getTransactionsObjects(String[] hashes) {
|
||||
|
||||
@@ -276,10 +282,9 @@ public class IotaAPI extends IotaAPICore {
|
||||
* Returns the transactionObject of a transaction hash. The input can be a valid
|
||||
* findTransactions input
|
||||
*
|
||||
* @param {object} input
|
||||
* @method getTransactionsObjects
|
||||
* @returns {function} callback
|
||||
* @returns {object} success
|
||||
* @param input the inputs
|
||||
* @return Transactions
|
||||
**/
|
||||
public List<Transaction> findTransactionObjects(String[] input) {
|
||||
FindTransactionResponse ftr = findTransactions(input, null, null, null);
|
||||
@@ -293,11 +298,9 @@ public class IotaAPI extends IotaAPICore {
|
||||
* Wrapper function for findTransactions, getTrytes and transactionObjects
|
||||
* Returns the transactionObject of a transaction hash. The input can be a valid
|
||||
* findTransactions input
|
||||
*
|
||||
* @param {object} input
|
||||
* @method getTransactionsObjects
|
||||
* @returns {function} callback
|
||||
* @returns {object} success
|
||||
* @param input the inputs
|
||||
* @return Transactions
|
||||
**/
|
||||
public List<Transaction> findTransactionObjectsByBundle(String[] input) {
|
||||
FindTransactionResponse ftr = findTransactions(null, null, null, input);
|
||||
@@ -311,13 +314,12 @@ public class IotaAPI extends IotaAPICore {
|
||||
/**
|
||||
* Prepares transfer by generating bundle, finding and signing inputs
|
||||
*
|
||||
* @param seed
|
||||
* @param security
|
||||
* @param transfers
|
||||
* @param remainder
|
||||
* @param inputs
|
||||
* @param security
|
||||
* @returns {array} trytes Returns bundle trytes
|
||||
* @param seed 81-tryte encoded address of recipient
|
||||
* @param security security secuirty level of private key / seed
|
||||
* @param transfers Array of transfer objects
|
||||
* @param remainder Optional (default null). if defined, this address will be used for sending the remainder value (of the inputs) to.
|
||||
* @param inputs the inputs
|
||||
* @return trytes Returns bundle trytes
|
||||
**/
|
||||
public List<String> prepareTransfers(String seed, int security, final List<Transfer> transfers, String remainder, List<Input> inputs) throws NotEnoughBalanceException, InvalidSecurityLevelException {
|
||||
|
||||
@@ -471,10 +473,10 @@ public class IotaAPI extends IotaAPICore {
|
||||
/**
|
||||
* Gets the inputs of a seed
|
||||
*
|
||||
* @param seed
|
||||
* @param security security secuirty level of private key / seed
|
||||
* @param start start Starting key index
|
||||
* @param end end Ending key index
|
||||
* @param seed tryte-encoded seed. It should be noted that this seed is not transferred
|
||||
* @param security security secuirty level of private key / seed
|
||||
* @param start start Starting key index
|
||||
* @param end end Ending key index
|
||||
* @param threshold threshold Min balance required
|
||||
**/
|
||||
public GetBalancesAndFormatResponse getInputs(String seed, int security, int start, int end, long threshold) throws InvalidSecurityLevelException {
|
||||
@@ -526,8 +528,18 @@ public class IotaAPI extends IotaAPICore {
|
||||
}
|
||||
}
|
||||
|
||||
// Calls getBalances and formats the output
|
||||
// returns the final inputsObject then
|
||||
|
||||
/**
|
||||
* Gets the balances and formats the output
|
||||
*
|
||||
* @param addresses the addresses
|
||||
* @param threshold threshold Min balance required
|
||||
* @param start start Starting key index
|
||||
* @param end end Ending key index
|
||||
* @param stopWatch the stopwatch
|
||||
* @param security security secuirty level of private key / seed
|
||||
* @return Inputs object
|
||||
**/
|
||||
public GetBalancesAndFormatResponse getBalanceAndFormat(final List<String> addresses, long threshold, int start, int end, StopWatch stopWatch, int security) throws InvalidSecurityLevelException {
|
||||
|
||||
if (security < 1 || security > 3) {
|
||||
@@ -572,10 +584,8 @@ public class IotaAPI extends IotaAPICore {
|
||||
/**
|
||||
* Gets the associated bundle transactions of a single transaction
|
||||
* Does validation of signatures, total sum as well as bundle order
|
||||
*
|
||||
* @param {string} transaction Hash of a tail transaction
|
||||
* @method getBundle
|
||||
* @returns {list} bundle Transaction objects
|
||||
* @param transaction the transaction encoded in trytes
|
||||
* @return an array of bundle, if there are multiple arrays it means that there are conflicting bundles.
|
||||
**/
|
||||
public GetBundleResponse getBundle(String transaction) throws ArgumentException, InvalidBundleException, InvalidSignatureException {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
@@ -655,12 +665,10 @@ public class IotaAPI extends IotaAPICore {
|
||||
/**
|
||||
* Replays a transfer by doing Proof of Work again
|
||||
*
|
||||
* @param {string} tail
|
||||
* @param {int} depth
|
||||
* @param {int} minWeightMagnitude
|
||||
* @param {function} callback
|
||||
* @method replayBundle
|
||||
* @returns {object} analyzed Transaction objects
|
||||
* @param transaction the transaction
|
||||
* @param depth the depth
|
||||
* @param minWeightMagnitude the minimum weight magnitude
|
||||
* @return analyzed Transaction objects
|
||||
**/
|
||||
public ReplayBundleResponse replayBundle(String transaction, int depth, int minWeightMagnitude) throws InvalidBundleException, ArgumentException, InvalidSignatureException, InvalidTrytesException {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
@@ -692,10 +700,8 @@ public class IotaAPI extends IotaAPICore {
|
||||
/**
|
||||
* Wrapper function for getNodeInfo and getInclusionStates
|
||||
*
|
||||
* @param {array} hashes
|
||||
* @method getLatestInclusion
|
||||
* @returns {function} callback
|
||||
* @returns {array} state
|
||||
* @param hashes the hashes
|
||||
* @return inclusion state
|
||||
**/
|
||||
public GetInclusionStateResponse getLatestInclusion(String[] hashes) throws NoNodeInfoException {
|
||||
GetNodeInfoResponse getNodeInfoResponse = getNodeInfo();
|
||||
@@ -706,6 +712,19 @@ public class IotaAPI extends IotaAPICore {
|
||||
return getInclusionStates(hashes, latestMilestone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function that basically does prepareTransfers, as well as attachToTangle and finally, it broadcasts and stores the transactions locally.
|
||||
*
|
||||
* @param seed tryte-encoded seed
|
||||
* @param security security secuirty level of private key / seed
|
||||
* @param depth the depth
|
||||
* @param minWeightMagnitude the minimum weight magnitude
|
||||
* @param transfers array of transfer objects
|
||||
* @param inputs Option (default null). List of inputs used for funding the transfer
|
||||
* @param address if defined, this address will be used for sending the remainder value (of the inputs) to
|
||||
* @return array of Transaction objects
|
||||
**/
|
||||
|
||||
public SendTransferResponse sendTransfer(String seed, int security, int depth, int minWeightMagnitude, final List<Transfer> transfers, Input[] inputs, String address) throws NotEnoughBalanceException, InvalidSecurityLevelException, InvalidTrytesException {
|
||||
|
||||
if (security < 1 || security > 3) {
|
||||
@@ -734,11 +753,10 @@ public class IotaAPI extends IotaAPICore {
|
||||
* the bundle hash of the transaction is no longer the same. In case the input
|
||||
* transaction hash is not a tail, we return an error.
|
||||
*
|
||||
* @param {string} trunkTx Hash of a trunk or a tail transaction of a bundle
|
||||
* @param {string} bundleHash
|
||||
* @param {array} bundle List of bundles to be populated
|
||||
* @method traverseBundle
|
||||
* @returns {array} bundle Transaction objects
|
||||
* @param trunkTx Hash of a trunk or a tail transaction of a bundle
|
||||
* @param bundleHash the bundle hashes
|
||||
* @param bundle List of bundles to be populated
|
||||
* @return bundle Transaction objects
|
||||
**/
|
||||
public Bundle traverseBundle(String trunkTx, String bundleHash, Bundle bundle) throws ArgumentException {
|
||||
GetTrytesResponse gtr = getTrytes(trunkTx);
|
||||
@@ -750,7 +768,7 @@ public class IotaAPI extends IotaAPICore {
|
||||
}
|
||||
|
||||
Transaction trx = new Transaction(gtr.getTrytes()[0], customCurl);
|
||||
if (trx == null || trx.getBundle() == null) {
|
||||
if (trx.getBundle() == null) {
|
||||
throw new ArgumentException("Invalid trytes, could not create object");
|
||||
}
|
||||
// If first transaction to search is not a tail, return error
|
||||
@@ -786,13 +804,10 @@ public class IotaAPI extends IotaAPICore {
|
||||
* Prepares transfer by generating the bundle with the corresponding cosigner transactions
|
||||
* Does not contain signatures
|
||||
*
|
||||
* @param {object} transfers
|
||||
* @param {function} callback
|
||||
* @param securitySum sum of security levels used by all co-signers
|
||||
* @param inputAddress array of input addresses as well as the securitySum
|
||||
* @param remainderAddress Has to be generated by the cosigners before initiating the transfer, can be null if fully spent
|
||||
* @method initiateTransfer
|
||||
* @returns {array} Array of transaction objects
|
||||
* @return bundle of transaction objects
|
||||
**/
|
||||
private GetTransferResponse initiateTransfer(int securitySum, final List<String> inputAddress, String remainderAddress, final List<Transfer> transfers) {
|
||||
StopWatch sw = new StopWatch();
|
||||
@@ -960,7 +975,7 @@ public class IotaAPI extends IotaAPICore {
|
||||
}
|
||||
|
||||
Transaction trx = new Transaction(gtr.getTrytes()[0], customCurl);
|
||||
if (trx == null || trx.getBundle() == null) {
|
||||
if (trx.getBundle() == null) {
|
||||
throw new ArgumentException("Invalid trytes, could not create object");
|
||||
}
|
||||
if (Integer.parseInt(trx.getCurrentIndex()) == 0) return trx.getHash();
|
||||
|
||||
@@ -2,7 +2,7 @@ package jota;
|
||||
|
||||
/**
|
||||
* IOTA's node command list
|
||||
* <p>
|
||||
*
|
||||
* 'params' is not currently used.
|
||||
*/
|
||||
public enum IotaAPICommands {
|
||||
|
||||
@@ -19,7 +19,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Returns information about your node.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "getNodeInfo"}'
|
||||
*
|
||||
@@ -31,7 +31,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Get the list of latest tips (unconfirmed transactions).
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "getNeighbors"}'
|
||||
*/
|
||||
@@ -41,7 +41,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Add a list of neighbors to your node.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "addNeighbors", "uris": ["udp://8.8.8.8:14265", "udp://8.8.8.5:14265"]}'
|
||||
*/
|
||||
@@ -51,7 +51,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Removes a list of neighbors to your node.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "removeNeighbors", "uris": ["udp://8.8.8.8:14265", "udp://8.8.8.5:14265"]}'
|
||||
*/
|
||||
@@ -61,7 +61,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Get the list of latest tips (unconfirmed transactions).
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "getTips"}'
|
||||
*/
|
||||
@@ -71,7 +71,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Find the transactions which match the specified input and return
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 \ -X POST \ -H 'Content-Type: application/json' \
|
||||
* -d '{"command": "findTransactions", "addresses": ["RVORZ9SIIP9RCYMREUIXXVPQIPHVCNPQ9HZWYKFWYWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAZETAIRPTM"]}'
|
||||
*/
|
||||
@@ -82,7 +82,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Get the inclusion states of a set of transactions. This is for determining if a transaction was accepted and confirmed by the network or not. You can search for multiple tips (and thus, milestones) to get past inclusion states of transactions.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "getInclusionStates", "transactions"Q9HZWYKFWYWZRE9JQKG9REPKIASHUUECPSQO9JT9XNMVKWYGVAZETAIRPTM"], "tips" : []}'
|
||||
*/
|
||||
@@ -92,7 +92,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Returns the raw trytes data of a transaction.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "getTrytes", "hashes": ["OAATQS9VQLSXCLDJVJJVYUGONXAXOFMJOZNSYWRZSWECMXAQQURHQBJNLD9IOFEPGZEPEMPXCIVRX9999"]}'
|
||||
*/
|
||||
@@ -102,7 +102,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Tip selection which returns trunkTransaction and branchTransaction. The input value is the latest coordinator milestone, as provided through the getNodeInfo API call.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "getTransactionsToApprove", "depth": 27}'
|
||||
*/
|
||||
@@ -112,7 +112,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* It returns the confirmed balance which a list of addresses have at the latest confirmed milestone.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "getBalances", "addresses": ["HBBYKAKTILIPVUKFOTSLHGENPTXYBNKXZFQFR9VQFWNBMTQNRVOUKPVPRNBSZVVILMAFBKOTBLGLWLOHQ"], "threshold": 100}'
|
||||
*/
|
||||
@@ -122,7 +122,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Attaches the specified transactions (trytes) to the Tangle by doing Proof of Work.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "attachToTangle", "trunkTransaction": "JVMTDGDPDFYHMZPMWEKKANBQSLSDTIIHAYQUMZOKHXXXGJHJDQPOMDOMNRDKYCZRUFZROZDADTHZC9999", "branchTransaction": "P9KFSJVGSPLXAEBJSHWFZLGP9GGJTIO9YITDEHATDTGAFLPLBZ9FOFWWTKMAZXZHFGQHUOXLXUALY9999", "minWeightMagnitude": 18, "trytes": ["TRYTVALUEHERE"]}'
|
||||
*/
|
||||
@@ -132,7 +132,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Interrupts and completely aborts the attachToTangle process.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "interruptAttachingToTangle" }
|
||||
*/
|
||||
@@ -142,7 +142,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Broadcast a list of transactions to all neighbors. The input trytes for this call are provided by attachToTangle.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "broadcastTransactions", "trytes": ["BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"]}
|
||||
*/
|
||||
@@ -152,7 +152,7 @@ public interface IotaAPIService {
|
||||
|
||||
/**
|
||||
* Store transactions into the local storage. The trytes to be used for this call are returned by attachToTangle.
|
||||
* <p>
|
||||
*
|
||||
* curl http://localhost:14265 -X POST -H 'Content-Type: application/json'
|
||||
* -d '{"command": "storeTransactions", "trytes": ["BYSWEAUTWXHXZ9YBZISEK9LUHWGMHXCGEVNZHRLUWQFCUSDXZHOFHWHL9MQPVJXXZLIXPXPXF9KYEREFSKCPKYIIKPZVLHUTDFQKKVVBBN9ATTLPCNPJDWDEVIYYLGPZGCWXOBDXMLJC9VO9QXTTBLAXTTBFUAROYEGQIVB9MJWJKXJMCUPTWAUGFZBTZCSJVRBGMYXTVBDDS9MYUJCPZ9YDWWQNIPUAIJXXSNLKUBSCOIJPCLEFPOXFJREXQCUVUMKSDOVQGGHRNILCO9GNCLWFM9APMNMWYASHXQAYBEXF9QRIHIBHYEJOYHRQJAOKAQ9AJJFQ9WEIWIJOTZATIBOXQLBMIJU9PCGBLVDDVFP9CFFSXTDUXMEGOOFXWRTLFGV9XXMYWEMGQEEEDBTIJ9OJOXFAPFQXCDAXOUDMLVYRMRLUDBETOLRJQAEDDLNVIRQJUBZBO9CCFDHIX9MSQCWYAXJVWHCUPTRSXJDESISQPRKZAFKFRULCGVRSBLVFOPEYLEE99JD9SEBALQINPDAZHFAB9RNBH9AZWIJOTLBZVIEJIAYGMC9AZGNFWGRSWAXTYSXVROVNKCOQQIWGPNQZKHUNODGYADPYLZZZUQRTJRTODOUKAOITNOMWNGHJBBA99QUMBHRENGBHTH9KHUAOXBVIVDVYYZMSEYSJWIOGGXZVRGN999EEGQMCOYVJQRIRROMPCQBLDYIGQO9AMORPYFSSUGACOJXGAQSPDY9YWRRPESNXXBDQ9OZOXVIOMLGTSWAMKMTDRSPGJKGBXQIVNRJRFRYEZ9VJDLHIKPSKMYC9YEGHFDS9SGVDHRIXBEMLFIINOHVPXIFAZCJKBHVMQZEVWCOSNWQRDYWVAIBLSCBGESJUIBWZECPUCAYAWMTQKRMCHONIPKJYYTEGZCJYCT9ABRWTJLRQXKMWY9GWZMHYZNWPXULNZAPVQLPMYQZCYNEPOCGOHBJUZLZDPIXVHLDMQYJUUBEDXXPXFLNRGIPWBRNQQZJSGSJTTYHIGGFAWJVXWL9THTPWOOHTNQWCNYOYZXALHAZXVMIZE9WMQUDCHDJMIBWKTYH9AC9AFOT9DPCADCV9ZWUTE9QNOMSZPTZDJLJZCJGHXUNBJFUBJWQUEZDMHXGBPTNSPZBR9TGSKVOHMOQSWPGFLSWNESFKSAZY9HHERAXALZCABFYPOVLAHMIHVDBGKUMDXC9WHHTIRYHZVWNXSVQUWCR9M9RAGMFEZZKZ9XEOQGOSLFQCHHOKLDSA9QCMDGCGMRYJZLBVIFOLBIJPROKMHOYTBTJIWUZWJMCTKCJKKTR9LCVYPVJI9AHGI9JOWMIWZAGMLDFJA9WU9QAMEFGABIBEZNNAL9OXSBFLOEHKDGHWFQSHMPLYFCNXAAZYJLMQDEYRGL9QKCEUEJ9LLVUOINVSZZQHCIKPAGMT9CAYIIMTTBCPKWTYHOJIIY9GYNPAJNUJ9BKYYXSV9JSPEXYMCFAIKTGNRSQGUNIYZCRT9FOWENSZQPD9ALUPYYAVICHVYELYFPUYDTWUSWNIYFXPX9MICCCOOZIWRNJIDALWGWRATGLJXNAYTNIZWQ9YTVDBOFZRKO9CFWRPAQQRXTPACOWCPRLYRYSJARRKSQPR9TCFXDVIXLP9XVL99ERRDSOHBFJDJQQGGGCZNDQ9NYCTQJWVZIAELCRBJJFDMCNZU9FIZRPGNURTXOCDSQGXTQHKHUECGWFUUYS9J9NYQ9U9P9UUP9YMZHWWWCIASCFLCMSKTELZWUGCDE9YOKVOVKTAYPHDF9ZCCQAYPJIJNGSHUIHHCOSSOOBUDOKE9CJZGYSSGNCQJVBEFTZFJ9SQUHOASKRRGBSHWKBCBWBTJHOGQ9WOMQFHWJVEG9NYX9KWBTCAIXNXHEBDIOFO9ALYMFGRICLCKKLG9FOBOX9PDWNQRGHBKHGKKRLWTBEQMCWQRLHAVYYZDIIPKVQTHYTWQMTOACXZOQCDTJTBAAUWXSGJF9PNQIJ9AJRUMUVCPWYVYVARKR9RKGOUHHNKNVGGPDDLGKPQNOYHNKAVVKCXWXOQPZNSLATUJT9AUWRMPPSWHSTTYDFAQDXOCYTZHOYYGAIM9CELMZ9AZPWB9MJXGHOKDNNSZVUDAGXTJJSSZCPZVPZBYNNTUQABSXQWZCHDQSLGK9UOHCFKBIBNETK999999999999999999999999999999999999999999999999999999999999999999999999999999999NOXDXXKUDWLOFJLIPQIBRBMGDYCPGDNLQOLQS99EQYKBIU9VHCJVIPFUYCQDNY9APGEVYLCENJIOBLWNB999999999XKBRHUD99C99999999NKZKEKWLDKMJCI9N9XQOLWEPAYWSH9999999999999999999999999KDDTGZLIPBNZKMLTOLOXQVNGLASESDQVPTXALEKRMIOHQLUHD9ELQDBQETS9QFGTYOYWLNTSKKMVJAUXSIROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999IROUICDOXKSYZTDPEDKOQENTJOWJONDEWROCEJIEWFWLUAACVSJFTMCHHXJBJRKAAPUDXXVXFWP9X9999"]}'
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@ package jota.pow;
|
||||
|
||||
/**
|
||||
* (c) 2016 Come-from-Beyond
|
||||
* <p>
|
||||
*
|
||||
* JCurl belongs to the sponge function family.
|
||||
*/
|
||||
public class JCurl implements ICurl {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package jota.utils;
|
||||
|
||||
import jota.pow.JCurl;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Created by pinpong on 02.12.16.
|
||||
*/
|
||||
public class Checksum {
|
||||
|
||||
/**
|
||||
* adds the checksum to the specified address
|
||||
*
|
||||
* @param address address without checksum
|
||||
* @return the address with the appended checksum
|
||||
**/
|
||||
public static String addChecksum(String address) {
|
||||
InputValidator.checkAddress(address);
|
||||
String addressWithChecksum = address;
|
||||
@@ -15,32 +20,70 @@ public class Checksum {
|
||||
return addressWithChecksum;
|
||||
}
|
||||
|
||||
public static String removeChecksum(String addressWithChecksum) {
|
||||
if (isAddressWithChecksum(addressWithChecksum)) {
|
||||
return getAddress(addressWithChecksum);
|
||||
/**
|
||||
* remove the checksum to the specified address
|
||||
*
|
||||
* @param address address with checksum
|
||||
* @return the address without checksum
|
||||
**/
|
||||
public static String removeChecksum(String address) {
|
||||
if (isAddressWithChecksum(address)) {
|
||||
return getAddress(address);
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
throw new RuntimeException("Invalid address: " + address);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove checksum length
|
||||
*
|
||||
* @param addressWithChecksum address with checksum
|
||||
* @return address without checksum
|
||||
**/
|
||||
private static String getAddress(String addressWithChecksum) {
|
||||
return addressWithChecksum.substring(0, Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if checksum is valid
|
||||
*
|
||||
* @param addressWithChecksum address
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean isValidChecksum(String addressWithChecksum) {
|
||||
String addressWithoutChecksum = removeChecksum(addressWithChecksum);
|
||||
String addressWithRecalculateChecksum = addressWithChecksum += calculateChecksum(addressWithoutChecksum);
|
||||
return addressWithRecalculateChecksum.equals(addressWithChecksum);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if specified address is a address with checksum
|
||||
*
|
||||
* @param address address
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean isAddressWithChecksum(String address) {
|
||||
return InputValidator.checkAddress(address) && address.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if specified address is a address
|
||||
*
|
||||
* @param address address
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean isAddressWithoutChecksum(String address) {
|
||||
return InputValidator.checkAddress(address) && address.length() == Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM;
|
||||
}
|
||||
|
||||
public static String calculateChecksum(String address) {
|
||||
/**
|
||||
* calculate the checksum to the specified address
|
||||
*
|
||||
* @param address address
|
||||
* @return the checksum
|
||||
**/
|
||||
private static String calculateChecksum(String address) {
|
||||
JCurl curl = new JCurl();
|
||||
curl.reset();
|
||||
curl.setState(Converter.copyTrits(address, curl.getState()));
|
||||
|
||||
@@ -11,11 +11,23 @@ import java.util.List;
|
||||
*/
|
||||
public class InputValidator {
|
||||
|
||||
/**
|
||||
* validates an address
|
||||
*
|
||||
* @param address address to validate
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean isAddress(String address) {
|
||||
return (address.length() == Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM ||
|
||||
address.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM) && isTrytes(address, address.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* checks whether the specified address is an address
|
||||
*
|
||||
* @param address address to validate
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean checkAddress(String address) {
|
||||
if (!isAddress(address)) {
|
||||
throw new RuntimeException("Invalid address: " + address);
|
||||
@@ -23,18 +35,44 @@ public class InputValidator {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if input is correct trytes consisting of A-Z9 optionally validates length
|
||||
*
|
||||
* @param trytes the trytes
|
||||
* @param length the length
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean isTrytes(final String trytes, final int length) {
|
||||
return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$");
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if input is correct trytes consisting of 9 optionally validates length
|
||||
*
|
||||
* @param trytes the trytes
|
||||
* @param length the length
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean isNinesTrytes(final String trytes, final int length) {
|
||||
return trytes.matches("^[9]{" + (length == 0 ? "0," : length) + "}$");
|
||||
}
|
||||
|
||||
/**
|
||||
* determines whether the specified string represents a signed integer
|
||||
*
|
||||
* @param value the value
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean isValue(final String value) {
|
||||
return NumberUtils.isNumber(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* determines whether the specified string represents a signed integer
|
||||
*
|
||||
* @param value the value
|
||||
* @return boolean
|
||||
**/
|
||||
public static boolean isArrayOfTrytes(String[] trytes){
|
||||
for (String tryte : trytes) {
|
||||
// Check if correct 2673 trytes
|
||||
@@ -96,6 +134,12 @@ public class InputValidator {
|
||||
return isTrytes(transfer.getTag(), 27);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate the seed
|
||||
*
|
||||
* @param seed the seed
|
||||
* @return validated seed
|
||||
**/
|
||||
public static String validateSeed(String seed) {
|
||||
if (seed.length() > 81)
|
||||
return null;
|
||||
|
||||
@@ -7,6 +7,14 @@ import java.text.DecimalFormat;
|
||||
*/
|
||||
public class IotaUnitConverter {
|
||||
|
||||
/**
|
||||
* convert the iota amount
|
||||
*
|
||||
* @param amount the amount
|
||||
* @param fromUnit the source unit e.g. the unit of amount
|
||||
* @param toUnit the target unit
|
||||
* @return the specified amount in the target unit
|
||||
**/
|
||||
public static long convertUnits(long amount, IotaUnits fromUnit, IotaUnits toUnit) {
|
||||
long amountInSource = (long) (amount * Math.pow(10, fromUnit.getValue()));
|
||||
return convertUnits(amountInSource, toUnit);
|
||||
@@ -16,6 +24,13 @@ public class IotaUnitConverter {
|
||||
return (long) (amount / Math.pow(10, toUnit.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the iota amount to text
|
||||
*
|
||||
* @param amount the amount
|
||||
* @param extended extended length
|
||||
* @return the specified amount in the target unit
|
||||
**/
|
||||
public static String convertRawIotaAmountToDisplayText(long amount, boolean extended) {
|
||||
IotaUnits unit = findOptimalIotaUnitToDisplay(amount);
|
||||
double amountInDisplayUnit = convertAmountTo(amount, unit);
|
||||
@@ -44,6 +59,12 @@ public class IotaUnitConverter {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* finds the optimal unit to display the specified amount in
|
||||
*
|
||||
* @param amount the amount
|
||||
* @return the optimal IotaUnit
|
||||
**/
|
||||
public static IotaUnits findOptimalIotaUnitToDisplay(long amount) {
|
||||
int length = String.valueOf(amount).length();
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@ import java.security.SecureRandom;
|
||||
*/
|
||||
public class SeedRandomGenerator {
|
||||
|
||||
|
||||
/**
|
||||
* generate a new seed
|
||||
*
|
||||
* @return random generated seed
|
||||
**/
|
||||
public static String generateNewSeed() {
|
||||
char[] chars = Constants.TRYTE_ALPHABET.toCharArray();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
@@ -8,20 +8,20 @@ public class TrytesConverter {
|
||||
/**
|
||||
* Conversion of ascii encoded bytes to trytes.
|
||||
* Input is a string (can be stringified JSON object), return value is Trytes
|
||||
* <p>
|
||||
*
|
||||
* How the conversion works:
|
||||
* 2 Trytes === 1 Byte
|
||||
* There are a total of 27 different tryte values: 9ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
* <p>
|
||||
*
|
||||
* 1. We get the decimal value of an individual ASCII character
|
||||
* 2. From the decimal value, we then derive the two tryte values by basically calculating the tryte equivalent (e.g. 100 === 19 + 3 * 27)
|
||||
* a. The first tryte value is the decimal value modulo 27 (27 trytes)
|
||||
* b. The second value is the remainder (decimal value - first value), divided by 27
|
||||
* 3. The two values returned from Step 2. are then input as indices into the available values list ('9ABCDEFGHIJKLMNOPQRSTUVWXYZ') to get the correct tryte value
|
||||
* <p>
|
||||
* <p>
|
||||
*
|
||||
*
|
||||
* EXAMPLE
|
||||
* <p>
|
||||
*
|
||||
* Lets say we want to convert the ASCII character "Z".
|
||||
* 1. 'Z' has a decimal value of 90.
|
||||
* 2. 90 can be represented as 9 + 3 * 27. To make it simpler:
|
||||
@@ -31,7 +31,7 @@ public class TrytesConverter {
|
||||
* a. The first tryte value is '9ABCDEFGHIJKLMNOPQRSTUVWXYZ'[9] === "I"
|
||||
* b. The second tryte value is '9ABCDEFGHIJKLMNOPQRSTUVWXYZ'[3] === "C"
|
||||
* Our tryte pair is "IC"
|
||||
* <p>
|
||||
*
|
||||
*
|
||||
* @param inputString
|
||||
* @return The ASCII char "Z" is represented as "IC" in trytes.
|
||||
|
||||
@@ -77,7 +77,7 @@ public class IotaCoreApiTest {
|
||||
|
||||
@Test
|
||||
public void shouldFindTransactionsByApprovees() {
|
||||
FindTransactionResponse trans = proxy.findTransactionsByApprovees(new String[]{TEST_HASH});
|
||||
FindTransactionResponse trans = proxy.findTransactionsByApprovees(TEST_HASH);
|
||||
assertThat(trans.getHashes(), IsNull.notNullValue());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user