Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
474eb81
added account permission delegation
cybele-ripple Feb 2, 2026
ba48f46
remove extra files
cybele-ripple Feb 5, 2026
7304367
defined explicit permission values, added unit tests, added lookups
cybele-ripple Feb 6, 2026
fc834c7
Merge branch 'main' into account-permission-delegation
cybele-ripple Feb 6, 2026
a9d902b
updated integration tests and field implementation
cybele-ripple Feb 6, 2026
bfc033d
Add ability to access flags from Transaction.java (#690)
sappenin Feb 7, 2026
ab0045c
Batch PR1: Add tfInnerBatchTxn to all transactions
sappenin Feb 10, 2026
be9d4f7
added account permission delegation
cybele-ripple Feb 2, 2026
d9ff2f1
new enum for granularpermission, added transactiontype enum, check fo…
cybele-ripple Feb 18, 2026
9868950
removed incomplete file
cybele-ripple Feb 18, 2026
408efff
resolve merge conflicts
cybele-ripple Feb 18, 2026
af2fc8f
added integration tests related to Delegate
cybele-ripple Feb 19, 2026
d5ce28f
Merge branch 'main' into account-permission-delegation
sappenin Feb 20, 2026
be26ff3
combined delegateset and delegate it, updated GranularPermission to a…
cybele-ripple Feb 20, 2026
a270901
added metadelegateobject, updated metaledger, updated tests to reflec…
cybele-ripple Feb 20, 2026
4844bc4
removed changes made to UInt32Type and removed unit test that related…
cybele-ripple Feb 20, 2026
38c887c
added DelegateLedgerEntryParams and related tests, fixed line length …
cybele-ripple Feb 20, 2026
26c6a98
added for loop to create permissionValues from GranularPermission
cybele-ripple Feb 20, 2026
f3ca876
changed from null to return optional
cybele-ripple Feb 20, 2026
bd1b597
ensure line <=120 chars
cybele-ripple Feb 20, 2026
ea3dcea
remove extra blank lines, added test coverage for DefaultDefinitionsP…
cybele-ripple Feb 20, 2026
e32e21d
checkstyle fix
cybele-ripple Feb 20, 2026
30cf686
set version to 6.1.0-rc.1-SNAPSHOT
Patel-Raj11 Feb 21, 2026
35c6694
Set version back to HEAD-SNAPSHOT
sappenin Feb 21, 2026
198668f
renamed permissions to accountpermissions, streamlined listing granul…
cybele-ripple Feb 26, 2026
9f3b13a
Merge branch 'main' into account-permission-delegation
cybele-ripple Feb 26, 2026
bfdaafc
added test coverage
cybele-ripple Feb 26, 2026
5bd0628
fixed snapshot typo
cybele-ripple Feb 26, 2026
2b357cc
added coverage for SignatureUtils.java
cybele-ripple Feb 26, 2026
7cc6652
used TransactionType for non_delegable_transactions
cybele-ripple Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import com.google.common.io.Resources;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class DefaultDefinitionsProvider implements DefinitionsProvider {
Expand All @@ -43,14 +45,61 @@ public DefaultDefinitionsProvider(final ObjectMapper objectMapper) {

this.supplier = Suppliers.memoize(() -> {
try {
return objectMapper.readerFor(Definitions.class)
Definitions baseDefinitions = objectMapper.readerFor(Definitions.class)
.readValue(Resources.getResource(DefaultDefinitionsProvider.class, "/definitions.json"));

// Generate PERMISSION_VALUES dynamically from TRANSACTION_TYPES
Map<String, Integer> permissionValues = generatePermissionValues(baseDefinitions);

// Return a new Definitions object with the generated PERMISSION_VALUES
return ImmutableDefinitions.builder()
.from(baseDefinitions)
.permissionValues(permissionValues)
.build();
} catch (IOException e) {
throw new IllegalStateException("Cannot read definition.json file", e);
}
});
}

/**
* Generate PERMISSION_VALUES mapping from TRANSACTION_TYPES.
* This follows the same logic as xrpl.js:
* - Granular permissions start at 65537
* - Transaction type permissions are transaction type code + 1
*
* @param definitions The base {@link Definitions} loaded from definitions.json
* @return A {@link Map} of permission names to their numeric values
*/
private Map<String, Integer> generatePermissionValues(Definitions definitions) {
Map<String, Integer> permissionValues = new HashMap<>();

// Add granular permissions (starting at 65537)
permissionValues.put("TrustlineAuthorize", 65537);
permissionValues.put("TrustlineFreeze", 65538);
permissionValues.put("PaymentMint", 65539);
permissionValues.put("PaymentBurn", 65540);
permissionValues.put("PaymentClawback", 65541);
permissionValues.put("MPTokenIssuanceCreate", 65542);
permissionValues.put("MPTokenIssuanceDestroy", 65543);
permissionValues.put("MPTokenIssuanceAuthorize", 65544);
permissionValues.put("MPTokenIssuanceUnlock", 65545);
permissionValues.put("MPTokenIssuanceLock", 65546);
permissionValues.put("MPTokenIssuanceTransfer", 65547);
permissionValues.put("MPTokenIssuanceClawback", 65548);

// Add transaction type permissions (transaction type code + 1)
// Exclude pseudo-transactions and non-delegatable transactions
definitions.transactionTypes().forEach((txType, txCode) -> {
// Skip Invalid (-1) and pseudo-transactions (>= 100)
if (txCode >= 0 && txCode < 100) {
permissionValues.put(txType, txCode + 1);
}
});

return permissionValues;
}

@Override
public Definitions get() {
return supplier.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.collect.ImmutableMap;
import org.immutables.value.Value.Default;
import org.immutables.value.Value.Immutable;

import java.util.List;
Expand Down Expand Up @@ -77,4 +79,16 @@ public interface Definitions {
@JsonProperty("TRANSACTION_RESULTS")
Map<String, Integer> transactionResults();

/**
* Permission values mappings (permission value to ordinal value).
* This field is not present in the generated definitions.json file and is populated
* programmatically by {@link DefaultDefinitionsProvider}.
*
* @return {@link Map} keyed by {@link String} with {@link Integer} values for all permission values.
*/
@JsonProperty("PERMISSION_VALUES")
@Default
default Map<String, Integer> permissionValues() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Immutables should fill-in default collections for you automagically, so I think this can be updated to this instead (unless you have some reason to keep this as-is):

 @JsonProperty("PERMISSION_VALUES")
 Map<String, Integer> permissionValues();

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

return ImmutableMap.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public class DefinitionsService {

private final Map<Integer, String> ledgerEntryTypeReverseLookupMap;

private final Map<Integer, String> permissionValueReverseLookupMap;

/**
* Required-args Constructor.
*
Expand Down Expand Up @@ -84,6 +86,7 @@ public class DefinitionsService {
this.transactionTypeReverseLookupMap = inverse(definitions.transactionTypes());
this.transactionResultReverseLookupNap = inverse(definitions.transactionResults());
this.ledgerEntryTypeReverseLookupMap = inverse(definitions.ledgerEntryTypes());
this.permissionValueReverseLookupMap = inverse(definitions.permissionValues());
}

/**
Expand Down Expand Up @@ -184,6 +187,8 @@ public Optional<Integer> mapFieldSpecialization(String fieldName, String value)
return Optional.ofNullable(definitions.transactionResults().get(value));
case "TransactionType":
return Optional.ofNullable(definitions.transactionTypes().get(value));
case "PermissionValue":
return Optional.ofNullable(definitions.permissionValues().get(value));
default:
return Optional.empty();
}
Expand All @@ -208,6 +213,8 @@ public Optional<String> mapFieldRawValueToSpecialization(String fieldName, Strin
return Optional.ofNullable(transactionResultReverseLookupNap.get(Integer.valueOf(value)));
case "TransactionType":
return Optional.ofNullable(transactionTypeReverseLookupMap.get(Integer.valueOf(value)));
case "PermissionValue":
return Optional.ofNullable(permissionValueReverseLookupMap.get(Integer.valueOf(value)));
default:
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
* =========================LICENSE_END==================================
*/

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.LongNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.primitives.UnsignedLong;
import org.xrpl.xrpl4j.codec.binary.definitions.DefinitionsService;
import org.xrpl.xrpl4j.codec.binary.definitions.FieldInstance;
import org.xrpl.xrpl4j.codec.binary.serdes.BinaryParser;

/**
Expand All @@ -48,8 +52,45 @@ public UInt32Type fromJson(JsonNode value) {
return new UInt32Type(UnsignedLong.valueOf(value.asText()));
}

@Override
public UInt32Type fromJson(JsonNode value, FieldInstance fieldInstance) throws JsonProcessingException {
// Special handling for PermissionValue field - convert string permission names to UInt32 values
if (fieldInstance != null && "PermissionValue".equals(fieldInstance.name()) && value.isTextual()) {
String textValue = value.asText();

// Check if it's already a numeric value (from mapSpecializedValues)
try {
int numericValue = Integer.parseInt(textValue);
return new UInt32Type(UnsignedLong.valueOf(numericValue));
} catch (NumberFormatException e) {
// It's a permission name, look it up in the mapping
Integer permissionValue = DefinitionsService.getInstance()
.mapFieldSpecialization("PermissionValue", textValue)
.orElseThrow(() -> new IllegalArgumentException("Unknown permission value: " + textValue));
return new UInt32Type(UnsignedLong.valueOf(permissionValue));
}
}
return fromJson(value);
}

@Override
public JsonNode toJson() {
return new LongNode(UnsignedLong.valueOf(toHex(), 16).longValue());
}

@Override
public JsonNode toJson(FieldInstance fieldInstance) {
// Special handling for PermissionValue field - convert UInt32 values back to string permission names
if (fieldInstance != null && "PermissionValue".equals(fieldInstance.name())) {
UnsignedLong value = UnsignedLong.valueOf(toHex(), 16);
String intValueStr = String.valueOf(value.intValue());

// Look up the permission name from the reverse mapping
return DefinitionsService.getInstance()
.mapFieldRawValueToSpecialization("PermissionValue", intValueStr)
.<JsonNode>map(TextNode::new)
.orElseGet(this::toJson);
}
return toJson();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
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.DelegateSet;
import org.xrpl.xrpl4j.model.transactions.DepositPreAuth;
import org.xrpl.xrpl4j.model.transactions.DidDelete;
import org.xrpl.xrpl4j.model.transactions.DidSet;
Expand Down Expand Up @@ -433,6 +434,10 @@ public <T extends Transaction> SingleSignedTransaction<T> addSignatureToTransact
transactionWithSignature = CredentialDelete.builder().from((CredentialDelete) transaction)
.transactionSignature(signature)
.build();
} else if (DelegateSet.class.isAssignableFrom(transaction.getClass())) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Since we are adding tests in SignatureUtilsTest for all transaction types, we should add it for DelegateSet.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this comment as resolved, but when I run SignatureUtilsTest.java with coverage, it shows this as still uncovered.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added coverage for this line and reran tests with coverage to confirm

transactionWithSignature = DelegateSet.builder().from((DelegateSet) transaction)
.transactionSignature(signature)
.build();
} else if (PermissionedDomainSet.class.isAssignableFrom(transaction.getClass())) {
transactionWithSignature = PermissionedDomainSet.builder().from((PermissionedDomainSet) transaction)
.transactionSignature(signature)
Expand Down Expand Up @@ -684,6 +689,10 @@ public <T extends Transaction> T addMultiSignaturesToTransaction(T transaction,
transactionWithSignatures = CredentialDelete.builder().from((CredentialDelete) transaction)
.signers(signers)
.build();
} else if (DelegateSet.class.isAssignableFrom(transaction.getClass())) {
transactionWithSignatures = DelegateSet.builder().from((DelegateSet) transaction)
.signers(signers)
.build();
} else if (PermissionedDomainSet.class.isAssignableFrom(transaction.getClass())) {
transactionWithSignatures = PermissionedDomainSet.builder().from((PermissionedDomainSet) transaction)
.signers(signers)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package org.xrpl.xrpl4j.model.ledger;

/*-
* ========================LICENSE_START=================================
* xrpl4j :: model
* %%
* Copyright (C) 2020 - 2022 XRPL Foundation and its contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================LICENSE_END==================================
*/

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.primitives.UnsignedInteger;
import org.immutables.value.Value;
import org.xrpl.xrpl4j.model.flags.Flags;
import org.xrpl.xrpl4j.model.transactions.Address;
import org.xrpl.xrpl4j.model.transactions.DelegateSet;
import org.xrpl.xrpl4j.model.transactions.Hash256;
import org.xrpl.xrpl4j.model.transactions.PermissionWrapper;

import java.util.List;

/**
* This object represents a set of permissions that an account has delegated to another account.
* {@link DelegateSet} transactions create these objects.
*/
@Value.Immutable
@JsonSerialize(as = ImmutableDelegateObject.class)
@JsonDeserialize(as = ImmutableDelegateObject.class)
public interface DelegateObject extends LedgerObject {

/**
* Construct a builder for this class.
*
* @return An {@link ImmutableDelegateObject.Builder}.
*/
static ImmutableDelegateObject.Builder builder() {
return ImmutableDelegateObject.builder();
}

/**
* The type of ledger object, which will always be "Delegate" in this case.
*
* @return Always {@link LedgerEntryType#DELEGATE}.
*/
@JsonProperty("LedgerEntryType")
@Value.Derived
default LedgerEntryType ledgerEntryType() {
return LedgerEntryType.DELEGATE;
}

/**
* The account that wants to authorize another account.
*
* @return The {@link Address} of the account.
*/
@JsonProperty("Account")
Address account();

/**
* The authorized account.
*
* @return The {@link Address} of the authorized account.
*/
@JsonProperty("Authorize")
Address authorize();

/**
* The transaction permissions that the account has access to.
*
* @return A {@link List} of {@link PermissionWrapper}s.
*/
@JsonProperty("Permissions")
List<PermissionWrapper> permissions();

/**
* A hint indicating which page of the sender's owner directory links to this object,
* in case the directory consists of multiple pages.
*
* <p>Note: The object does not contain a direct link to the owner directory containing it, since that value can be
* derived from the Account.
*
* @return A {@link String} containing the owner node hint.
*/
@JsonProperty("OwnerNode")
String ownerNode();

/**
* A bit-map of boolean flags. No flags are defined for the Delegate object
* type, so this value is always 0.
*
* @return Always {@link Flags#UNSET}.
*/
@JsonProperty("Flags")
@Value.Derived
default Flags flags() {
return Flags.UNSET;
}

/**
* The identifying hash of the transaction that most recently modified this object.
*
* @return A {@link Hash256} containing the previous transaction hash.
*/
@JsonProperty("PreviousTxnID")
Hash256 previousTransactionId();

/**
* The index of the ledger that contains the transaction that most recently modified this object.
*
* @return An {@link UnsignedInteger} representing the previous transaction ledger sequence.
*/
@JsonProperty("PreviousTxnLgrSeq")
UnsignedInteger previousTransactionLedgerSequence();

/**
* The unique ID of the {@link DelegateObject}.
*
* @return A {@link Hash256} containing the ID.
*/
Hash256 index();
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
// @JsonSubTypes.Type(value = ImmutableAmendmentsObject.class, name = "Amendments"),
@JsonSubTypes.Type(value = ImmutableCheckObject.class, name = "Check"),
@JsonSubTypes.Type(value = ImmutableCredentialObject.class, name = "Credential"),
@JsonSubTypes.Type(value = ImmutableDelegateObject.class, name = "Delegate"),
@JsonSubTypes.Type(value = ImmutableDepositPreAuthObject.class, name = "DepositPreauth"),
// @JsonSubTypes.Type(value = ImmutableDirectoryNodeObject.class, name = "DirectoryNode"),
@JsonSubTypes.Type(value = ImmutableEscrowObject.class, name = "Escrow"),
Expand Down Expand Up @@ -164,6 +165,11 @@ enum LedgerEntryType {
*/
NFTOKEN_PAGE("NFTokenPage"),

/**
* The {@link LedgerEntryType} for {@code Delegate} ledger objects.
*/
DELEGATE("Delegate"),

/**
* The {@link LedgerEntryType} for {@code AmmObject} ledger objects.
*
Expand Down
Loading
Loading