diff --git a/design/design.md b/design/design.md index 2ef7bef1..429e6ded 100644 --- a/design/design.md +++ b/design/design.md @@ -111,10 +111,19 @@ is not always true, as fungible derivative contracts exist, for example. The new `FungibleState` is simply defined as: ```kotlin +// kotlin + interface FungibleState : ContractState { val amount: Amount } ``` +```java +// java + +interface FungibleState extends ContractState { + Amount getAmount(); +} +``` Where `T` is some type of token or a reference to a type of token defined elsewhere. `T` deliberately has no upper-bound to maintain flexibility going forward. Note that `Issuer` is omitted and the interface implements `ContractState` as opposed @@ -129,8 +138,31 @@ This paper proposes to add a new type called `StatePointer `. `StatePointer`s fo refer to a state from inside another state. For example: ```kotlin +// kotlin + data class FooState(val ref: StateRef) : ContractState ``` +```java +// java + +class FooState implements ContractState { + private final StateRef ref; + + public FooState(StateRef ref) { + this.ref = ref; + } + + public StateRef getRef() { + return ref; + } + + @NotNull + @Override + public List getParticipants() { + return null; + } +} +``` `StatePointer`s come in two variants: @@ -144,6 +176,8 @@ expectation that the state being depended upon will evolve independently to the responsibility for updates or to compartmentalise data for privacy reasons. ```kotlin +// kotlin + class LinearPointer( override val pointer: UniqueIdentifier, override val type: Class @@ -162,6 +196,39 @@ class LinearPointer( // Omitted code. } ``` +```java +// java + +public class LinearPointer extends StatePointer { + + private final UniqueIdentifier pointer; + private final Class type; + private final Boolean isResolved; + + public LinearPointer(UniqueIdentifier pointer, Class type, Boolean isResolved) { + super(); + this.pointer = pointer; + this.type = type; + this.isResolved = isResolved; + } + + // Getters Omitted + + @NotNull + @Override + public StateAndRef resolve(@NotNull ServiceHub services) { + // Omitted Code + } + + @NotNull + @Override + public StateAndRef resolve(@NotNull LedgerTransaction ltx) { + return ltx.referenceInputRefsOfType(type) + .stream() + .filter(it -> pointer.equals(it.getState().getData().getLinearId())).findFirst().orElseThrow(null); + } +} +``` The `LinearPointer` contains the `linearId` of the `LinearState` being pointed to and two `resolve` methods. Resolving a `LinearPointer` returns a `StateAndRef` containing the latest version of the `LinearState` that the node calling `resolve` @@ -215,6 +282,8 @@ digits. Therefore, to be able to reason about amounts of tokens arithmetically, `TokenType` also defines a class and identifier which are used for serialisation. The `tokenClass` property is implemented by default. ```kotlin +// kotlin + @CordaSerializable @DoNotImplement open class TokenType( @@ -245,6 +314,60 @@ open class TokenType( open val tokenClass: Class<*> get() = javaClass } ``` +```java +// java - for reference only + +@CordaSerializable +@DoNotImplement +public class TokenType implements TokenizableAssetInfo { + /** + * All [TokenType]s must have a [tokenIdentifier], which is typically a 3-4 character, upper case alphabetic string. + * The [tokenIdentifier] is used in conjunction with the [tokenClass] to create an instance of a [TokenType], for + * example: (FiatCurrency, GBP), (DigitalCurrency, BTC), or (Stock, GOOG). For [TokenPointer]s this property will + * contain the linearId of the [EvolvableTokenType] which is pointed to. The linearId can be used to obtain the + * underlying [EvolvableTokenType] from the vault. + */ + private final String tokenIdentifier; + + /** + * The number of fractional digits allowable for this token type. Specifying "0" will only allow integer amounts of + * the token type. Specifying "2", allows two decimal places, much like most fiat currencies, and so on... + */ + private final Integer fractionDigits; + private final Class tokenClass = this.getClass(); + + public TokenType(String tokenIdentifier, Integer fractionDigits) { + this.tokenIdentifier = tokenIdentifier; + this.fractionDigits = fractionDigits; + } + + public String getTokenIdentifier() { + return tokenIdentifier; + } + + public Integer getFractionDigits() { + return fractionDigits; + } + + /** + * For use by the [Amount] class. There is no need to override this. + */ + @NotNull + @Override + public BigDecimal getDisplayTokenSize() { + return null; + } + + /** + * This property is used for when querying the vault for tokens. It allows us to construct an instance of a + * [TokenType] with a specified [tokenIdentifier], or for [EvolvableTokenType]s, as the [tokenIdentifier] is a + * linearId, which is opaque, the [tokenClass] provides a bit more context on what is being pointed to. + */ + public Class getTokenClass() { + return tokenClass; + } +} +``` `TokenType`s can be composed into a `NonFungibleToken` or a `FungibleToken` and they are almost always wrapped with an `IssuedTokenType` class. @@ -272,6 +395,8 @@ The `Amount` of an `IssuedTokenType` they had issued would be the corresponding security held in custody would be implied via some of information contained within the token type state. E.g. a stock symbol or ISIN. ```kotlin +// kotlin + @CordaSerializable data class IssuedTokenType( val issuer: Party, @@ -280,6 +405,23 @@ data class IssuedTokenType( // Omitted code. } ``` +```java +// java + +@CordaSerializable +public class IssuedTokenType extends TokenType { + private final Party issuer; + private final TokenType tokenType; + + public IssuedTokenType(Party issuer, TokenType tokenType) { + super(tokenType.getTokenIdentifier(), tokenType.getFractionDigits()); + this.issuer = issuer; + this.tokenType = tokenType; + } + + // Omitted code. +} +``` #### EvolvableTokenType `EvolvableTokenType`s _are_ state objects because the expectation is that they will evolve over time. Of course in-lining @@ -289,6 +431,8 @@ That's what `TokenPointer` is for (see below). This way, the token can evolve in (some amount of) the token type. ```kotlin +// kotlin + abstract class EvolvableTokenType : LinearState { abstract val maintainers: List @@ -304,6 +448,31 @@ abstract class EvolvableTokenType : LinearState { } } ``` +```java +// java + +abstract class EvolvableTokenType implements LinearState { + private final List maintainers; + private final Integer fractionDigits; + + public EvolvableTokenType(List maintainers, Integer fractionDigits) { + this.maintainers = maintainers; + this.fractionDigits = fractionDigits; + } + + // Defaults to the maintainer but can be overridden if necessary + @Override + public List getParticipants() { + return new ArrayList<>(maintainers); + } + + // For obtaining a pointer to this [EvolveableToken]. + public TokenPointer toPointer() { + final LinearPointer linearPointer = new LinearPointer<>(getLinearId(), EvolvableTokenType.class); + return new TokenPointer<>(linearPointer, fractionDigits); + } +} +``` It is expected that a specific set of `Party`s create and maintain `EvolvableTokenType`s. Note, that these `Party`s don't necessarily _have_ to be the issuer of the `EvolvableTokenType`. One of them probably _is_ the issuer of the token but @@ -322,11 +491,58 @@ the data within the `EvolvableTokenType`. This way, the `TokenType` can evolve i it, as the data is held in a separate state object. ```kotlin -data class TokenPointer( +// kotlin + +class TokenPointer( val pointer: LinearPointer, - override val displayTokenSize: BigDecimal -) : TokenType { - override fun toString(): String = "Pointer(${pointer.pointer.id}, ${pointer.type.canonicalName})" + fractionDigits: Int +) : TokenType(pointer.pointer.id.toString(), fractionDigits) { + /** + * The fully qualified class name for the [EvolvableTokenType] being pointed to. + */ + override val tokenClass: Class<*> get() = pointer.type + + override fun toString(): String = "TokenPointer($tokenClass, $tokenIdentifier)" +} +``` +```java +// java + +public class TokenPointer2 extends TokenType { + private final LinearPointer pointer; + private final int fractionDigits; + private final Class tokenClass; + + public TokenPointer2(LinearPointer pointer, Integer fractionDigits) { + super(Objects.requireNonNull(pointer.getPointer().getExternalId()), fractionDigits); + this.pointer = pointer; + this.fractionDigits = fractionDigits; + this.tokenClass = pointer.getType(); + } + + @NotNull + @Override + public Class getTokenClass() { + return tokenClass; + } + + public LinearPointer getPointer() { + return pointer; + } + + @Override + public int getFractionDigits() { + return fractionDigits; + } + + /** + * The fully qualified class name for the [EvolvableTokenType] being pointed to. + */ + @NotNull + @Override + public String toString() { + return "TokenPointer(" + tokenClass + getTokenIdentifier() + ")"; + } } ``` @@ -339,6 +555,8 @@ the `FungibleToken` represents an agreement between an issuer of the `IssuedToke In effect, the `FungibleToken` conveys a right for the holder to make a claim on the issuer for whatever the `TokenType` represents. ```kotlin +// kotlin + @BelongsToContract(FungibleTokenContract::class) open class FungibleToken( override val amount: Amount, @@ -348,6 +566,30 @@ open class FungibleToken( // Methods omitted. } ``` +```java +// java + +@BelongsToContract(FungibleTokenContract.class) +public class FungibleToken implements FungibleState, AbstractToken, QueryableState { + private final Amount amount; + private final AbstractParty holder; + private final SecureHash tokenTypeJarHash; + + public FungibleToken(Amount amount, AbstractParty holder) { + this.amount = amount; + this.holder = holder; + this.tokenTypeJarHash = TransactionUtilitiesKt.getAttachmentIdForGenericParam(amount.getToken().getTokenType()); + } + + @ConstructorForDeserialization + public FungibleToken(Amount amount, AbstractParty holder, SecureHash tokenTypeJarHash) { + this.amount = amount; + this.holder = holder; + this.tokenTypeJarHash = tokenTypeJarHash; + } + // Methods Omitted +} +``` `FungibleToken` is something that can be split and merged. It contains the following properties: @@ -378,6 +620,8 @@ between the issuer and holder. In effect, the `NonFungibleToken` conveys a right for whatever the `TokenType` represents. This is the equivalent of **ERC-721**. ```kotlin +// kotlin + @BelongsToContract(NonFungibleTokenContract::class) open class NonFungibleToken( val token: IssuedTokenType, @@ -388,6 +632,33 @@ open class NonFungibleToken( // Methods omitted. } ``` +```java +// java + +@BelongsToContract(NonFungibleTokenContract.class) +public class NonFungibleToken implements AbstractToken, QueryableState, LinearState { + private final IssuedTokenType token; + private final AbstractParty holder; + private final UniqueIdentifier linearId; + private final SecureHash tokenTypeJarHash; + + public NonFungibleToken(IssuedTokenType token, AbstractParty holder, UniqueIdentifier linearId) { + this.token = token; + this.holder = holder; + this.linearId = linearId; + this.tokenTypeJarHash = TransactionUtilitiesKt.getAttachmentIdForGenericParam(token.getTokenType()); + } + + @ConstructorForDeserialization + public NonFungibleToken(IssuedTokenType token, AbstractParty holder, UniqueIdentifier linearId, SecureHash tokenTypeJarHash) { + this.token = token; + this.holder = holder; + this.linearId = linearId; + this.tokenTypeJarHash = tokenTypeJarHash; + } + // Methods Omitted +} +``` The `linearId` property uniquely identifies this NonFungibleToken. @@ -410,6 +681,8 @@ within a `LinearState` which manages the workflow of the derivative contract. We can define example instances of a `TokenType`, in this case `FiatCurrency`—which is already included in the `money` module of the token SDK. ```kotlin +// kotlin + // A simple definition of FiatCurrency using java.util.Currency. class FiatCurrency { companion object { @@ -421,10 +694,23 @@ class FiatCurrency { } } ``` +```java +// java + +// A simple definition of FiatCurrency using java.util.Currency. +public class FiatCurrency { + public static TokenType getInstance(String currencyCode) { + Currency currency = Currency.getInstance(currencyCode); + return new TokenType(currency.getCurrencyCode(), currency.getDefaultFractionDigits()); + } +} +``` We can now use the above definition to create some `FungibleToken`s: ```kotlin +// kotlin + // £10. val tenPounds: Amount = 10.GBP // £10 issued by Alice, where ALICE is a Party object. @@ -435,10 +721,26 @@ val ownedTenPounds: FungibleToken = issuedTenPounds heldBy BOB val tenQuid = 10.GBP issuedBy ALICE heldBy BOB val oneMillionDollars = 1_000_000.USD issuedBy ZOE heldBy RICH ``` +```java +// java + +class CreateTokens { + Amount tenPounds = UtilitiesKt.GBP(10); + // £10 issued by Alice, where ALICE is a Party object. + Amount issuedTenPounds = AmountUtilitiesKt.issuedBy(tenPounds, ALICE); + // £10 issued by Alice and owned by Bob. + FungibleToken ownedTenPounds = TokenUtilitiesKt.heldBy(issuedTenPounds, BOB); + // Or all together with type inference. + FungibleToken tenQuid = TokenUtilitiesKt.heldBy(AmountUtilitiesKt.issuedBy(UtilitiesKt.GBP(10), ALICE), BOB); + FungibleToken oneMillionDollars = TokenUtilitiesKt.heldBy(AmountUtilitiesKt.issuedBy(UtilitiesKt.USD(1000000), ZOE), RICH); +} +``` The fungible tokens can be used to create transactions: ```kotlin +// kotlin + // Couple the dollars with an issuer, in this case ZOE. val zoeDollar: IssuedTokenType = USD issuedBy ZOE // Use "X of Y" syntax to create amounts of some IssuedTokenType. @@ -453,6 +755,21 @@ val builder = TransactionBuilder(honestNotary).apply { addCommand(IssueTokenCommand(zoeDollar), listOf(zoeKey)) } ``` +```java +// java + +class CreateTokens { + // Couple the dollars with an issuer, in this case ZOE. + IssuedTokenType zoeDollar = AmountUtilitiesKt.issuedBy(UtilitiesKt.getUSD(), ZOE); + // Use "X of Y" syntax to create amounts of some IssuedTokenType. + FungibleToken millionDollars = TokenUtilitiesKt.heldBy(AmountUtilitiesKt.of(1000000, zoeDollar), BOB); + // A transaction to issue a million Zoe Dollars. + TransactionBuilder builder = new TransactionBuilder(honestNotary) + .addOutputState(millionDollars) + .addCommand(new IssueTokenCommand(zoeDollar, Collections.emptyList()), + ImmutableList.of(zoeDollar.getIssuer().getOwningKey())); +} +``` #### Stock @@ -468,6 +785,8 @@ would subscribe to updates to ensure they have the most up-to-date version of th The MEGA CORP example in code would be as follows: ```kotlin +// kotlin + // Example stock token. This could be used for any stock type. @BelongsToContract(StockContract::class) data class Stock( @@ -481,6 +800,7 @@ data class Stock( fun dividend(amount: TokenType): Stock } +// To demonstrate using above definition // Define MEGA CORP Stock reference data then issue it on the ledger (not shown). val megaCorpStock = Stock( symbol = "MEGA", @@ -497,12 +817,67 @@ val issuedMegaCorpStock = stockPointer issuedBy CUSTODIAN val stockTokensForAlice = 1_000 of issuedMegaCorpStock heldBy ALICE // MEGA CORP announces a dividend and commits the updated token type definition to ledger. -megaCorpStock.dividend(10.POUNDS) +val updatedStock = megaCorpStock.dividend(10.POUNDS) -// MEGA CORP distributes the updated token type definition to those who require it. +// MEGA CORP updates the token type definition using UpdateEvolvableTokenFlow() +// with the option to distribute to groups + +// Resolving the pointer now gives us the updated token type definition. +val resolved = stockPointer.pointer.resolve(services) +``` +```java +// java -// Resolving the pointer gives us the updated token type definition. -val resolved = stockPointer.resolve(services) +// Example stock token. This could be used for any stock type. +@BelongsToContract(StockContract.class) +public class Stock extends EvolvableTokenType { + private final String symbol; + private final String name; + private final BigDecimal displayTokenSize; + private final List maintainers; + private final UniqueIdentifier linearId; + + public Stock(String symbol, String name, BigDecimal displayTokenSize, List maintainers) { + this.symbol = symbol; + this.name = name; + this.displayTokenSize = displayTokenSize; + this.maintainers = maintainers; + this.linearId = new UniqueIdentifier(); + } + // Getters omitted. + + // Things one can do with stock + public Stock dividend(Amount amount) { + // logic in here + } +} + +class Demonstrate { + // To demonstrate using the above definition + // Define MEGA CORP Stock reference data then issue it on the ledger (not shown) + Stock megaCorpStock = new Stock( + "MEGA", + "MEGA CORP", + BigDecimal.ONE, + ImmutableList.of(REF_DATA_MAINTAINER) + ); + + // Create a pointer to the MEGA CORP stock. + TokenPointer stockPointer = megaCorpStock.toPointer(Stock.class); + // Create an issued token type for the MEGA CORP stock. + IssuedTokenType issuedMegaCorpStock = AmountUtilitiesKt.issuedBy(stockPointer, CUSTODIAN); + // Create a FungibleToken for some amount of MEGA CORP stock. + FungibleToken stockTokensForAlice = TokenUtilitiesKt.heldBy(AmountUtilitiesKt.of(1000, issuedMegaCorpStock), ALICE); + + // MEGA CORP announces a dividend and commits the updated token type definition to ledger. + Stock updatedStock = megaCorpStock.dividend(Currencies.POUNDS(10)); + + // MEGA CORP updates the token type definition using UpdateEvolvableTokenFlow() + // with the option to distribute to groups + + // Resolving the pointer gives us the updated token type definition + TokenType resolved = stockPointer.getPointer().resolve(getServiceHub()); +} ``` Here, the `EvolvableTokenType` is linked to the `FungibleToken` via the `TokenPointer`. The pointer includes the `linearId` diff --git a/design/token-selection.md b/design/token-selection.md index 2bc47cd7..4933f33c 100644 --- a/design/token-selection.md +++ b/design/token-selection.md @@ -58,12 +58,36 @@ fully resumed all existing flow checkpoints, a double spend error may occur. For the initial design, we will keep it simple and essentially have a map of maps. SoftLocking will be done by doing an atomic C-A-S (compare-and-swap) on a boolean. +```kotlin +// kotlin + private val cache: ConcurrentMap = ConcurrentHashMap() class TokenIndex(@Kdoc("Any because we support UUID and publickey) val owner: Any, val tokenClazz: Class<*>, val tokenIdentifier: String) class TokenBucket() : ConcurrentMap>, Boolean> - +``` +```java +// java + + private final ConcurrentMap cache = new ConcurrentHashMap<>(); + + class TokenIndex { + private final Object Owner; + private final Class tokenClazz; + private final String tokenIdentifier; + + public TokenIndex(Object owner, Class tokenClazz, String tokenIdentifier) { + Owner = owner; + this.tokenClazz = tokenClazz; + this.tokenIdentifier = tokenIdentifier; + } + } + + class TokenBucket extends ConcurrentMap>, boolean> { + // methods ommitted + } +``` #### Node Startup 1. On startup, load all states within the vault diff --git a/docs/DvPTutorial.md b/docs/DvPTutorial.md index c08a7ed4..af91c5d7 100644 --- a/docs/DvPTutorial.md +++ b/docs/DvPTutorial.md @@ -34,6 +34,8 @@ First let's define `House` state using `EvolvableTokenType`. We add `address` an we expect to change over time. In our use case house valuation can change. ```kotlin +// kotlin + // A token representing a house on ledger. @BelongsToContract(HouseContract::class) data class House( @@ -44,11 +46,34 @@ data class House( override val linearId: UniqueIdentifier ) : EvolvableTokenType() ``` +```java +// java + +@BelongsToContract(HouseContract.class) +public class House extends EvolvableTokenType { + private final String address; + private final Amount valuation; + private final List maintainers; + private final int fractionDigits = 0; + private final UniqueIdentifier linearId; + + public House(String address, Amount valuation, List maintainers) { + this.address = address; + this.valuation = valuation; + this.maintainers = maintainers; + this.linearId = new UniqueIdentifier(); + } + + // Getters omitted. +} +``` We should make sure that when transferring house we don't change the address in a state, also valuation should be greater than zero (both when we create and update the state). Let's write some additional checks in `HouseContract`: ```kotlin +// kotlin + // House contract that adds additional checks on create and update of the token. class HouseContract : EvolvableTokenContract(), Contract { @@ -68,6 +93,34 @@ class HouseContract : EvolvableTokenContract(), Contract { } } ``` +```java +// java + +// House contract that adds additional checks on create and update of the token. +public class HouseContract extends EvolvableTokenContract implements Contract { + + @Override + public void additionalCreateChecks(@NotNull LedgerTransaction tx) { + // Not much to do for this example token. + House newHouse = (House) tx.getOutputStates().get(0); + requireThat(req -> { + req.using("Valuation must be greater than zero.", newHouse.getValuation().compareTo(Amount.zero(newHouse.getValuation().getToken())) > 0); + return null; + }); + } + + @Override + public void additionalUpdateChecks(@NotNull LedgerTransaction tx) { + House oldHouse = (House) tx.getInputStates().get(0); + House newHouse = (House) tx.getOutputStates().get(0); + requireThat(req -> { + req.using("The address cannot change.", oldHouse.getAddress().equals(newHouse.getAddress())); + req.using("Valuation must be greater than zero.", newHouse.getValuation().compareTo(Amount.zero(newHouse.getValuation().getToken())) > 0); + return null; + }); + } +} +``` **Note** In later Corda releases your evolvable states won't have to implement `Contract` only `EvolvableTokenContract`. @@ -76,12 +129,25 @@ class HouseContract : EvolvableTokenContract(), Contract { Let's take a look how to create and issue `EvolvableTokenType` onto the ledger. ```kotlin +// kotlin + // From within the flow. val house: House = House(...) val notary: Party = getPreferredNotary(serviceHub) // Or provide notary party using your favourite function from NotaryUtilities. // We need to create the evolvable token first. subFlow(CreateEvolvableTokens(house withNotary notary)) ``` +```java +// java + + // From within the flow. + House house = new House(...); + // Or provide notary party using your favourite function from NotaryUtilities. + Party notary = NotaryUtilitiesKt.getPreferredNotary(getServiceHub(), NotaryUtilitiesKt.firstNotary()); + // We need to create the evolvable token first. + subFlow(new CreateEvolvableTokens(new TransactionState<>(house, notary))); + +``` `CreateEvolvableTokens` flow creates the evolvable state and shares it with maintainers and potential observers. To issue a token that references the `EvolvableTokenType` we have to call one of the flows from the family of `IssueTokens` flows. @@ -93,6 +159,8 @@ This way, the token can evolve independently to which party currently owns (some Let's issue `NonFungibleToken` referencing `House` held by Alice party. ```kotlin +// kotlin + val aliceParty: Party = ... val issuerParty: Party = ourIdentity val housePtr = house.toPointer() @@ -100,6 +168,16 @@ Let's issue `NonFungibleToken` referencing `House` held by Alice party. val houseToken: NonFungibleToken = housePtr issuedBy issuerParty heldBy aliceParty subFlow(ConfidentialIssueTokens(listOf(houseToken))) ``` +```java +// java + + Party aliceParty = ...; + Party issuerParty = getOurIdentity(); + TokenPointer housePtr = house.toPointer(House.class); + // Create NonFungibleToken referencing house with Alice party as an owner. + NonFungibleToken houseToken = TokenUtilities.heldBy(AmountUtilitiesKt.issuedBy(housePtr, issuerParty), aliceParty); + subFlow(new ConfidentialIssueTokens(ImmutableList.of(houseToken))); +``` There are some flows from issue tokens family that let you issue a token onto the ledger. In this case we used `ConfidentialIssueTokens` this flow generates confidential identities first to use them in the transaction instead of well known @@ -109,10 +187,19 @@ For testing delivery versus payment it would be great to have some money tokens fungible `GBP` tokens is straightforward: ```kotlin + // kotlin + // Let's print some money! val otherParty: Party = ... subFlow(IssueTokens(listOf(1_000_00.GBP issuedBy issuerParty heldBy otherParty))) // Initiating version of IssueFlow ``` +```java + // java + + // Let's print some money! + Party otherParty = ...; + subFlow(new IssueTokens(ImmutableList.of(TokenUtilitiesKt.heldBy(AmountUtilitiesKt.issuedBy(UtilitiesKt.GBP(100000), issuerParty), otherParty)); +``` **Note** There are different versions of `IssueFlow` inlined (that require you to pass in flow sessions) and initiating (callable via RPC), it's worth checking API documentation. @@ -122,6 +209,8 @@ fungible `GBP` tokens is straightforward: After the initial setup phase let's write a simple flow that involves two parties that want to swap a house for some `GBP`. ```kotlin +// kotlin + @StartableByRPC @InitiatingFlow class SellHouseFlow(val house: House, val newHolder: Party) : FlowLogic() { @@ -131,17 +220,53 @@ After the initial setup phase let's write a simple flow that involves two partie } } ``` +```java +// java + + @StartableByRPC + @InitiatingFlow + public class SellHouseFlow extends FlowLogic { + private final House house; + private final Party newHolder; + + public SellHouseFlow(House house, Party newHolder) { + this.house = house; + this.newHolder = newHolder; + } + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + // TODO("Implement delivery verses payment logic.") + return null; + } + } +``` Flow takes house to sell and new owner party. Let's define price notification to be sent to the new owner: ```kotlin +// kotlin + @CordaSerializable data class PriceNotification(val amount: Amount) ``` +```java +// java + + @CordaSerializable + class PriceNotification { + private final Amount amount; + public PriceNotification(Amount amount) { this.amount = amount; } + public Amount getAmount() { return amount; } + } +``` Then construct transaction builder with house move to the new holder: ```kotlin +// kotlin + @Suspendable override fun call(): SignedTransaction { val housePtr = house.toPointer() @@ -151,10 +276,25 @@ Then construct transaction builder with house move to the new holder: ... } ``` +```java +// java + + @Suspendable + @Override + public SignedTransaction call() throws FlowException { + TokenPointer housePtr = house.toPointer(House.class); + // We can specify preferred notary in cordapp config file, otherwise the first one from network parameters is chosen. + TransactionBuilder txBuilder = new TransactionBuilder(NotaryUtilitiesKt.getPreferredNotary(getServiceHub(), NotaryUtilitiesKt.firstNotary())); + MoveTokensUtilitiesKt.addMoveNonFungibleTokens(txBuilder, getServiceHub(), housePtr, newHolder); + ... + } +``` Time to contact the counterparty to collect `GBP` states in exchange for house: ```kotlin +// kotlin + ... // Initiate new flow session. If this flow is supposed to be called as inline flow, then session should have been already passed. val session = initiateFlow(newHolder) @@ -168,29 +308,65 @@ Time to contact the counterparty to collect `GBP` states in exchange for house: // usually we would like to implement it as part of the contract ... ``` +```java +// java + + ... + // Initiate new flow session. If this flow is supposed to be called as inline flow, then session should have been already passed. + FlowSession session = initiateFlow(newHolder); + // Ask for input stateAndRefs - send notification with the amount to exchange. + session.send(new PriceNotification(house.getValuation())); + // Receive GBP states back. + List> inputs = subFlow(new ReceiveStateAndRefFlow(session)); + // Receive outputs. + List outputs = session.receive(List.class).unwrap(it -> it); + // For the future we could add some checks for inputs and outputs - that they sum up to house valuation, + // usually we would like to implement it as part of the contract + ... +``` Add move of `GBP` tokens to the transaction builder: ```kotlin +// kotlin + ... addMoveTokens(txBuilder, inputs, outputs) ... ``` +```java +// java + + ... + MoveTokensUtilitiesKt.addMoveTokens(txBuilder, inputs, outputs); + ... +``` It can happen that input states to the transaction have confidential identities as participants, we should synchronise any identities before the final phase: ```kotlin +// kotlin + ... subFlow(SyncKeyMappingFlow(session, txBuilder.toWireTransaction(serviceHub))) ... ``` +```java +// java + + ... + subFlow(new SyncKeyMappingFlow(session, txBuilder.toWireTransaction(getServiceHub()))); + ... +``` ## Signing and finalising transaction The last step is signing the transaction by all parties involved: ```kotlin +// kotlin + ... // Because states on the transaction can have confidential identities on them, we need to sign them with corresponding keys. val ourSigningKeys = txBuilder.toLedgerTransaction(serviceHub).ourSigningKeys(serviceHub) @@ -199,6 +375,16 @@ The last step is signing the transaction by all parties involved: val stx = subFlow(CollectSignaturesFlow(initialStx, listOf(session), ourSigningKeys)) ... ``` +```java +// java + + ... + // Because states on the transaction can have confidential identities on them, we need to sign them with corresponding keys. + List ourSigningKeys = FlowUtilitiesKt.ourSigningKeys(txBuilder.toLedgerTransaction(getServiceHub()), getServiceHub()); + SignedTransaction initialStx = getServiceHub().signInitialTransaction(txBuilder, ourSigningKeys); + SignedTransaction stx = subFlow(new CollectSignaturesFlow(initialStx, ImmutableList.of(session), ourSigningKeys)); + ... +``` Evolvable tokens are special, because they can change over time. To keep all the parties interested notified about the update distribution lists are used. This is a tactical solution that will be changed in the future for more robust design. @@ -206,6 +392,7 @@ Distribution list is a list of identities that should receive updates, it's usua For this mechanism to behave correctly we need to add special `UpdateDistributionListFlow` subflow: ```kotlin +// kotlin ... // Update distribution list. subFlow(UpdateDistributionListFlow(stx)) @@ -213,12 +400,24 @@ For this mechanism to behave correctly we need to add special `UpdateDistributio return subFlow(ObserverAwareFinalityFlow(stx, listOf(session))) } ``` +```java +// java + + ... + // Update the distribution list. + subFlow(new UpdateDistributionListFlow(stx)); + // Finalise transaction! If you want to have observers notified, you can pass optional observers sessions. + return subFlow(new ObserverAwareFinalityFlow(stx, ImmutableList.of(session))); + ... +``` ## Write responder flow The responder flow is pretty straightforward to write calling corresponding flow handlers in order: ```kotlin +// kotlin + @InitiatedBy(SellHouseFlow::class) class SellHouseFlowHandler(val otherSession: FlowSession) : FlowLogic() { @Suspendable @@ -245,6 +444,51 @@ The responder flow is pretty straightforward to write calling corresponding flow } } ``` +```java +// java + +@InitiatedBy(SellHouseFlow) +public class SellHouseFlowHandler extends FlowLogic { + private final FlowSession otherSession; + + public SellHouseFlowHandler(FlowSession otherSession) { + this.otherSession = otherSession; + } + + @Suspendable + @Override + public Void call() throws FlowException { + // Receive notification with house price. + TestFlow.PriceNotification priceNotification = otherSession.receive(TestFlow.PriceNotification.class).unwrap(it -> it); + // Generate fresh key, possible change outputs will belong to this key. + AnonymousParty changeHolder = getServiceHub().getKeyManagementService().freshKeyAndCert(getOurIdentityAndCert(), false).getParty().anonymise(); + // Choose state and refs to send back. + DatabaseTokenSelection tokenSelection = (new DatabaseSelectionConfig().toSelector(getServiceHub())); + Pair>, List> selectedTokens = tokenSelection.generateMove( + Collections.singletonList(new Pair>(otherSession.getCounterparty(), priceNotification.getAmount())), + changeHolder, + new TokenQueryBy(null, it -> { + return true; + }, null), + getRunId().getUuid() + ); + List> inputs = selectedTokens.getFirst(); + List outputs = selectedTokens.getSecond(); + subFlow(new SendStateAndRefFlow(otherSession, inputs)); + otherSession.send(outputs); + subFlow(new SyncKeyMappingFlowHandler(otherSession)); + subFlow(new SignTransactionFlow(otherSession) { + @Override + protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException { + // We should perform some basic sanity checks before signing the transaction. This step was omitted for simplicity. + } + }); + subFlow(new ObserverAwareFinalityFlowHandler(otherSession)); + + return null; + } +} +``` Notice the ```DatabaseTokenSelection(serviceHub).generateMove(...)``` used for chosing tokens that cover the required amount. @@ -258,10 +502,27 @@ updating the already issued house valuation and all interested parties will get It's sufficient for the issuer (or house state maintainer) to update that token by simply running: ```kotlin +// kotlin + // Update that evolvable state on issuer node. val oldHouse: StateAndRef = ... // Query for the state. val newHouse: House = oldHouse.state.data.copy(valuation = 800_000L.GBP) - subFlow(UpdateEvolvableToken(oldStateAndRef = old, newState = new)) + subFlow(UpdateEvolvableToken(oldStateAndRef = oldHouse, newState = newHouse)) +``` +```java +// java + + // Update that evolvable state on issuer node. + StateAndRef oldHouse = ...; // Query for the state. + House newHouse = new House( + oldHouse.getState().getData().getAddress(), + UtilitiesKt.GBP(800000), + oldHouse.getState().getData().getMaintainers(), + oldHouse.getState().getData().getFractionDigits(), + oldHouse.getState().getData().getLinearId() + ); + subFlow(new UpdateEvolvableToken(oldHouse, newHouse)); + ``` diff --git a/docs/IWantTo.md b/docs/IWantTo.md index c2648d20..35639a61 100644 --- a/docs/IWantTo.md +++ b/docs/IWantTo.md @@ -6,10 +6,19 @@ Two `TokenType` helpers already exist in the token SDK, `FiatCurrency` and `DigitalCurrency`. There are easy to use utilities for both, for example: ```kotlin +// kotlin + val pounds: TokenType = GBP val euros: TokenType = EUR val bitcoin: TokenType = BTC ``` +```java +// java + + TokenType pounds = FiatCurrency.Companion.getInstance("GBP"); + TokenType euros = FiatCurrency.Companion.getInstance("EUR"); + TokenType bitcoin = FiatCurrency.Companion.getInstance("BTC"); +``` Creating your own tokens is easy; just create an instance of `TokenType` class. You will need to specify a `tokenIdentifier` property and how many `fractionDigits` @@ -22,8 +31,15 @@ amounts of this token can have. E.g. You can also add a `toString` override, if you like. ```kotlin +// kotlin + val myTokenType: TokenType = TokenType(tokenIdentifier = "TEST", fractionDigits = 2) ``` +```java +// java + + TokenType myTokenType = new TokenType("TEST", 2); +``` The `tokenIdentifier` is used along with the `tokenClass` property (defined in `TokenType`) when serializing token types. Two properties are required, @@ -35,6 +51,8 @@ instances of one `tokenClass`, each with their own `tokenIdentifier`. Create an `IssuedTokenType` as follows ```kotlin +// kotlin + // With your own instance of token type. val issuer: Party = ... val myTokenType = TokenType("MyToken", 2) @@ -42,7 +60,20 @@ Create an `IssuedTokenType` as follows // Or with the built in tokens. val issuedGbp: IssuedTokenType = GBP issuedBy issuer - val issuedGbp: IssuedTokenType = BTC issuedBy issuer + val issuedBtc: IssuedTokenType = BTC issuedBy issuer +``` +```java +// java + + // With your own instance of token type. + Party issuer = ...; + TokenType myTokenType = new TokenType("MyToken", 2); + IssuedTokenType issuedTokenType = AmountUtilitiesKt.issuedBy(myTokenType, issuer); + + // Or with the built in tokens. + IssuedTokenType issuedGbp = AmountUtilitiesKt.issuedBy(FiatCurrency.Companion.getInstance("GBP"), issuer); + IssuedTokenType issuedBtc = AmountUtilitiesKt.issuedBy(FiatCurrency.Companion.getInstance("BTC"), issuer); + ``` The issuing party must be a `Party` as opposed to an `AbstractParty` or @@ -56,23 +87,47 @@ Once you have an `IssuedTokenType` you can optionally create some amount of it using the `of` syntax. For example: ```kotlin +// kotlin + val issuer: Party = ... val myTokenType = TokenType("MyToken", 2) val myIssuedTokenType: IssuedTokenType = myTokenType issuedBy issuer val tenOfMyIssuedTokenType = 10 of myIssuedTokenType ``` +```java +// java + + Party issuer = ...; + TokenType myTokenType = new TokenType("MyToken", 2); + IssuedTokenType myIssuedTokenType = AmountUtilitiesKt.issuedBy(myTokenType, issuer); + Amount tenOfMyIssuedTokenType = AmountUtilitiesKt.amount(10, myIssuedTokenType); +``` Or: ```kotlin +// kotlin + val tenPounds: Amount = 10 of GBP issuedBy issuer ``` +```java +// java + + Amount tenPounds = AmountUtilitiesKt.amount(10, AmountUtilitiesKt.issuedBy(FiatCurrency.Companion.getInstance("GBP"), issuer)); +``` Or: ```kotlin +// kotlin + val tenPounds = 10.GBP issuedBy issuer ``` +```java +// java + + Amount tenPounds = AmountUtilitiesKt.issuedBy(UtilitiesKt.GBP(10), issuer); +``` If you do not need to create amounts of your token because it is always intended to be issued as a `NonFungibleToken` then you don't have to create @@ -85,6 +140,8 @@ token or fungible token, we need to specify which party the proposed holder is. This can be done using the `heldBy` syntax: ```kotlin +// kotlin + val issuer: Party = ... val holder: Party = ... @@ -97,6 +154,21 @@ is. This can be done using the `heldBy` syntax: // Adding a holder to a token type, creates a non-fungible token. val nonFungibleToken: NonFungibleToken = myIssuedTokenType heldBy holder ``` +```java +// java + + Party issuer = ...; + Party holder = ...; + + TokenType myTokenType = new TokenType("MyToken", 2); + // Note: in java, Amounts and TokenTypes can also be instatiated with their constructors in addition to the provided utilities + IssuedTokenType myIssuedTokenType = new IssuedTokenType(issuer, myTokenType); + Amount tenOfMyIssuedTokenType = new Amount<>(10, myIssuedTokenType); + + // Adding a holder to an amount of a token type, creates a fungible token + FungibleToken fungibleToken = TokenUtilitiesKt.heldBy(tenOfMyIssuedTokenType, holder); + NonFungibleToken nonFungibleToken = TokenUtilities.heldBy(myIssuedTokenType, holder); +``` Once you have a `FungibleToken` or a `NonFungibleToken`, you can then go and issue that token on ledger. @@ -145,19 +217,69 @@ Confidential versions additionally request that the recipients generate new keys **Initiating** ```kotlin +// kotlin + // As in previous examples val fungibleToken: FungibleToken = ... val nonFungibleToken: NonFungibleToken = ... // Start flows via RPC or as a subFlow (it starts a new session with a holder of the token!) // All of the below flows can take a list of observer parties. + +// Fungible +IssueTokens(listOf(fungibleToken)) +IssueTokens(listOf(10 of myTokenType issuedBy issuer heldBy holder)) +IssueTokens(listOf(10 of myIssuedTokenType heldBy holder)) + +// Nonfungible +IssueTokens(listOf(nonFungibleToken)) +IssueTokens(listOf(myTokenType issuedBy issuer heldBy holder)) +IssueTokens(listOf(myIssuedTokenType heldBy holder)) +``` +```java +// java + +// As in previous examples +FungibleToken fungibleToken = ...; +NonFungibleToken nonFungibleToken = ...; +// Start flows via RPC or as a subFlow (it starts a new session with a holder of the token!) +// All of the below flows can take a list of observer parties. + + // Fungible -IssueTokens(fungibleToken) -IssueTokens(10 of myTokenType, issuer, holder) -IssueTokens(10 of myIssuedTokenType, holder) +new IssueTokens(Collections.singletonList(fungibleToken)); +new IssueTokens(Collections.singletonList( + TokenUtilitiesKt.heldBy( + AmountUtilitiesKt.issuedBy( + AmountUtilitiesKt.amount(10, myTokenType), + issuer + ), + holder + ) +)); +new IssueTokens(Collections.singletonList( + TokenUtilitiesKt.heldBy( + AmountUtilitiesKt.amount(10, myIssuedTokenType), + holder + ) +)); + // Nonfungible -IssueTokens(nonFungibleToken) -IssueTokens(myTokenType, issuer, holder) -IssueTokens(myIssuedTokenType, holder) +new IssueTokens(Collections.singletonList(nonFungibleToken)); +new IssueTokens(Collections.singletonList( + TokenUtilities.heldBy( + AmountUtilitiesKt.issuedBy( + myTokenType, + issuer + ), + holder + ) +)); +new IssueTokens(Collections.singletonList( + TokenUtilities.heldBy( + myIssuedTokenType, + holder + ) +)); ``` There are many other constructor overloads for initiating `IssueTokens` it's worth investigating the class itself. @@ -169,15 +291,29 @@ _Conidential version:_ `ConfidentialIssueTokens`, _responder_: `ConfidentialIssu **Inline** ```kotlin +// kotlin + // We need to pass in counterparties sessions. val holderSession = initateFlow(holder) ... // All of the below flows can take a list of observer sessions. // Fungible -subFlow(IssueTokensFlow(fungibleToken, listOf(holderSession))) +subFlow(IssueTokensFlow(listOf(fungibleToken), listOf(holderSession))) // NonFungible -subFlow(IssueTokensFlow(nonFungibleToken, listOf(holderSession))) +subFlow(IssueTokensFlow(listOf(nonFungibleToken), listOf(holderSession))) ``` +```java +// java + +// We need to pass in counterparties sessions. +FlowSession holderSession = initiateFlow(holder); +... +// All of the below flows can take a list of observer sessions. +// Fungible +subFlow(new IssueTokensFlow(Collections.singletonList(fungibleToken), ImmutableList.of(holderSession))); +// NonFungible +subFlow(new IssueTokensFlow(Collections.singletonList(nonFungibleToken), ImmutableList.of(holderSession))); +``` There are other constructor overloads worth investigating. @@ -186,14 +322,29 @@ _Responder flow:_ `IssueTokensFlowHandler` **Confidential inline** ```kotlin +// kotlin + // We need to pass in counterparties sessions. val holderSession = initateFlow(holder) ... // All of the below flows can take a list of observer sessions. // Fungible -subFlow(ConfidentialIssueTokensFlow(fungibleToken, listOf(holderSession))) +subFlow(ConfidentialIssueTokensFlow(listOf(fungibleToken), listOf(holderSession))) +// NonFungible +subFlow(ConfidentialIssueTokensFlow(listOf(nonFungibleToken), listOf(holderSession))) +``` +```java +// java + +// We need to pass in counterparties sessions. +FlowSession holderSession = initiateFlow(holder); +... +// All of the below flows can take a list of observer sessions. +// Fungible +subFlow(new ConfidentialIssueTokensFlow(Collections.singletonList(fungibleToken), ImmutableList.of(holderSession))); // NonFungible -subFlow(ConfidentialIssueTokensFlow(nonFungibleToken, listOf(holderSession))) +subFlow(new ConfidentialIssueTokensFlow(Collections.singletonList(nonFungibleToken), ImmutableList.of(holderSession))); + ``` _Responder flow:_ `ConfidentialIssueTokensFlowHandler` @@ -221,9 +372,12 @@ As usual you can provide additional observers parties/sessions for finalization **Initiating** ```kotlin +// kotlin + val holder: Party = ... val otherHolder: Party = ... val issuer: Party = ... + // Move amount of token to the new holder MoveFungibleTokens(100 of myTokenType, holder) // Move different amounts of token to multiple holders @@ -236,6 +390,33 @@ MoveFungibleTokens( queryCriteria = tokenAmountWithIssuerCriteria(myTokenType, issuer) ) ``` +```java +// java + +Party holder = ...; +Party otherHolder = ...; +Party issuer = ...; + +// Move amount of token to the new holder +new MoveFungibleTokens( + AmountUtilitiesKt.amount(10, myTokenType), + holder +); +// Move different amounts of token to multiple holders +Amount amountOne = AmountUtilitiesKt.amount(13, myTokenType); +Amount amountTwo = AmountUtilitiesKt.amount(44, myTokenType); +new MoveFungibleTokens(ImmutableList.of( + new PartyAndAmount<>(holder, amountOne), + new PartyAndAmount<>(otherHolder, amountTwo) +)); +// Move amount of token issued by particular issuer to the new holder - this is an example of using optional queryCriteria +// parameter. +new MoveFungibleTokens( + new PartyAndAmount(holder, amountOne), + Collections.emptyList(), + SelectionUtilitiesKt.tokenAmountWithIssuerCriteria(myTokenType, issuer) +); +``` _Responder flow:_ `MoveFungibleTokensHandler` @@ -244,6 +425,8 @@ _Confidential version:_ `ConfidentialMoveFungibleTokens`, _responder_: `Confiden **Inline** ```kotlin +// kotlin + // We need to pass in counterparties sessions. val holderSession = initateFlow(holder) val otherHolderSession = initateFlow(otherHolder) @@ -261,6 +444,32 @@ subFlow(MoveFungibleTokensFlow( observers = emptyList() )) ``` +```java +// java + +// We need to pass in counterparties sessions. +FlowSession holderSession = initiateFlow(holder); +FlowSession otherHolderSession = initiateFlow(otherHolder); +... +// All of the below flows can take a list of observer sessions. +// Construct many moves in one transaction. +Amount amountOne = AmountUtilitiesKt.amount(13, myTokenType); +Amount amountTwo = AmountUtilitiesKt.amount(44, myTokenType); +subFlow(new MoveFungibleTokensFlow( + ImmutableList.of( + new PartyAndAmount(holder, amountOne), + new PartyAndAmount(otherHolder, amountTwo)), + ImmutableList.of(holderSession, otherHolderSession) +)); +// Move only tokens issued by particular issuer. +Amount amount = new Amount<>(5, myTokenType); +subFlow(new MoveFungibleTokensFlow( + new PartyAndAmount(holder, amount), + SelectionUtilitiesKt.tokenAmountWithIssuerCriteria(myTokenType, issuer), + ImmutableList.of(holderSession, otherHolderSession), + Collections.emptyList() +)); +``` _Responder flow:_ `MoveTokensFlowHandler` @@ -271,11 +480,21 @@ _Confidential version:_ `ConfidentialMoveFungibleTokensFlow`, _responder:_ `Conf **Initiating** ```kotlin +// kotlin + val holder: Party = ... ... // Move non fungible token to the new holder MoveNonFungibleTokens(PartyAndToken(holder, myTokenType)) ``` +```java +// java + +Party holder = ... +... +// Move non fungible token to the new holder +new MoveNonFungibleTokens(new PartyAndToken(holder, myTokenType)); +``` Similar to previous examples you can provide `queryCriteria` and list of observer parties. @@ -286,10 +505,23 @@ _Confidential version:_ `ConfidentialMoveNonFungibleTokens`, _responder_: `Confi **Inline** ```kotlin +// kotlin + val holderSession = initateFlow(holder) val observerSession = initatieFlow(observer) subFlow(MoveNonFungibleTokensFlow(PartyAndToken(holder, myTokenType), listOf(holderSession), listOf(observerSession))) ``` +```java +// java + +FlowSession holderSession = initiateFlow(holder); +FlowSession observerSession = initiateFlow(observer); +subFlow(new ConfidentialMoveNonFungibleTokensFlow( + new PartyAndToken(holder, myTokenType), + ImmutableList.of(holderSession), + ImmutableList.of(observerSession) +)); +``` _Responder flow:_ `MoveTokensFlowHandler` @@ -323,6 +555,8 @@ Selection is performed using the same utilities used in `MoveFungibleTokens`. **Initiating** ```kotlin +// kotlin + val amountToRedeem = 10.GBP val issuerParty: Party = ... val observerParty: Party = ... @@ -330,6 +564,20 @@ val observerParty: Party = ... // It is also possible to provide custom query criteria for token selection. RedeemFungibleTokens(amount = amountToRedeem, issuer = issuerParty, observers = listOf(observerParty)) ``` +```java +// java + +Party issuerParty = ...; +Party observerParty = ...; +Amount amountToRedeem = UtilitiesKt.GBP(10); + +// It is also possible to provide custom query criteria for token selection. +new RedeemFungibleTokens( + amountToRedeem, + issuerParty, + ImmutableList.of(observerParty) +); +``` _Responder flow:_ `RedeemFungibleTokensHandler` @@ -338,6 +586,8 @@ _Confidential version:_ `ConfidentialRedeemFungibleTokens`, _responder_: `Confid **Inline** ```kotlin +// kotlin + val issuerSession = initateFlow(issuer) val observerSession = initatieFlow(observer) val changeHolder: AbstractParty = ... // It can be either confidential identity belonging to tokens holder or well known identity of holder @@ -349,6 +599,21 @@ subFlow(RedeemFungibleTokensFlow( observerSessions = listOf(observerSession) )) ``` +```java +// java + +FlowSession issuerSession = initiateFlow(issuer); +FlowSession observerSession = initiateFlow(observer); +AbstractParty changeHolder = ...; // It can be either confidential identity belonging to tokens holder or well known identity of holder +// It is also possible to provide custom query criteria for token selection. + +subFlow(new RedeemFungibleTokensFlow( + UtilitiesKt.GBP(1000), + issuerSession, + changeHolder, + ImmutableList.of(observerSession) +)); +``` _Responder flow:_ `RedeemTokensFlowHandler` @@ -361,22 +626,43 @@ _Confidential version:_ `ConfidentialRedeemFungibleTokensFlow`, **Initiating** ```kotlin +// kotlin + val myTokenType: TokenType = ... val issuerParty: Party = ... val observerParty: Party = ... RedeemNonFungibleTokens(myTokenType, issuerParty, listOf(observerParty)) ``` +```java +// java + +TokenType myTokenType = ...; +Party issuerParty = ...; +Party observerParty = ...; + +new RedeemNonFungibleTokens(myTokenType, issuerParty, ImmutableList.of(observerParty)); +``` _Responder flow:_ `RedeemNonFungibleTokensHandler` **Inline** ```kotlin +// kotlin + val myTokenType: TokenType = ... val issuerSession = initateFlow(issuerParty) val observerSession = initatieFlow(observerParty) -subFlow(RedeemNonFungibleTokensFlow(myTokenType, listOf(issuerSession), listOf(observerSession))) +subFlow(RedeemNonFungibleTokensFlow(myTokenType, issuerSession, listOf(observerSession))) +``` +```java +// java + +TokenType myTokenType = ...; +FlowSession issuerSession = initiateFlow(issuerParty); +FlowSession observerSession = initiateFlow(observerParty); +subFlow(new RedeemNonFungibleTokensFlow(myTokenType, issuerSession, ImmutableList.of(observerSession))); ``` _Responder flow:_ `RedeemTokensFlowHandler` @@ -396,10 +682,21 @@ Observers record states in `StatesToRecord.ALL_VISIBLE` mode. Participants handl You can call it at the finalisation step: ```kotlin +// kotlin + val stx: SignedTransaction = ... -val participantSession: FlowSession = initFlow(participantParty) -val observerSession: FlowSession = initFlow(observerParty) +val participantSession: FlowSession = initiateFlow(participantParty) +val observerSession: FlowSession = initiateFlow(observerParty) subFlow(ObserverAwareFinalityFlow(stx, listOf(participantSession, observerSession))) +``` +```java +// java + +SignedTransaction stx = ...; +FlowSession participantSession = initiateFlow(participantParty); +FlowSession observerSession = initiateFlow(observerParty); +subFlow(new ObserverAwareFinalityFlow(stx, ImmutableList.of(participantSession, observerSession))); + ``` **Keeping distribution lists up-to-date** @@ -411,9 +708,17 @@ that takes care of adding new parties to the distribution list kept by the token Simply call at the end of your flow: ```kotlin +// kotlin + val stx: SignedTransaction = ... subFlow(UpdateDistributionListFlow(stx)) ``` +```java +// java + +SignedTransaction stx = ...; +subFlow(new UpdateDistributionListFlow(stx)); +``` ### Creating your own subtypes of TokenType @@ -422,7 +727,19 @@ top of the `tokenIdentifier` and `fractionDigits` then it is still possible to create your own `TokenType` sub-type by sub-classing `TokenType`. ```kotlin -class MyTokenType(override val tokenIdentifier: String, override val fractionDigits: Int = 0) : TokenType +// kotlin + +class MyTokenType(override val tokenIdentifier: String, override val fractionDigits: Int = 0) : TokenType(tokenIdentifier, fractionDigits) +``` +```java +// java + +class MyTokenType extends TokenType { + public MyTokenType(@NotNull String tokenIdentifier) { super(tokenIdentifier, 0); } + public MyTokenType(@NotNull String tokenIdentifier, int fractionDigits) { + super(tokenIdentifier, fractionDigits); + } +} ``` The above defined token type, allows CorDapp developers to create multiple @@ -436,22 +753,45 @@ instances of the token type with different identifiers, for example: Create an instance of your new token type like you would a regular object. ```kotlin +// kotlin + val myTokenType = MyTokenType("TEST", 2) ``` +```java +// java + + MyTokenType myTokenType = new MyTokenType("TEST", 2); +``` This creates a token of ```kotlin +// kotlin + val tokenClass: MyTokenType val tokenIdentifier: TEST ``` +```java +// java + + Class tokenClass = MyTokenType.class + String tokenIdentifier = "TEST" +``` Similar to the above you can create `IssuedTokenType` using your new token: ```kotlin +// kotlin + val issuer: Party = ... val issuedTokenType: IssuedTokenType = myTokenType issuedBy issuer ``` +```java +// java + +Party issuer = ...; +IssuedTokenType issuedTokenType = AmountUtilitiesKt.issuedBy(myTokenType, issuer); +``` #### Specyfing the notary from the notary list in network parameters @@ -466,7 +806,15 @@ notary = "O=Notary,L=London,C=GB" All flows from `token-sdk` will use this notary. If you want to use it from your custom flows, you can call: ```kotlin +// kotlin + val notary = getPreferredNotary(serviceHub) // And pass it to transaction builder TransactionBuilder(notary = notary) +``` +```java +// java + +Party notary = NotaryUtilitiesKt.getPreferredNotary(getServiceHub(), NotaryUtilitiesKt.firstNotary()); +new TransactionBuilder(notary); ``` \ No newline at end of file diff --git a/docs/InMemoryTokenSelection.md b/docs/InMemoryTokenSelection.md index 7ea5428f..c2de069e 100644 --- a/docs/InMemoryTokenSelection.md +++ b/docs/InMemoryTokenSelection.md @@ -59,29 +59,54 @@ Let's take a look how to use the feature. From your flow construct `LocalTokenSelector` instance: ```kotlin +// kotlin + val localTokenSelector = LocalTokenSelector(serviceHub) ``` +```java + +LocalTokenSelector localTokenSelector = new LocalTokenSelector(getServiceHub()); +``` After that you can choose states for move by either calling `selectTokens`: ```kotlin +// kotlin + val transactionBuilder: TransactionBuilder = ... val participantSessions: List = ... val observerSessions: List = ... val requiredAmount: Amount = ... val queryBy: TokenQueryBy = ... // See section below on queries // Just select states for spend, without output and change calculation -val selectedStates: List> = localTokenSelector.selectStates( +val selectedStates: List> = localTokenSelector.selectTokens( lockID = transactionBuilder.lockId, // Defaults to FlowLogic.currentTopLevel?.runId?.uuid ?: UUID.randomUUID() requiredAmount = requiredAmount, queryBy = queryBy) ``` +```java +// java + +TransactionBuilder transactionBuilder = ...; +FlowSession participantSession = ...; +FlowSession observerSession = ...; +Amount requiredAmount = ...; +TokenQueryBy queryBy = ...; // See section below on queries +// Just select states for spend, without output and change calculation +List> selectedState = localTokenSelector.selectTokens( + requiredAmount, + queryBy, + transactionBuilder.getLockId() +); +``` or even better `generateMove` method returns list of inputs and list of output states that can be passed to `addMove` or `MoveTokensFlow`: ```kotlin +// kotlin + // or generate inputs, outputs with change, grouped by issuers -val partiesAndAmounts: List> = ... // As in previous tutorials, list of parties that should receive amount of TokenType +val partiesAndAmounts: List>> = ... // As in previous tutorials, list of parties that should receive amount of TokenType val changeHolder: AbstractParty = ... // Party that should receive change val (inputs, outputs) = localTokenSelector.generateMove( lockId = transactionBuilder.lockId, // Defaults to FlowLogic.currentTopLevel?.runId?.uuid ?: UUID.randomUUID() @@ -95,6 +120,26 @@ subflow(MoveTokensFlow(inputs, outputs, participantSessions, observerSessions)) //... implement some business specific logic addMoveTokens(transactionBuilder, inputs, outputs) ``` +```java +// java + +List>> partiesAndAmounts = ...; +AbstractParty changeHolder = ...; +Pair>, List> selectedTokens = localTokenSelector.generateMove( + partiesAndAmounts, + changeHolder, + queryBy, + transactionBuilder.getLockId() +); +List> inputs = selectedTokens.getFirst(); +List outputs = selectedTokens.getSecond(); + +// Call subflow +subFlow(new MoveTokensFlow(inputs, outputs, participantSessions, observerSessions)); +// or use utilities functions +//... implement some business specific logic +MoveTokensUtilitiesKt.addMoveTokens(transactionBuilder, inputs, outputs); +``` Then finalize transaction, update distribution list etc, see [Most common tasks](docs/IWantTo.md) @@ -106,17 +151,19 @@ already performed selection and provide input and output states directly. Fungib Using in memory selection when redeeming tokens looks very similar to move: ```kotlin +// kotlin + val vaultWatcherService = serviceHub.cordaService(VaultWatcherService::class.java) val localTokenSelector = LocalTokenSelector(serviceHub, vaultWatcherService, autoUnlockDelay = autoUnlockDelay) // Smilar to previous case, we need to choose states that cover the amount. -val exitStates: List> = localTokenSelector.selectStates( +val exitStates: List> = localTokenSelector.selectTokens( lockID = transactionBuilder.lockId, // Defaults to FlowLogic.currentTopLevel?.runId?.uuid ?: UUID.randomUUID() requiredAmount = requiredAmount, queryBy = queryBy) // See section below on queries // Exit states and get possible change output. -val (inputs, changeOutput) = generateExit( +val (inputs, changeOutput) = localTokenSelector.generateExit( exitStates = exitStates, amount = requiredAmount, changeHolder = changeHolder @@ -131,6 +178,38 @@ addTokensToRedeem( changeOutput = changeOutput ) ``` +```java +// java + +VaultWatcherService vaultWatcherService = getServiceHub().cordaService(VaultWatcherService.class); +LocalTokenSelector localTokenSelector = new LocalTokenSelector(getServiceHub(), vaultWatcherService, autoUnlockDelay, null); + +// Smilar to previous case, we need to choose states that cover the amount. +List> exitStates = localTokenSelector.selectTokens( + requiredAmount, + queryBy, + transactionBuilder.getLockId() +); + +// Exit states and get possible change output +Pair>, FungibleToken> statesSelectedFromExit = localTokenSelector.generateExit( + exitStates, + requiredAmount, + changeHolder +); +List> inputs = statesSelectedFromExit.getFirst(); +FungibleToken changeOutput = statesSelectedFromExit.getSecond(); +// Call subflow top redeem states with the issuer +FlowSession issuerSession = initiateFlow(issuer); +FlowSession observerSession = initiateFlow(observer); +subFlow(new RedeemTokensFlow(inputs, changeOutput, issuerSession, observerSessions)); +// or use utilites functions. +RedeemFlowUtilitiesKt.addTokensToRedeem( + transactionBuilder, + inputs, + changeOutput +); +``` ### Providing Queries to LocalTokenSelector @@ -140,12 +219,28 @@ you can provide any states filtering as `predicate` function (don't use `queryCr and exists only to keep bakcwards compatibility with databse selection!). ```kotlin +// kotlin + val issuerParty: Party = ... val notaryParty: Party = ... // Get list of input and output states that can be passed to addMove or MoveTokensFlow val (inputs, outputs) = localTokenSelector.generateMove( + lockID = transactionBuilder.lockId, partiesAndAmounts = listOf(Pair(receivingParty, tokensAmount)), changeHolder = this.ourIdentity, // Get tokens issued by issuerParty and notarised by notaryParty queryBy = TokenQueryBy(issuer = issuerParty, predicate = { it.state.notary == notaryParty })) ``` +```java +// java + +Party issuerParty = ...; +Party notaryParty = ...; +// Get list of input and output states that can be passed to addMove or MoveTokensFlow +Pair>, List> selectedTokens = localTokenSelector.generateMove( + Collections.singletonList(new Pair>(receivingParty, tokensAmount)), + changeHolder, + queryBy = new TokenQueryBy(issuer, it -> (it.getState().getNotary().equals(notaryParty)), null), + transactionBuilder.getLockId() +); +```