diff --git a/content/_code-samples/issue-a-token/java/IssueToken.java b/content/_code-samples/issue-a-token/java/IssueToken.java new file mode 100644 index 0000000000..f12464548d --- /dev/null +++ b/content/_code-samples/issue-a-token/java/IssueToken.java @@ -0,0 +1,280 @@ +// Stand-alone code sample for the "issue a token" tutorial: +// https://xrpl.org/issue-a-fungible-token.html +// License: https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.primitives.UnsignedInteger; +import com.google.common.primitives.UnsignedLong; +import okhttp3.HttpUrl; +import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; +import org.xrpl.xrpl4j.client.XrplClient; +import org.xrpl.xrpl4j.client.faucet.FaucetClient; +import org.xrpl.xrpl4j.client.faucet.FundAccountRequest; +import org.xrpl.xrpl4j.crypto.KeyMetadata; +import org.xrpl.xrpl4j.crypto.PrivateKey; +import org.xrpl.xrpl4j.crypto.signing.SignatureService; +import org.xrpl.xrpl4j.crypto.signing.SignedTransaction; +import org.xrpl.xrpl4j.crypto.signing.SingleKeySignatureService; +import org.xrpl.xrpl4j.model.client.accounts.AccountInfoRequestParams; +import org.xrpl.xrpl4j.model.client.accounts.AccountLinesRequestParams; +import org.xrpl.xrpl4j.model.client.accounts.TrustLine; +import org.xrpl.xrpl4j.model.client.common.LedgerIndex; +import org.xrpl.xrpl4j.model.client.fees.FeeResult; +import org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams; +import org.xrpl.xrpl4j.model.client.transactions.TransactionRequestParams; +import org.xrpl.xrpl4j.model.client.transactions.TransactionResult; +import org.xrpl.xrpl4j.model.immutables.FluentCompareTo; +import org.xrpl.xrpl4j.model.transactions.AccountSet; +import org.xrpl.xrpl4j.model.transactions.ImmutableTrustSet; +import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.Payment; +import org.xrpl.xrpl4j.model.transactions.TrustSet; +import org.xrpl.xrpl4j.wallet.DefaultWalletFactory; +import org.xrpl.xrpl4j.wallet.Wallet; +import org.xrpl.xrpl4j.wallet.WalletFactory; + +import java.util.List; + +public class IssueToken { + + public static void main(String[] args) + throws InterruptedException, JsonRpcClientErrorException, JsonProcessingException { + // ----------------------------------------------------------- + // Construct a network client + // ----------------------------------------------------------- + HttpUrl rippledUrl = HttpUrl + .get("https://s.altnet.rippletest.net:51234/"); + XrplClient xrplClient = new XrplClient(rippledUrl); + + // ----------------------------------------------------------- + // Create a cold and hot Wallet using a WalletFactory + // ----------------------------------------------------------- + WalletFactory walletFactory = DefaultWalletFactory.getInstance(); + Wallet coldWallet = walletFactory.randomWallet(true).wallet(); + Wallet hotWallet = walletFactory.randomWallet(true).wallet(); + + // ----------------------------------------------------------- + // Fund the account using the testnet Faucet + // ----------------------------------------------------------- + FaucetClient faucetClient = FaucetClient + .construct(HttpUrl.get("https://faucet.altnet.rippletest.net")); + faucetClient.fundAccount(FundAccountRequest.of(coldWallet.classicAddress())); + faucetClient.fundAccount(FundAccountRequest.of(hotWallet.classicAddress())); + + // If you go too soon, the funding transaction might slip back a ledger and + // then your starting Sequence number will be off. This is mostly relevant + // when you want to use a Testnet account right after getting a reply from + // the faucet. + boolean accountsFunded = false; + while (!accountsFunded) { + try { + xrplClient.accountInfo( + AccountInfoRequestParams.builder() + .ledgerIndex(LedgerIndex.VALIDATED) + .account(coldWallet.classicAddress()) + .build() + ); + + xrplClient.accountInfo( + AccountInfoRequestParams.builder() + .ledgerIndex(LedgerIndex.VALIDATED) + .account(hotWallet.classicAddress()) + .build() + ); + + accountsFunded = true; + } catch (JsonRpcClientErrorException e) { + if (!e.getMessage().equals("Account not found.")) { + throw e; + } + Thread.sleep(1000); + } + } + + // ----------------------------------------------------------- + // Get the current network fee + // ----------------------------------------------------------- + FeeResult feeResult = xrplClient.fee(); + + // ----------------------------------------------------------- + // Configure cold wallet settings + // ----------------------------------------------------------- + UnsignedInteger coldWalletSequence = xrplClient.accountInfo( + AccountInfoRequestParams.builder() + .ledgerIndex(LedgerIndex.CURRENT) + .account(coldWallet.classicAddress()) + .build() + ).accountData().sequence(); + + AccountSet setDefaultRipple = AccountSet.builder() + .account(coldWallet.classicAddress()) + .fee(feeResult.drops().minimumFee()) + .sequence(coldWalletSequence) + .signingPublicKey(coldWallet.publicKey()) + .setFlag(AccountSet.AccountSetFlag.DEFAULT_RIPPLE) + .lastLedgerSequence(computeLastLedgerSequence(xrplClient)) + .build(); + + PrivateKey coldWalletPrivateKey = PrivateKey.fromBase16EncodedPrivateKey( + coldWallet.privateKey().get() + ); + SignatureService coldWalletSignatureService = new SingleKeySignatureService(coldWalletPrivateKey); + + SignedTransaction signedSetDefaultRipple = coldWalletSignatureService.sign( + KeyMetadata.EMPTY, + setDefaultRipple + ); + + submitAndWaitForValidation(signedSetDefaultRipple, xrplClient); + + // ----------------------------------------------------------- + // Configure hot wallet settings + // ----------------------------------------------------------- + UnsignedInteger hotWalletSequence = xrplClient.accountInfo( + AccountInfoRequestParams.builder() + .ledgerIndex(LedgerIndex.CURRENT) + .account(hotWallet.classicAddress()) + .build() + ).accountData().sequence(); + + AccountSet setRequireAuth = AccountSet.builder() + .account(hotWallet.classicAddress()) + .fee(feeResult.drops().minimumFee()) + .sequence(hotWalletSequence) + .signingPublicKey(hotWallet.publicKey()) + .setFlag(AccountSet.AccountSetFlag.REQUIRE_AUTH) + .lastLedgerSequence(computeLastLedgerSequence(xrplClient)) + .build(); + + PrivateKey hotWalletPrivateKey = PrivateKey.fromBase16EncodedPrivateKey( + hotWallet.privateKey().get() + ); + SignatureService hotWalletSignatureService = new SingleKeySignatureService(hotWalletPrivateKey); + + SignedTransaction signedSetRequireAuth = hotWalletSignatureService.sign( + KeyMetadata.EMPTY, + setRequireAuth + ); + + submitAndWaitForValidation(signedSetRequireAuth, xrplClient); + + // ----------------------------------------------------------- + // Create TrustLine + // ----------------------------------------------------------- + String currencyCode = "FOO"; + ImmutableTrustSet trustSet = TrustSet.builder() + .account(hotWallet.classicAddress()) + .fee(feeResult.drops().openLedgerFee()) + .sequence(hotWalletSequence.plus(UnsignedInteger.ONE)) + .limitAmount(IssuedCurrencyAmount.builder() + .currency(currencyCode) + .issuer(coldWallet.classicAddress()) + .value("10000000000") + .build()) + .signingPublicKey(hotWallet.publicKey()) + .build(); + + SignedTransaction signedTrustSet = hotWalletSignatureService.sign( + KeyMetadata.EMPTY, + trustSet + ); + + submitAndWaitForValidation(signedTrustSet, xrplClient); + + // ----------------------------------------------------------- + // Send token + // ----------------------------------------------------------- + Payment payment = Payment.builder() + .account(coldWallet.classicAddress()) + .fee(feeResult.drops().openLedgerFee()) + .sequence(coldWalletSequence.plus(UnsignedInteger.ONE)) + .destination(hotWallet.classicAddress()) + .amount(IssuedCurrencyAmount.builder() + .issuer(coldWallet.classicAddress()) + .currency(currencyCode) + .value("3840") + .build()) + .signingPublicKey(coldWallet.publicKey()) + .build(); + + SignedTransaction signedPayment = coldWalletSignatureService.sign( + KeyMetadata.EMPTY, + payment + ); + + submitAndWaitForValidation(signedPayment, xrplClient); + + // ----------------------------------------------------------- + // Check balances + // ----------------------------------------------------------- + List lines = xrplClient.accountLines( + AccountLinesRequestParams.builder() + .account(hotWallet.classicAddress()) + .ledgerIndex(LedgerIndex.VALIDATED) + .build() + ).lines(); + System.out.println("Hot wallet TrustLines: " + lines); + } + + private static UnsignedInteger computeLastLedgerSequence(XrplClient xrplClient) + throws JsonRpcClientErrorException { + // Get the latest validated ledger index + LedgerIndex validatedLedger = xrplClient.ledger( + LedgerRequestParams.builder() + .ledgerIndex(LedgerIndex.VALIDATED) + .build() + ) + .ledgerIndex() + .orElseThrow(() -> new RuntimeException("LedgerIndex not available.")); + + // Workaround for https://github.com/XRPLF/xrpl4j/issues/84 + return UnsignedInteger.valueOf( + validatedLedger.plus(UnsignedLong.valueOf(4)).unsignedLongValue().intValue() + ); + } + + private static void submitAndWaitForValidation(SignedTransaction signedTransaction, XrplClient xrplClient) + throws InterruptedException, JsonRpcClientErrorException, JsonProcessingException { + + xrplClient.submit(signedTransaction); + + boolean transactionValidated = false; + boolean transactionExpired = false; + while (!transactionValidated && !transactionExpired) { + Thread.sleep(4 * 1000); + LedgerIndex latestValidatedLedgerIndex = xrplClient.ledger( + LedgerRequestParams.builder().ledgerIndex(LedgerIndex.VALIDATED).build() + ) + .ledgerIndex() + .orElseThrow(() -> + new RuntimeException("Ledger response did not contain a LedgerIndex.") + ); + + TransactionResult transactionResult = xrplClient.transaction( + TransactionRequestParams.of(signedTransaction.hash()), + Payment.class + ); + + if (transactionResult.validated()) { + System.out.println("Transaction was validated with result code " + + transactionResult.metadata().get().transactionResult()); + transactionValidated = true; + } else { + boolean lastLedgerSequenceHasPassed = FluentCompareTo. + is(latestValidatedLedgerIndex.unsignedLongValue()) + .greaterThan(UnsignedLong.valueOf( + signedTransaction.signedTransaction().lastLedgerSequence().get().intValue() + ) + ); + if (lastLedgerSequenceHasPassed) { + System.out.println("LastLedgerSequence has passed. Last tx response: " + + transactionResult); + transactionExpired = true; + } else { + System.out.println("Transaction not yet validated."); + } + } + } + } + +}