Advanced Ledger Extensions API Reference
Advanced Contract Design
All of the contract design issues described in Building Basic Contract Design are implemented by the Advanced UTXO Unspent Transaction Output. The unspent output of a cryptocurrency transaction, representing the amount of digital currency that has not been spent and is available for use in future transactions. Extensions library, and are included in the specific implementations; for example, chainable, fungible, and identifiable contracts.
Advanced Ledger Types
This section describes the modules of the Advanced UTXO Ledger Extensions library. It contains the following:
base
com.r3.corda.ledger.utxo.base
The base
module provides the underlying component model for designing extensible contracts with delegated contract verification constraint logic, as well as other components which allow CorDapp
Corda Distributed Application. A Java (or any JVM targeting language) application built using the Corda build toolchain and CorDapp API to solve some problem that is best solved in a decentralized manner.
Developers to better express intent throughout their applications.
chainable
com.r3.corda.ledger.utxo.chainable
The chainable
module provides the component model for designing chainable states
An immutable object representing a fact known by one or more participants at a specific point in time. You can use states to represent any type of data, and any kind of fact.
and contracts. Chainable states represent strictly linear state chains, where every state in the chain points to the previous state in the chain. This could be thought of as a similar concept to a blockchain, where each new block points to the previous block.
Designing Chainable States
A chainable state can be implemented by implementing the ChainableState<T>
interface; for example:
@BelongsToContract(ExampleChainableContract.class)
public final class ExampleChainableState extends ChainableState<ExampleChainableState> {
@Nullable
private final StaticPointer<ExampleChainableState> pointer;
public ExampleChainableState(@NotNull final StaticPointer<ExampleChainableState> pointer) {
this.pointer = pointer;
}
@Nullable
public StaticPointer<ExampleChainableState> getPreviousStatePointer() {
return pointer;
}
@NotNull
public List<PublicKey> getParticipants() {
return List.of(...);
}
}
Designing Chainable Commands
Chainable commands allow users to create, update, and delete chainable states.
The ChainableContractCreateCommand
creates new chainable states and verifies the following constraints:
- On chainable state(s) creating, at least one chainable state must be created.
- On chainable state(s) creating, the previous state pointer of every created chainable state must be null.
public final class Create extends ChainableContractCreateCommand<ExampleChainableState> {
@NotNull
public Class<ExampleChainableState> getContractStateType() {
return ExampleChainableState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Create constraints
}
}
The ChainableContractUpdateCommand
supports updating existing chainable states and verifies the following constraints:
- On chainable state(s) updating, at least one chainable state must be consumed.
- On chainable state(s) updating, at least one chainable state must be created.
- On chainable state(s) updating, the previous state pointer of every created chainable state must not be null.
- On chainable state(s) updating, the previous state pointer of every created chainable state must be pointing to exactly one consumed chainable state, exclusively.
public final class Update extends ChainableContractUpdateCommand<ExampleChainableState> {
@NotNull
public Class<ExampleChainableState> getContractStateType() {
return ExampleChainableState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Update constraints
}
}
The ChainableContractDeleteCommand
supports deleting existing chainable states and verifies the following constraint:
- On chainable state(s) deleting, at least one chainable state must be consumed.
public final class Delete extends ChainableContractDeleteCommand<ExampleChainableState> {
@NotNull
public Class<ExampleChainableState> getContractStateType() {
return ExampleChainableState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Delete constraints
}
}
Designing Chainable Contracts
A chainable contract can be implemented by extending the ChainableContract
class; for example:
public final class ExampleChainableContract extends ChainableContract {
@Override
public List<Class<? extends ChainableContractCommand<?>>> getPermittedCommandTypes() {
return List.of(Create.class, Update.class, Delete.class);
}
}
Creating, Updating, and Deleting Chainable States
To create a chainable state, create an instance of a ChainableState
with a pointer
of null
:
new ExampleChainableState(null);
To update an existing chainable state, retrieve the existing ChainableState
’s StateRef
and create a new instance of the ChainableState
. Pass in the StateRef
from the previous step as the pointer
value:
new ExampleChainableState(new StaticPointer(previousStateRef, ExampleChainableState.class));
Consume the existing instance in the same transaction that contains the updated instance.
To delete a chainable state, retrieve the existing ChainableState
’s StateRef
and consume the state using the StateRef
.
fungible
com.r3.corda.ledger.utxo.fungible
The fungible
module provides the component model for designing fungible states and contracts. Fungible states represent states that have a scalar numeric quantity, and can be split, merged and mutually exchanged with other fungible states of the same class. Fungible states represent the building blocks for states like tokens.
Designing Fungible States
A fungible state can be implemented by implementing the FungibleState<T>
interface; for example:
public final class ExampleFungibleState extends FungibleState<NumericDecimal> {
@NotNull
private final NumericDecimal quantity;
public ExampleFungibleState(@NotNull final NumericDecimal quantity) {
this.quantity = quantity;
}
@NotNull
public NumericDecimal getQuantity() {
return quantity;
}
@NotNull
public List<PublicKey> getParticipants() {
return List.of(...);
}
@Override
public boolean isFungibleWith(@NotNull final FungibleState<NumericDecimal> other) {
return this == other || other instanceof ExampleFungibleState // && other fungibility rules.
}
}
Designing Fungible Commands
Fungible commands allow users to create, update and delete fungible states.
The FungibleContractCreateCommand
creates new fungible states and verifies the following constraints:
- On fungible state(s) creating, at least one fungible state must be created.
- On fungible state(s) creating, the quantity of every created fungible state must be greater than zero.
public final class Create extends FungibleContractCreateCommand<ExampleFungibleState> {
@NotNull
public Class<ExampleFungibleState> getContractStateType() {
return ExampleFungibleState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Create constraints
}
}
The FungibleContractUpdateCommand
supports updating existing fungible states and verifies the following constraints:
- On fungible state(s) updating, at least one fungible state must be consumed.
- On fungible state(s) updating, at least one fungible state must be created.
- On fungible state(s) updating, the quantity of every created fungible state must be greater than zero.
- On fungible state(s) updating, the sum of the unscaled values of the consumed states must be equal to the sum of the unscaled values of the created states.
- On fungible state(s) updating, the sum of the consumed states that are fungible with each other must be equal to the sum of the created states that are fungible with each other.
public final class Update extends FungibleContractUpdateCommand<ExampleFungibleState> {
@NotNull
public Class<ExampleFungibleState> getContractStateType() {
return ExampleFungibleState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Update constraints
}
}
The FungibleContractDeleteCommand
supports deleting existing fungible states and verifies the following constraints:
- On fungible state(s) deleting, at least one fungible state input must be consumed.
- On fungible state(s) deleting, the sum of the unscaled values of the consumed states must be greater than the sum of the unscaled values of the created states.
- On fungible state(s) deleting, the sum of consumed states that are fungible with each other must be greater than the sum of the created states that are fungible with each other.
public final class Delete extends FungibleContractDeleteCommand<ExampleFungibleState> {
@NotNull
public Class<ExampleFungibleState> getContractStateType() {
return ExampleFungibleState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Delete constraints
}
}
Designing Fungible Contracts
A fungible contract can be implemented by extending the FungibleContract
class, for example:
public final class ExampleFungibleContract extends FungibleContract {
@Override
public List<Class<? extends FungibleContractCommand<?>>> getPermittedCommandTypes() {
return List.of(Create.class, Update.class, Delete.class);
}
}
identifiable
com.r3.corda.ledger.utxo.identifiable
The identifiable
module provides the component model for designing identifiable states and contracts. Identifiable states represent states that have a unique identifier that is guaranteed unique at the network level. Identifiable states are designed to evolve over time, where unique identifiers can be used to resolve the history of the identifiable state.
Designing Identifiable States
An identifiable state can be implemented by implementing the IdentifiableState
interface, for example:
public final class ExampleIdentifiableState extends IdentifiableState {
@Nullable
private final StateRef id;
public ExampleIdentifiableState(@Nullable final StateRef id) {
this.id = id;
}
@Nullable
public StateRef getId() {
return id;
}
@NotNull
public List<PublicKey> getParticipants() {
return List.of(...);
}
}
Designing Identifiable Commands
Identifiable commands support creating, updating and deleting identifiable states.
The IdentifiableContractCreateCommand
supports creating new identifiable states and verifies the identifiable state(s) creation, at least one identifiable state must be created.
public final class Create extends IdentifiableContractCreateCommand<ExampleIdentifiableState> {
@NotNull
public Class<ExampleIdentifiableState> getContractStateType() {
return ExampleIdentifiableState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Create constraints
}
}
The IdentifiableContractUpdateCommand
updates existing identifiable states and verifies the following constraints:
- On identifiable state(s) updating, at least one identifiable state must be consumed.
- On identifiable state(s) updating, at least one identifiable state must be created.
- On identifiable state(s) updating, each created identifiable state’s identifier must match one consumed identifiable state’s state reference or identifier, exclusively.
public final class Update extends IdentifiableContractUpdateCommand<ExampleIdentifiableState> {
@NotNull
public Class<ExampleIdentifiableState> getContractStateType() {
return ExampleIdentifiableState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Update constraints
}
}
The IdentifiableContractDeleteCommand
deletes existing identifiable states and verifies the identifiable state(s) deletion, at least one identifiable state must be consumed.
public final class Delete extends IdentifiableContractDeleteCommand<ExampleIdentifiableState> {
@NotNull
public Class<ExampleIdentifiableState> getContractStateType() {
return ExampleIdentifiableState.class;
}
@Override
protected void onVerify(@NotNull final UtxoLedgerTransaction transaction) {
// Verify additional Delete constraints
}
}
Designing Identifiable Contracts
An identifiable contract can be implemented by extending the IdentifiableContract
class, for example:
public final class ExampleIdentifiableContract extends IdentifiableContract {
@Override
public List<Class<? extends IdentifiableContractCommand<?>>> getPermittedCommandTypes() {
return List.of(Create.class, Update.class, Delete.class);
}
}
Creating, Updating, and Deleting Identifiable States
To create a unique identifiable state, create an instance of an IdentifiableState
with an id
of null
.
To update an existing identifiable state, do the following:
- Retrieve the existing
IdentifiableState
. - Extract the
id
from theIdentifiableState
. If theid
isnull
, extract theStateRef
instead. - Create a new instance of the
IdentifiableState
and pass in theid
orStateRef
from the previous step. - Consume the existing instance in the same transaction that contains the updated instance.
To delete an identifiable state, do the following:
- Retrieve the existing
IdentifiableState
’sStateRef
. - Consume the state using the
StateRef
.
Retrieving the Latest Identifiable States
To retrieve the latest IdentifiableState
s:
- Call
UtxoLedgerService.query
. - Pass in
IdentifiableStateQueries.GET_BY_IDS
as the query name. - Set the result class as
StateAndRef
. - Call
setParameter
with a key ofids
and value containing all theid
s (orStateRef
s) asString
s for the states that are retrieved.
For example:
@CordaInject
private UtxoLedgerService utxoLedgerService;
PagedQuery.ResultSet<?> resultSet = utxoLedgerService.query(IdentifiableStateQueries.GET_BY_IDS, StateAndRef.class)
.setLimit(3)
.setCreatedTimestampLimit(Instant.now())
.setParameter("ids", List.of(id1, id2, id3))
.execute();
List<StateAndRef<ExampleIdentifiableState>> results = (List<StateAndRef<ExampleIdentifiableState>>) resultSet.getResults();
ownable
com.r3.corda.ledger.utxo.ownable
The ownable
module provides the component to design ownable states and contracts; that is, states that have a defined owner and need the owner’s signature to be consumed.
Designing Ownable States
An ownable state can be designed by implementing the OwnableState
interface:
@BelongsToContract(ExampleOwnableState.class)
public final class ExampleOwnableState implements OwnableState {
@NotNull
private final PublicKey owner;
public ExampleOwnableState(@NotNull PublicKey owner) {
this.owner = owner;
}
@NotNull
@Override
public PublicKey getOwner() {
return owner;
}
@NotNull
@Override
public List<PublicKey> getParticipants() {
return List.of(...);
}
}
Designing Ownable Contracts
The contract for an ownable state must check in the verify
method that the owner of consumed ownable
states have signed the transaction. To simplify writing such a contract, the library provides
OwnableConstraints
helpers. Include and invoke the appropriate helper in the contract
to get this behaviour
public final class ExampleOwnableContract extends DelegatedContract<ExampleOwnableContract.ExampleOwnableContractCommand> {
@NotNull
@Override
protected List<Class<? extends ExampleOwnableContract.ExampleOwnableContractCommand>> getPermittedCommandTypes() {
return List.of(Update.class);
}
interface ExampleOwnableContractCommand extends VerifiableCommand, ContractStateType<ExampleOwnableState> {
}
public final static class Update implements ExampleOwnableContractCommand {
@NotNull
@Override
public Class<ExampleOwnableState> getContractStateType() {
return ExampleOwnableState.class;
}
@Override
public void verify(@NotNull UtxoLedgerTransaction transaction) {
OwnableConstraints.verifyUpdate(transaction, getContractStateType());
}
}
}
Retrieving Ownable States
To retrieve OwnableState
s for a particular owner:
- Call
UtxoLedgerService.query
. - Pass in
OwnableStateQueries.GET_BY_OWNER
as the query name. - Set the result class as
StateAndRef
. - Pass in the following parameters using
setParameter
owner
- Parse the owner key into aString
using theDigestService
.stateType
- The type of state to retrieve, as aString
.
For example:
@CordaInject
private UtxoLedgerService utxoLedgerService;
@CordaInject
private DigestService digestService;
PagedQuery.ResultSet<?> resultSet = utxoLedgerService.query(OwnableStateQueries.GET_BY_OWNER, StateAndRef.class)
.setLimit(50)
.setCreatedTimestampLimit(Instant.now())
.setParameter("owner", digestService.hash(ownerPublicKey.getEncoded(), DigestAlgorithmName.SHA2_256).toString())
.setParameter("stateType", ExampleOwnableState.class.getName())
.execute();
List<StateAndRef<ExampleOwnableState>> results = (List<StateAndRef<ExampleOwnableState>>) resultSet.getResults();
while (resultSet.hasNext()) {
results.addAll((List<StateAndRef<ExampleOwnableState>>) resultSet.next());
}
Designing Wellknown Ownable States
A wellknown ownable state can be designed by implementing the WellKnownOwnableState
interface. It does not provide any contract functionality:
@BelongsToContract(ExampleContract.class)
public class ExampleWellKnownOwnableState implements WellKnownOwnableState {
@NotNull
private final MemberX500Name ownerName;
public ExampleWellKnownOwnableState(@NotNull MemberX500Name ownerName) {
this.ownerName = ownerName;
}
@NotNull
@Override
public MemberX500Name getOwnerName() {
return ownerName;
}
@NotNull
@Override
public List<PublicKey> getParticipants() {
return List.of(...);
}
}
Retrieving Wellknown Ownable States
To retrieve WellKnownOwnableState
s for a particular owner:
- Call
UtxoLedgerService.query
. - Pass in
WellKnownOwnableStateQueries.GET_BY_OWNER_NAME
as the query name. - Set the result class as
StateAndRef
. - Pass in the following parameters using
setParameter
ownerName
- The owner name, as aString
.stateType
- The type of state to retrieve, as aString
.
For example:
@CordaInject
private UtxoLedgerService utxoLedgerService;
@CordaInject
private DigestService digestService;
PagedQuery.ResultSet<?> resultSet = utxoLedgerService.query(WellKnownOwnableStateQueries.GET_BY_OWNER_NAME, StateAndRef.class)
.setLimit(50)
.setCreatedTimestampLimit(Instant.now())
.setParameter("ownerName", ownerX500Name.toString())
.setParameter("stateType", ExampleWellKnownOwnableState.class.getName())
.execute();
List<StateAndRef<ExampleWellKnownOwnableState>> results = (List<StateAndRef<ExampleWellKnownOwnableState>>) resultSet.getResults();
while (resultSet.hasNext()) {
results.addAll((List<StateAndRef<ExampleWellKnownOwnableState>>) resultSet.next());
}
WellKnownOwnableState
can be used independently from OwnableState
, they work well with each other as it can leverage the OwnableConstraints
while querying for specific owners by name instead of by PublicKey
s.issuable
com.r3.corda.ledger.utxo.issuable
The issuable
module designs states that have an issuer as part of the state, verifying that
any issuance of the state has been signed by the issuer, thus restricting who can issue states
of this particular type.
Designing Issuable States
An issuable state can be designed by implementing the IssuableState
interface:
@BelongsToContract(ExampleIssuableContract.class)
public class ExampleIssuableState implements IssuableState {
@NotNull
private final PublicKey issuer;
public ExampleIssuableState(@NotNull PublicKey issuer) {
this.issuer = issuer;
}
@NotNull
@Override
public PublicKey getIssuer() {
return issuer;
}
@NotNull
@Override
public List<PublicKey> getParticipants() {
return List.of(...);
}
}
Designing Issuable Contracts
The contract for issuable states needs to verify that the issuance rules are adhered to; that is, that
the issuer signs for issuance and deletion of any issuable states. This can be achieved by invoking
the IssuableConstraints
helpers provided in the library.
public final class ExampleIssuableContract extends DelegatedContract<ExampleIssuableContract.ExampleIssuableContractCommand> {
@NotNull
@Override
protected List<Class<? extends ExampleIssuableContract.ExampleIssuableContractCommand>> getPermittedCommandTypes() {
return List.of(Create.class, Delete.class);
}
interface ExampleIssuableContractCommand extends VerifiableCommand, ContractStateType<ExampleIssuableState> {
}
public final static class Create implements ExampleIssuableContractCommand {
@NotNull
@Override
public Class<ExampleIssuableState> getContractStateType() {
return ExampleIssuableState.class;
}
@Override
public void verify(@NotNull UtxoLedgerTransaction transaction) {
IssuableConstraints.verifyCreate(transaction, getContractStateType());
}
}
public final static class Delete implements ExampleIssuableContractCommand {
@NotNull
@Override
public Class<ExampleIssuableState> getContractStateType() {
return ExampleIssuableState.class;
}
@Override
public void verify(@NotNull UtxoLedgerTransaction transaction) {
IssuableConstraints.verifyCreate(transaction, getContractStateType());
}
}
}
Retrieving Issuable States
To retrieve IssuableState
s for a particular issuer:
- Call
UtxoLedgerService.query
. - Pass in
IssuableStateQueries.GET_BY_ISSUER
as the query name. - Set the result class as
StateAndRef
. - Pass in the following parameters using
setParameter
issuer
- Parse the issuer key into aString
using theDigestService
.stateType
- The type of state to retrieve, as aString
.
For example:
@CordaInject
private UtxoLedgerService utxoLedgerService;
@CordaInject
private DigestService digestService;
PagedQuery.ResultSet<?> resultSet = utxoLedgerService.query(IssuableStateQueries.GET_BY_ISSUER, StateAndRef.class)
.setLimit(50)
.setCreatedTimestampLimit(Instant.now())
.setParameter("issuer", digestService.hash(issuerPublicKey.getEncoded(), DigestAlgorithmName.SHA2_256).toString())
.setParameter("stateType", ExampleIssuableState.class.getName())
.execute();
List<StateAndRef<ExampleIssuableState>> results = (List<StateAndRef<ExampleIssuableState>>) resultSet.getResults();
while (resultSet.hasNext()) {
results.addAll((List<StateAndRef<ExampleIssuableState>>) resultSet.next());
}
Designing Wellknown Issuable States
A wellknown issuable state can be designed by implementing the WellKnownIssuableState
interface:
@BelongsToContract(ExampleContract.class)
public class ExampleWellKnownIssuableState implements WellKnownIssuableState {
@NotNull
private final MemberX500Name issuerName;
public ExampleWellKnownIssuableState(@NotNull MemberX500Name issuerName) {
this.issuerName = issuerName;
}
@NotNull
@Override
public MemberX500Name getIssuerName() {
return issuerName;
}
@NotNull
@Override
public List<PublicKey> getParticipants() {
return List.of(...);
}
}
WellKnownIssuableState
can be used independently from IssuableState
, they work well with each other as it can leverage the IssuableConstraints
while querying for specific issuers by name instead of by PublicKey
s.Retrieving Wellknown Issuable States
To retrieve WellKnownIssuableState
s for a particular owner:
- Call
UtxoLedgerService.query
. - Pass in
WellKnownIssuableStateQueries.GET_BY_ISSUER_NAME
as the query name. - Set the result class as
StateAndRef
. - Pass in the following parameters using
setParameter
issuerName
- The issuer name, as aString
.stateType
- The type of state to retrieve, as aString
.
For example:
@CordaInject
private UtxoLedgerService utxoLedgerService;
@CordaInject
private DigestService digestService;
PagedQuery.ResultSet<?> resultSet = utxoLedgerService.query(WellKnownIssuableStateQueries.GET_BY_OWNER_NAME, StateAndRef.class)
.setLimit(50)
.setCreatedTimestampLimit(Instant.now())
.setParameter("issuerName", issuerX500Name.toString())
.setParameter("stateType", ExampleWellKnownIssuableState.class.getName())
.execute();
List<StateAndRef<ExampleWellKnownIssuableState>> results = (List<StateAndRef<ExampleWellKnownIssuableState>>) resultSet.getResults();
while (resultSet.hasNext()) {
results.addAll((List<StateAndRef<ExampleWellKnownIssuableState>>) resultSet.next());
}
Was this page helpful?
Thanks for your feedback!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.
We're sorry this page wasn't helpful. Let us know how we can make it better!
Chat with us
Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.
Create an issue
Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.
Propose documentation improvements directly
Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.