mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-04-29 15:37:48 +00:00
Compare commits
12 Commits
master
...
java-crede
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
508a39908c | ||
|
|
8c68d4a7ad | ||
|
|
4e7d0aadc9 | ||
|
|
bbe80b34c9 | ||
|
|
3152430e47 | ||
|
|
81279b4761 | ||
|
|
aaa4668392 | ||
|
|
583b169680 | ||
|
|
1241a33a5d | ||
|
|
d0f6d04715 | ||
|
|
f8d7ca470d | ||
|
|
d0187414a5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ yarn-error.log
|
|||||||
.venv/
|
.venv/
|
||||||
_code-samples/*/js/package-lock.json
|
_code-samples/*/js/package-lock.json
|
||||||
_code-samples/*/go/go.sum
|
_code-samples/*/go/go.sum
|
||||||
|
_code-samples/*/java/target/
|
||||||
_code-samples/*/*/*[Ss]etup.json
|
_code-samples/*/*/*[Ss]etup.json
|
||||||
|
|
||||||
# PHP
|
# PHP
|
||||||
|
|||||||
82
_code-samples/credential/java/README.md
Normal file
82
_code-samples/credential/java/README.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Credential Example (Java)
|
||||||
|
|
||||||
|
This directory contains a Java example demonstrating how to issue a credential, accept a credential, and delete a credential.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Install dependencies before running any examples:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mvn install
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manage Credentials
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mvn exec:java -Dexec.mainClass=com.example.xrpl.ManageCredentials
|
||||||
|
```
|
||||||
|
|
||||||
|
The script should output two newly funded accounts, the CredentialCreate transaction, CredentialAccept transaction, and CredentialDelete transaction. Each successful transaction submission includes a link to the transaction metadata on the XRPL Explorer.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
=== Funding issuer and subject accounts on Testnet ===
|
||||||
|
|
||||||
|
Issuer: r446kRqJA1XGo1zGMiC2RAebtWbQa4duHL
|
||||||
|
Subject: rs8vtTf3aLZW7XRCh398SUkuuAWBn7q49y
|
||||||
|
|
||||||
|
=== Preparing CredentialCreate transaction ===
|
||||||
|
|
||||||
|
{
|
||||||
|
"Account" : "r446kRqJA1XGo1zGMiC2RAebtWbQa4duHL",
|
||||||
|
"TransactionType" : "CredentialCreate",
|
||||||
|
"Fee" : "15",
|
||||||
|
"Sequence" : 16795444,
|
||||||
|
"LastLedgerSequence" : 16795464,
|
||||||
|
"SigningPubKey" : "EDC2C03C393852514C40CCCCF34CB61A8DDB4AECC6C95271468DDF13DE0979DCC7",
|
||||||
|
"Subject" : "rs8vtTf3aLZW7XRCh398SUkuuAWBn7q49y",
|
||||||
|
"CredentialType" : "6B79632D747261646572"
|
||||||
|
}
|
||||||
|
|
||||||
|
=== Submitting CredentialCreate transaction ===
|
||||||
|
|
||||||
|
CredentialCreate succeeded!
|
||||||
|
Explorer: https://testnet.xrpl.org/transactions/D7A00CFC8DFFE384F7A5D2DF14B3AC5629E8F8DBFD8BD06BC389363782F296B3
|
||||||
|
|
||||||
|
=== Preparing CredentialAccept transaction ===
|
||||||
|
|
||||||
|
{
|
||||||
|
"Account" : "rs8vtTf3aLZW7XRCh398SUkuuAWBn7q49y",
|
||||||
|
"TransactionType" : "CredentialAccept",
|
||||||
|
"Fee" : "15",
|
||||||
|
"Sequence" : 16795444,
|
||||||
|
"LastLedgerSequence" : 16795466,
|
||||||
|
"SigningPubKey" : "EDBED812587E0D7D9F965EFE63F4F2B2BB2EB559AD7D1FA9250C239C235CE62726",
|
||||||
|
"Issuer" : "r446kRqJA1XGo1zGMiC2RAebtWbQa4duHL",
|
||||||
|
"CredentialType" : "6B79632D747261646572"
|
||||||
|
}
|
||||||
|
|
||||||
|
=== Submitting CredentialAccept transaction ===
|
||||||
|
|
||||||
|
CredentialAccept succeeded!
|
||||||
|
Explorer: https://testnet.xrpl.org/transactions/C9E55B0A5270FEB37C18E710BDB01C46480530673FE8E4FC39FE3D6B036DF8F5
|
||||||
|
|
||||||
|
=== Preparing CredentialDelete transaction ===
|
||||||
|
|
||||||
|
{
|
||||||
|
"Account" : "rs8vtTf3aLZW7XRCh398SUkuuAWBn7q49y",
|
||||||
|
"TransactionType" : "CredentialDelete",
|
||||||
|
"Fee" : "15",
|
||||||
|
"Sequence" : 16795445,
|
||||||
|
"LastLedgerSequence" : 16795468,
|
||||||
|
"SigningPubKey" : "EDBED812587E0D7D9F965EFE63F4F2B2BB2EB559AD7D1FA9250C239C235CE62726",
|
||||||
|
"Issuer" : "r446kRqJA1XGo1zGMiC2RAebtWbQa4duHL",
|
||||||
|
"CredentialType" : "6B79632D747261646572"
|
||||||
|
}
|
||||||
|
|
||||||
|
=== Submitting CredentialDelete transaction ===
|
||||||
|
|
||||||
|
CredentialDelete succeeded!
|
||||||
|
Explorer: https://testnet.xrpl.org/transactions/0755B4FED0A646D5FB3698891D25DC0374C521DEF00D85D8FCFF58EB09CAB4FE
|
||||||
|
```
|
||||||
35
_code-samples/credential/java/pom.xml
Normal file
35
_code-samples/credential/java/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||||
|
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.example</groupId>
|
||||||
|
<artifactId>credential-samples</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>11</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xrpl</groupId>
|
||||||
|
<artifactId>xrpl4j-client</artifactId>
|
||||||
|
<version>6.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
package com.example.xrpl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
|
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.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.FeeUtils;
|
||||||
|
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.jackson.ObjectMapperFactory;
|
||||||
|
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;
|
||||||
|
import org.xrpl.xrpl4j.model.transactions.TransactionResultCodes;
|
||||||
|
import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This code sample demonstrates the Credential lifecycle on the XRPL.
|
||||||
|
* It issues a credential to a subject, accepts the credential, and then deletes it.
|
||||||
|
*/
|
||||||
|
public class ManageCredentials {
|
||||||
|
|
||||||
|
private static final HttpUrl NETWORK_URL = HttpUrl.get("https://s.altnet.rippletest.net:51234/");
|
||||||
|
private static final HttpUrl FAUCET_URL = HttpUrl.get("https://faucet.altnet.rippletest.net");
|
||||||
|
private static final String EXPLORER_BASE = "https://testnet.xrpl.org/transactions/";
|
||||||
|
|
||||||
|
private static final CredentialType CREDENTIAL_TYPE = CredentialType.ofPlainText("kyc-trader");
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Unwrap CompletionException so async failures print the same clean message
|
||||||
|
// as sync failures. CompletableFuture.join() wraps exceptions in CompletionException
|
||||||
|
Throwable cause = (e instanceof CompletionException && e.getCause() != null)
|
||||||
|
? e.getCause() : e;
|
||||||
|
System.err.println("Error: " + cause.getMessage());
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void run() {
|
||||||
|
|
||||||
|
// ----- Connect to Testnet and fund accounts -----
|
||||||
|
XrplClient xrplClient = new XrplClient(NETWORK_URL);
|
||||||
|
System.out.println("\n=== Funding issuer and subject accounts on Testnet ===\n");
|
||||||
|
|
||||||
|
CompletableFuture<KeyPair> issuerFuture = CompletableFuture.supplyAsync(
|
||||||
|
() -> createAndFundWallet(xrplClient));
|
||||||
|
CompletableFuture<KeyPair> subjectFuture = CompletableFuture.supplyAsync(
|
||||||
|
() -> createAndFundWallet(xrplClient));
|
||||||
|
CompletableFuture.allOf(issuerFuture, subjectFuture).join();
|
||||||
|
|
||||||
|
KeyPair issuer = issuerFuture.join();
|
||||||
|
KeyPair subject = subjectFuture.join();
|
||||||
|
Address issuerAddress = issuer.publicKey().deriveAddress();
|
||||||
|
Address subjectAddress = subject.publicKey().deriveAddress();
|
||||||
|
System.out.println("Issuer: " + issuerAddress);
|
||||||
|
System.out.println("Subject: " + subjectAddress);
|
||||||
|
|
||||||
|
// ----- Prepare CredentialCreate transaction -----
|
||||||
|
System.out.println("\n=== Preparing CredentialCreate transaction ===\n");
|
||||||
|
|
||||||
|
CredentialCreate createTx = CredentialCreate.builder()
|
||||||
|
.account(issuerAddress)
|
||||||
|
.subject(subjectAddress)
|
||||||
|
.credentialType(CREDENTIAL_TYPE)
|
||||||
|
.sequence(accountSequence(xrplClient, issuerAddress))
|
||||||
|
.fee(recommendedFee(xrplClient))
|
||||||
|
.lastLedgerSequence(lastLedgerSequence(xrplClient))
|
||||||
|
.signingPublicKey(issuer.publicKey())
|
||||||
|
.build();
|
||||||
|
printTransactionJson(createTx);
|
||||||
|
|
||||||
|
// ----- Sign, submit, and wait for CredentialCreate validation -----
|
||||||
|
System.out.println("\n=== Submitting CredentialCreate transaction ===\n");
|
||||||
|
|
||||||
|
TransactionResult<CredentialCreate> createResult = signSubmitAndWait(
|
||||||
|
xrplClient, issuer, createTx, CredentialCreate.class);
|
||||||
|
|
||||||
|
requireSuccess(createResult);
|
||||||
|
|
||||||
|
// ----- Prepare CredentialAccept transaction -----
|
||||||
|
System.out.println("\n=== Preparing CredentialAccept transaction ===\n");
|
||||||
|
|
||||||
|
CredentialAccept acceptTx = CredentialAccept.builder()
|
||||||
|
.account(subjectAddress)
|
||||||
|
.issuer(issuerAddress)
|
||||||
|
.credentialType(CREDENTIAL_TYPE)
|
||||||
|
.sequence(accountSequence(xrplClient, subjectAddress))
|
||||||
|
.fee(recommendedFee(xrplClient))
|
||||||
|
.lastLedgerSequence(lastLedgerSequence(xrplClient))
|
||||||
|
.signingPublicKey(subject.publicKey())
|
||||||
|
.build();
|
||||||
|
printTransactionJson(acceptTx);
|
||||||
|
|
||||||
|
// ----- Sign, Submit, and wait for CredentialAccept validation -----
|
||||||
|
System.out.println("\n=== Submitting CredentialAccept transaction ===\n");
|
||||||
|
|
||||||
|
TransactionResult<CredentialAccept> acceptResult = signSubmitAndWait(
|
||||||
|
xrplClient, subject, acceptTx, CredentialAccept.class);
|
||||||
|
|
||||||
|
requireSuccess(acceptResult);
|
||||||
|
|
||||||
|
// ----- Prepare CredentialDelete transaction -----
|
||||||
|
System.out.println("\n=== Preparing CredentialDelete transaction ===\n");
|
||||||
|
|
||||||
|
CredentialDelete deleteTx = CredentialDelete.builder()
|
||||||
|
.account(subjectAddress)
|
||||||
|
.issuer(issuerAddress)
|
||||||
|
.credentialType(CREDENTIAL_TYPE)
|
||||||
|
.sequence(accountSequence(xrplClient, subjectAddress))
|
||||||
|
.fee(recommendedFee(xrplClient))
|
||||||
|
.lastLedgerSequence(lastLedgerSequence(xrplClient))
|
||||||
|
.signingPublicKey(subject.publicKey())
|
||||||
|
.build();
|
||||||
|
printTransactionJson(deleteTx);
|
||||||
|
|
||||||
|
// ----- Sign, Submit, and wait for CredentialDelete validation -----
|
||||||
|
System.out.println("\n=== Submitting CredentialDelete transaction ===\n");
|
||||||
|
|
||||||
|
TransactionResult<CredentialDelete> deleteResult = signSubmitAndWait(
|
||||||
|
xrplClient, subject, deleteTx, CredentialDelete.class);
|
||||||
|
|
||||||
|
requireSuccess(deleteResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Helper functions =====
|
||||||
|
|
||||||
|
// Generates a new Ed25519 keypair, funds it from the Testnet faucet, and
|
||||||
|
// returns the keypair once the account is visible on a validated ledger.
|
||||||
|
private static KeyPair createAndFundWallet(XrplClient xrplClient) {
|
||||||
|
KeyPair keyPair = Seed.ed25519Seed().deriveKeyPair();
|
||||||
|
Address address = keyPair.publicKey().deriveAddress();
|
||||||
|
FaucetClient faucetClient = FaucetClient.construct(FAUCET_URL);
|
||||||
|
faucetClient.fundAccount(FundAccountRequest.of(address));
|
||||||
|
|
||||||
|
for (int attempt = 0; attempt < 20; attempt++) {
|
||||||
|
try {
|
||||||
|
xrplClient.accountInfo(AccountInfoRequestParams.builder()
|
||||||
|
.account(address)
|
||||||
|
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||||
|
.build());
|
||||||
|
return keyPair;
|
||||||
|
} catch (JsonRpcClientErrorException notYetVisible) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1_000L);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException("Account polling interrupted for " + address + ". " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Faucet funding for " + address + " did not confirm in time.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches the next transaction sequence number of an address from
|
||||||
|
// the latest validated ledger.
|
||||||
|
private static UnsignedInteger accountSequence(XrplClient xrplClient, Address address) {
|
||||||
|
try {
|
||||||
|
AccountInfoResult info = xrplClient.accountInfo(AccountInfoRequestParams.builder()
|
||||||
|
.account(address)
|
||||||
|
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||||
|
.build());
|
||||||
|
return info.accountData().sequence();
|
||||||
|
} catch (JsonRpcClientErrorException e) {
|
||||||
|
throw new RuntimeException("Failed to fetch account sequence for " + address + ". " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches the current network fee and returns the recommended fee for
|
||||||
|
// a standard (non-multisig, non-batch) transaction.
|
||||||
|
private static XrpCurrencyAmount recommendedFee(XrplClient xrplClient) {
|
||||||
|
try {
|
||||||
|
return FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee();
|
||||||
|
} catch (JsonRpcClientErrorException e) {
|
||||||
|
throw new RuntimeException("Failed to fetch network fee. " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes a safe LastLedgerSequence for a new transaction. The
|
||||||
|
// latest validated ledger index plus a small buffer (20 ledgers).
|
||||||
|
private static UnsignedInteger lastLedgerSequence(XrplClient xrplClient) {
|
||||||
|
try {
|
||||||
|
UnsignedInteger validatedLedger = xrplClient.ledger(LedgerRequestParams.builder()
|
||||||
|
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||||
|
.build())
|
||||||
|
.ledgerIndexSafe()
|
||||||
|
.unsignedIntegerValue();
|
||||||
|
return validatedLedger.plus(UnsignedInteger.valueOf(20));
|
||||||
|
} catch (JsonRpcClientErrorException e) {
|
||||||
|
throw new RuntimeException("Failed to compute LastLedgerSequence. " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints a transaction as a formatted JSON.
|
||||||
|
private static void printTransactionJson(Transaction tx) {
|
||||||
|
try {
|
||||||
|
System.out.println(ObjectMapperFactory.create().writerWithDefaultPrettyPrinter().writeValueAsString(tx));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException("Failed to serialize transaction JSON. " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signs and submits a transaction, then polls the network until
|
||||||
|
// the transaction reaches a validated state.
|
||||||
|
private static <T extends Transaction> TransactionResult<T> signSubmitAndWait(
|
||||||
|
XrplClient xrplClient,
|
||||||
|
KeyPair signer,
|
||||||
|
T transaction,
|
||||||
|
Class<T> transactionType
|
||||||
|
) {
|
||||||
|
SignatureService<PrivateKey> signatureService = new BcSignatureService();
|
||||||
|
|
||||||
|
UnsignedInteger lastLedgerSequence = transaction.lastLedgerSequence()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException(
|
||||||
|
"Must set LastLedgerSequence for polling expiration"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
SingleSignedTransaction<T> signed = signatureService.sign(signer.privateKey(), transaction);
|
||||||
|
SubmitResult<T> submit = xrplClient.submit(signed);
|
||||||
|
|
||||||
|
if (!TransactionResultCodes.TES_SUCCESS.equals(submit.engineResult())) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Submission rejected. " + submit.engineResult() + " — " + submit.engineResultMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Thread.sleep(1_000L);
|
||||||
|
|
||||||
|
// Poll network for validated status using tx hash
|
||||||
|
try {
|
||||||
|
TransactionResult<T> result = xrplClient.transaction(
|
||||||
|
TransactionRequestParams.of(signed.hash()), transactionType);
|
||||||
|
if (result.validated()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} catch (JsonRpcClientErrorException e) {
|
||||||
|
// Transaction not found; keep polling.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if transaction expired before polling again
|
||||||
|
UnsignedInteger currentLedger = xrplClient.ledger(LedgerRequestParams.builder()
|
||||||
|
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
|
||||||
|
.build())
|
||||||
|
.ledgerIndexSafe()
|
||||||
|
.unsignedIntegerValue();
|
||||||
|
if (currentLedger.compareTo(lastLedgerSequence) > 0) {
|
||||||
|
throw new IllegalStateException("Transaction expired. Current ledger " + currentLedger
|
||||||
|
+ " passed LastLedgerSequence " + lastLedgerSequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException("Transaction polling interrupted. " + e.getMessage(), e);
|
||||||
|
} catch (JsonRpcClientErrorException | JsonProcessingException e) {
|
||||||
|
throw new RuntimeException("Transaction processing failed. " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for a tesSUCCESS result code. If true, prints an explorer
|
||||||
|
// link. Otherwise, throws an error.
|
||||||
|
private static void requireSuccess(TransactionResult<?> result) {
|
||||||
|
String code = result.metadata().get().transactionResult();
|
||||||
|
String txType = result.transaction().transactionType().value();
|
||||||
|
if (!TransactionResultCodes.TES_SUCCESS.equals(code)) {
|
||||||
|
throw new IllegalStateException(txType + " failed with error code " + code);
|
||||||
|
}
|
||||||
|
System.out.println(txType + " succeeded!");
|
||||||
|
System.out.println("Explorer: " + EXPLORER_BASE + result.hash());
|
||||||
|
}
|
||||||
|
}
|
||||||
16
_code-samples/credential/java/src/main/resources/logback.xml
Normal file
16
_code-samples/credential/java/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Quiets xrpl4j's DEBUG chatter so tutorial output stays readable.
|
||||||
|
Raise xrpl4j to DEBUG to see wire-level transaction details. -->
|
||||||
|
<configuration>
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="org.xrpl.xrpl4j" level="WARN"/>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
@@ -373,6 +373,7 @@
|
|||||||
[get_counts command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
|
[get_counts command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
|
||||||
[get_counts method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
|
[get_counts method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
|
||||||
[Get Started Using Go]: /docs/tutorials/get-started/get-started-go.md
|
[Get Started Using Go]: /docs/tutorials/get-started/get-started-go.md
|
||||||
|
[Get Started Using Java]: /docs/tutorials/get-started/get-started-java.md
|
||||||
[Get Started Using JavaScript]: /docs/tutorials/get-started/get-started-javascript.md
|
[Get Started Using JavaScript]: /docs/tutorials/get-started/get-started-javascript.md
|
||||||
[Get Started Using Python]: /docs/tutorials/get-started/get-started-python.md
|
[Get Started Using Python]: /docs/tutorials/get-started/get-started-python.md
|
||||||
[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
|
[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
|
||||||
@@ -490,6 +491,7 @@
|
|||||||
[vault_info method]: /docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info.md
|
[vault_info method]: /docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info.md
|
||||||
[wallet_propose command]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
|
[wallet_propose command]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
|
||||||
[wallet_propose method]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
|
[wallet_propose method]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
|
||||||
|
[xrpl4j library]: https://github.com/XRPLF/xrpl4j
|
||||||
[xrpl-go library]: https://github.com/XRPLF/xrpl-go
|
[xrpl-go library]: https://github.com/XRPLF/xrpl-go
|
||||||
[xrpl.js library]: https://github.com/XRPLF/xrpl.js
|
[xrpl.js library]: https://github.com/XRPLF/xrpl.js
|
||||||
[xrpl-py library]: https://github.com/XRPLF/xrpl-py
|
[xrpl-py library]: https://github.com/XRPLF/xrpl-py
|
||||||
|
|||||||
151
docs/tutorials/compliance-features/manage-credentials.md
Normal file
151
docs/tutorials/compliance-features/manage-credentials.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
seo:
|
||||||
|
description: Issue, accept, and delete a credential on the XRP Ledger.
|
||||||
|
metadata:
|
||||||
|
indexPage: true
|
||||||
|
labels:
|
||||||
|
- Credentials
|
||||||
|
---
|
||||||
|
|
||||||
|
# Manage Credentials
|
||||||
|
|
||||||
|
This tutorial shows you how to manage the full lifecycle of [Credentials][] on the XRP Ledger: issuing a credential to a subject, accepting the credential, and deleting it.
|
||||||
|
|
||||||
|
{% amendment-disclaimer name="Credentials" /%}
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
By the end of this tutorial, you will be able to:
|
||||||
|
|
||||||
|
- Issue a credential to a subject account.
|
||||||
|
- Accept a credential as the subject.
|
||||||
|
- Delete a credential from the ledger.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To complete this tutorial, you should:
|
||||||
|
|
||||||
|
- Have a basic understanding of the XRP Ledger.
|
||||||
|
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||||
|
- **Java** with the [xrpl4j library][]. See [Get Started Using Java][] for setup steps.
|
||||||
|
|
||||||
|
## Source Code
|
||||||
|
|
||||||
|
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/credential/" %}code samples section of this website's repository{% /repo-link %}.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
### 1. Install dependencies
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Java" %}
|
||||||
|
From the code sample folder, use `mvn` to install dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn install
|
||||||
|
```
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
### 2. Set up client and fund accounts
|
||||||
|
|
||||||
|
To get started, import the necessary libraries and instantiate a client to connect to the XRPL Testnet. This example imports:
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Java" %}
|
||||||
|
- `xrpl4j`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||||
|
- `OkHttp`, `Guava`, `Jackson`: Used for HTTP URL construction, unsigned integer arithmetic, and JSON serialization.
|
||||||
|
- `java.util.concurrent`: Used for async operations.
|
||||||
|
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" before="// ----- Prepare CredentialCreate" /%}
|
||||||
|
|
||||||
|
The `createAndFundWallet()` helper generates an Ed25519 keypair, funds it from the Testnet faucet, and polls Testnet until the account is visible on a validated ledger.
|
||||||
|
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// Generates a new Ed25519 keypair" before="// Fetches the next transaction sequence number" /%}
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
### 3. Prepare CredentialCreate transaction
|
||||||
|
|
||||||
|
Create the [CredentialCreate transaction][] object.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Java" %}
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Prepare CredentialCreate" before="// ----- Sign, submit, and wait for CredentialCreate" /%}
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
The credential is identified by the issuer, subject, and credential type (written as a hexadecimal string).
|
||||||
|
|
||||||
|
### 4. Submit CredentialCreate transaction
|
||||||
|
|
||||||
|
Sign and submit the `CredentialCreate` transaction to the XRP Ledger.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Java" %}
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Sign, submit, and wait for CredentialCreate" before="// ----- Prepare CredentialAccept" /%}
|
||||||
|
|
||||||
|
The `signSubmitAndWait()` helper signs a transaction, submits it, and polls Testnet until it reaches a validated ledger.
|
||||||
|
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// Signs and submits a transaction" before="// Checks for a tesSUCCESS result code" /%}
|
||||||
|
|
||||||
|
The `requireSuccess` helper verifies that the transaction succeeded with a `tesSUCCESS` result code and posts a link to the transaction metadata on the XRPL Explorer.
|
||||||
|
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// Checks for a tesSUCCESS result code" /%}
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
### 5. Prepare CredentialAccept transaction
|
||||||
|
|
||||||
|
Create the [CredentialAccept transaction][] object. The subject account must accept the credential to make it valid.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Java" %}
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Prepare CredentialAccept" before="// ----- Sign, Submit, and wait for CredentialAccept" /%}
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
### 6. Submit CredentialAccept transaction
|
||||||
|
|
||||||
|
Sign and submit the `CredentialAccept` transaction to the XRP Ledger.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Java" %}
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Sign, Submit, and wait for CredentialAccept" before="// ----- Prepare CredentialDelete" /%}
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
### 7. Prepare CredentialDelete transaction
|
||||||
|
|
||||||
|
Create the [CredentialDelete transaction][] object. Either the issuer or the subject can delete a credential.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Java" %}
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Prepare CredentialDelete" before="// ----- Sign, Submit, and wait for CredentialDelete" /%}
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
### 8. Submit CredentialDelete transaction
|
||||||
|
|
||||||
|
Sign and submit the `CredentialDelete` transaction to the XRP Ledger.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Java" %}
|
||||||
|
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Sign, Submit, and wait for CredentialDelete" before="// ===== Helper functions" /%}
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
**Concepts**:
|
||||||
|
- [Credentials][]
|
||||||
|
|
||||||
|
**Tutorials**:
|
||||||
|
- [Verify Credentials](./verify-credentials.md)
|
||||||
|
|
||||||
|
**References**:
|
||||||
|
- [CredentialCreate transaction][]
|
||||||
|
- [CredentialAccept transaction][]
|
||||||
|
- [CredentialDelete transaction][]
|
||||||
|
|
||||||
|
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||||
@@ -297,6 +297,7 @@
|
|||||||
expanded: false
|
expanded: false
|
||||||
items:
|
items:
|
||||||
- page: docs/tutorials/compliance-features/require-destination-tags.md
|
- page: docs/tutorials/compliance-features/require-destination-tags.md
|
||||||
|
- page: docs/tutorials/compliance-features/manage-credentials.md
|
||||||
- page: docs/tutorials/compliance-features/verify-credentials.md
|
- page: docs/tutorials/compliance-features/verify-credentials.md
|
||||||
- page: docs/tutorials/compliance-features/create-permissioned-domains-in-javascript.md
|
- page: docs/tutorials/compliance-features/create-permissioned-domains-in-javascript.md
|
||||||
- group: Programmability
|
- group: Programmability
|
||||||
|
|||||||
Reference in New Issue
Block a user