mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-04-29 15:37:48 +00:00
initial code sample and file structure
This commit is contained in:
220
_code-samples/credential/java/ManageCredentials.java
Normal file
220
_code-samples/credential/java/ManageCredentials.java
Normal file
@@ -0,0 +1,220 @@
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import okhttp3.HttpUrl;
|
||||
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.keys.KeyPair;
|
||||
import org.xrpl.xrpl4j.crypto.keys.PrivateKey;
|
||||
import org.xrpl.xrpl4j.crypto.keys.Seed;
|
||||
import org.xrpl.xrpl4j.crypto.signing.SignatureService;
|
||||
import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
|
||||
import org.xrpl.xrpl4j.crypto.signing.bc.BcSignatureService;
|
||||
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoRequestParams;
|
||||
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
|
||||
import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
|
||||
import org.xrpl.xrpl4j.model.client.fees.FeeResult;
|
||||
import org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams;
|
||||
import org.xrpl.xrpl4j.model.client.transactions.SubmitResult;
|
||||
import org.xrpl.xrpl4j.model.client.transactions.TransactionRequestParams;
|
||||
import org.xrpl.xrpl4j.model.client.transactions.TransactionResult;
|
||||
import org.xrpl.xrpl4j.model.transactions.Address;
|
||||
import org.xrpl.xrpl4j.model.transactions.CredentialAccept;
|
||||
import org.xrpl.xrpl4j.model.transactions.CredentialCreate;
|
||||
import org.xrpl.xrpl4j.model.transactions.CredentialDelete;
|
||||
import org.xrpl.xrpl4j.model.transactions.CredentialType;
|
||||
import org.xrpl.xrpl4j.model.transactions.Transaction;
|
||||
|
||||
/**
|
||||
* Demonstrates the full Credential lifecycle on the XRPL Testnet:
|
||||
* fund an issuer and a subject account, issue a credential from the issuer
|
||||
* to the subject, have the subject accept it, then delete it.
|
||||
*
|
||||
* <p>As of 2026, Credentials are enabled on Testnet and Devnet.
|
||||
*
|
||||
* <p>Required Maven dependencies (xrpl4j 6.0.0 or later):
|
||||
* <ul>
|
||||
* <li>org.xrpl:xrpl4j-client</li>
|
||||
* <li>org.xrpl:xrpl4j-core</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ManageCredentials {
|
||||
|
||||
private static final HttpUrl TESTNET_RIPPLED_URL =
|
||||
HttpUrl.get("https://s.altnet.rippletest.net:51234/");
|
||||
|
||||
private static final HttpUrl TESTNET_FAUCET_URL =
|
||||
HttpUrl.get("https://apex-faucet.altnet.rippletest.net");
|
||||
|
||||
private static final CredentialType CREDENTIAL_TYPE =
|
||||
CredentialType.ofPlainText("driver-license");
|
||||
|
||||
private static final UnsignedInteger LAST_LEDGER_OFFSET = UnsignedInteger.valueOf(20);
|
||||
|
||||
private static final long FUNDING_POLL_INTERVAL_MS = 1_000L;
|
||||
private static final int FUNDING_MAX_ATTEMPTS = 30;
|
||||
private static final long VALIDATION_POLL_INTERVAL_MS = 4_000L;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
XrplClient xrplClient = new XrplClient(TESTNET_RIPPLED_URL);
|
||||
FaucetClient faucetClient = FaucetClient.construct(TESTNET_FAUCET_URL);
|
||||
SignatureService<PrivateKey> signatureService = new BcSignatureService();
|
||||
|
||||
// --- Create and fund wallets -----------------------------------------
|
||||
System.out.println("Creating issuer and subject wallets...");
|
||||
KeyPair issuer = Seed.ed25519Seed().deriveKeyPair();
|
||||
KeyPair subject = Seed.ed25519Seed().deriveKeyPair();
|
||||
Address issuerAddress = issuer.publicKey().deriveAddress();
|
||||
Address subjectAddress = subject.publicKey().deriveAddress();
|
||||
System.out.println("Issuer: " + issuerAddress);
|
||||
System.out.println("Subject: " + subjectAddress);
|
||||
|
||||
fundAndAwaitAccount(faucetClient, xrplClient, issuerAddress);
|
||||
fundAndAwaitAccount(faucetClient, xrplClient, subjectAddress);
|
||||
|
||||
FeeResult feeResult = xrplClient.fee();
|
||||
|
||||
// --- Issue credential ------------------------------------------------
|
||||
System.out.println("\nIssuing credential from issuer to subject...");
|
||||
CredentialCreate createTx = CredentialCreate.builder()
|
||||
.account(issuerAddress)
|
||||
.subject(subjectAddress)
|
||||
.credentialType(CREDENTIAL_TYPE)
|
||||
.sequence(accountSequence(xrplClient, issuerAddress))
|
||||
.fee(feeResult.drops().openLedgerFee())
|
||||
.lastLedgerSequence(nextLastLedgerSequence(xrplClient))
|
||||
.signingPublicKey(issuer.publicKey())
|
||||
.build();
|
||||
|
||||
TransactionResult<CredentialCreate> createResult = signAndAwaitValidation(
|
||||
xrplClient, signatureService, issuer, createTx, CredentialCreate.class
|
||||
);
|
||||
System.out.println("Credential issued. Hash: " + createResult.hash());
|
||||
|
||||
// --- Subject accepts credential --------------------------------------
|
||||
System.out.println("\nSubject accepting credential...");
|
||||
CredentialAccept acceptTx = CredentialAccept.builder()
|
||||
.account(subjectAddress)
|
||||
.issuer(issuerAddress)
|
||||
.credentialType(CREDENTIAL_TYPE)
|
||||
.sequence(accountSequence(xrplClient, subjectAddress))
|
||||
.fee(feeResult.drops().openLedgerFee())
|
||||
.lastLedgerSequence(nextLastLedgerSequence(xrplClient))
|
||||
.signingPublicKey(subject.publicKey())
|
||||
.build();
|
||||
|
||||
TransactionResult<CredentialAccept> acceptResult = signAndAwaitValidation(
|
||||
xrplClient, signatureService, subject, acceptTx, CredentialAccept.class
|
||||
);
|
||||
System.out.println("Credential accepted. Hash: " + acceptResult.hash());
|
||||
|
||||
// --- Subject deletes credential --------------------------------------
|
||||
System.out.println("\nSubject deleting credential...");
|
||||
CredentialDelete deleteTx = CredentialDelete.builder()
|
||||
.account(subjectAddress)
|
||||
.issuer(issuerAddress)
|
||||
.credentialType(CREDENTIAL_TYPE)
|
||||
.sequence(accountSequence(xrplClient, subjectAddress))
|
||||
.fee(feeResult.drops().openLedgerFee())
|
||||
.lastLedgerSequence(nextLastLedgerSequence(xrplClient))
|
||||
.signingPublicKey(subject.publicKey())
|
||||
.build();
|
||||
|
||||
TransactionResult<CredentialDelete> deleteResult = signAndAwaitValidation(
|
||||
xrplClient, signatureService, subject, deleteTx, CredentialDelete.class
|
||||
);
|
||||
System.out.println("Credential deleted. Hash: " + deleteResult.hash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests faucet funding for {@code address} and polls {@code account_info}
|
||||
* until the account is visible in a validated ledger.
|
||||
*/
|
||||
private static void fundAndAwaitAccount(
|
||||
FaucetClient faucetClient, XrplClient xrplClient, Address address
|
||||
) throws Exception {
|
||||
faucetClient.fundAccount(FundAccountRequest.of(address));
|
||||
|
||||
for (int attempt = 0; attempt < FUNDING_MAX_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
xrplClient.accountInfo(AccountInfoRequestParams.builder()
|
||||
.account(address)
|
||||
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||
.build());
|
||||
System.out.println("Funded: " + address);
|
||||
return;
|
||||
} catch (Exception notYetVisible) {
|
||||
Thread.sleep(FUNDING_POLL_INTERVAL_MS);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Faucet funding for " + address + " did not confirm in time.");
|
||||
}
|
||||
|
||||
private static UnsignedInteger accountSequence(XrplClient xrplClient, Address address)
|
||||
throws Exception {
|
||||
AccountInfoResult info = xrplClient.accountInfo(AccountInfoRequestParams.builder()
|
||||
.account(address)
|
||||
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||
.build());
|
||||
return info.accountData().sequence();
|
||||
}
|
||||
|
||||
private static UnsignedInteger nextLastLedgerSequence(XrplClient xrplClient) throws Exception {
|
||||
UnsignedInteger validated = xrplClient.ledger(LedgerRequestParams.builder()
|
||||
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||
.build())
|
||||
.ledgerIndex()
|
||||
.orElseThrow(() -> new IllegalStateException("No validated ledger index available."))
|
||||
.unsignedIntegerValue();
|
||||
return validated.plus(LAST_LEDGER_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs, submits, and polls until the transaction is validated or its
|
||||
* {@code LastLedgerSequence} has passed.
|
||||
*/
|
||||
private static <T extends Transaction> TransactionResult<T> signAndAwaitValidation(
|
||||
XrplClient xrplClient,
|
||||
SignatureService<PrivateKey> signatureService,
|
||||
KeyPair signer,
|
||||
T transaction,
|
||||
Class<T> transactionType
|
||||
) throws Exception {
|
||||
SingleSignedTransaction<T> signed = signatureService.sign(signer.privateKey(), transaction);
|
||||
SubmitResult<T> submit = xrplClient.submit(signed);
|
||||
|
||||
if (!"tesSUCCESS".equals(submit.engineResult())) {
|
||||
throw new IllegalStateException(
|
||||
"Submit failed: " + submit.engineResult() + " - " + submit.engineResultMessage()
|
||||
);
|
||||
}
|
||||
|
||||
UnsignedInteger lastLedgerSequence = transaction.lastLedgerSequence()
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
"Transaction must set LastLedgerSequence for reliable validation polling."
|
||||
));
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(VALIDATION_POLL_INTERVAL_MS);
|
||||
|
||||
TransactionResult<T> result = xrplClient.transaction(
|
||||
TransactionRequestParams.of(signed.hash()), transactionType
|
||||
);
|
||||
if (result.validated()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
UnsignedInteger latest = xrplClient.ledger(LedgerRequestParams.builder()
|
||||
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||
.build())
|
||||
.ledgerIndex()
|
||||
.orElseThrow(() -> new IllegalStateException("No validated ledger index available."))
|
||||
.unsignedIntegerValue();
|
||||
|
||||
if (latest.compareTo(lastLedgerSequence) > 0) {
|
||||
throw new IllegalStateException(
|
||||
"LastLedgerSequence passed before " + transactionType.getSimpleName() + " was validated."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user