Skip to content

Commit 5d3701d

Browse files
committed
spaceships r1
1 parent bf16546 commit 5d3701d

File tree

12 files changed

+1003
-184
lines changed

12 files changed

+1003
-184
lines changed

Tokens/spaceships-javaAPIs/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ allprojects {
5454
options.compilerArgs << "-parameters" // Required by Corda's serialisation framework.
5555
}
5656

57+
test {
58+
maxHeapSize = '2G'
59+
maxParallelForks = 4
60+
forkEvery = 4
61+
}
62+
5763
jar {
5864
// This makes the JAR's SHA-256 hash repeatable.
5965
preserveFileTimestamps = false

Tokens/spaceships-javaAPIs/contracts/src/main/java/net/corda/examples/spaceships/states/SpaceshipTokenState.java renamed to Tokens/spaceships-javaAPIs/contracts/src/main/java/net/corda/examples/spaceships/states/SpaceshipTokenType.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
import net.corda.core.contracts.LinearPointer;
99
import net.corda.core.contracts.UniqueIdentifier;
1010
import net.corda.core.identity.Party;
11+
import net.corda.core.serialization.ConstructorForDeserialization;
1112
import net.corda.examples.spaceships.contracts.SpaceshipTokenContract;
1213
import org.jetbrains.annotations.NotNull;
1314

1415
import java.util.Collections;
1516
import java.util.List;
1617

1718
@BelongsToContract(SpaceshipTokenContract.class)
18-
public class SpaceshipTokenState extends EvolvableTokenType {
19+
public class SpaceshipTokenType extends EvolvableTokenType {
1920
private final Party manufacturer;
2021
private final String model;
2122
private final String planetOfOrigin;
@@ -25,18 +26,38 @@ public class SpaceshipTokenState extends EvolvableTokenType {
2526
private final boolean fungible;
2627
private final Amount<TokenType> value; // price OR price/share in case of Fungible
2728

28-
public SpaceshipTokenState(Party manufacturer, String model, String planetOfOrigin, int seatingCapacity, Amount<TokenType> value, boolean fungible) {
29+
public static int fungibleFractionDigits = 4;
30+
31+
@ConstructorForDeserialization
32+
public SpaceshipTokenType(UniqueIdentifier linearId, Party manufacturer, String model, String planetOfOrigin, int seatingCapacity, Amount<TokenType> value, boolean fungible) {
33+
this.linearId = linearId;
2934
this.manufacturer = manufacturer;
30-
this.linearId = new UniqueIdentifier();
3135
this.model = model;
3236
this.planetOfOrigin = planetOfOrigin;
3337
this.seatingCapacity = seatingCapacity;
34-
if (fungible) this.fractionDigits = 4;
38+
if (fungible) this.fractionDigits = fungibleFractionDigits;
3539
else this.fractionDigits = 0;
3640
this.value = value;
3741
this.fungible = fungible;
3842
}
3943

44+
// Auto-gen linearId
45+
public SpaceshipTokenType (Party manufacturer, String model, String planetOfOrigin, int seatingCapacity, Amount<TokenType> value, boolean fungible) {
46+
this(new UniqueIdentifier(), manufacturer, model, planetOfOrigin, seatingCapacity, value, fungible);
47+
}
48+
49+
public static SpaceshipTokenType createUpdatedSpaceShipTokenType(SpaceshipTokenType original, int seatingCapacity, Amount<TokenType> value) {
50+
return new SpaceshipTokenType(
51+
original.getLinearId(),
52+
original.getManufacturer(),
53+
original.getModel(),
54+
original.getPlanetOfOrigin(),
55+
seatingCapacity,
56+
value,
57+
original.isFungible()
58+
);
59+
}
60+
4061
public String getModel() {
4162
return model;
4263
}
@@ -81,8 +102,8 @@ public UniqueIdentifier getLinearId() {
81102

82103

83104
/* This method returns a TokenPointer by using the linear Id of the evolvable state */
84-
public TokenPointer<SpaceshipTokenState> toPointer(){
85-
LinearPointer<SpaceshipTokenState> linearPointer = new LinearPointer<>(linearId, SpaceshipTokenState.class);
105+
public TokenPointer<SpaceshipTokenType> toPointer(){
106+
LinearPointer<SpaceshipTokenType> linearPointer = new LinearPointer<>(linearId, SpaceshipTokenType.class);
86107
return new TokenPointer<>(linearPointer, fractionDigits);
87108
}
88109
}

Tokens/spaceships-javaAPIs/workflows/src/main/java/net/corda/examples/spaceships/flows/BuySpaceShipFlows.java

Lines changed: 0 additions & 108 deletions
This file was deleted.
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package net.corda.examples.spaceships.flows;
2+
3+
import co.paralleluniverse.fibers.Suspendable;
4+
import com.r3.corda.lib.tokens.contracts.states.FungibleToken;
5+
import com.r3.corda.lib.tokens.contracts.states.NonFungibleToken;
6+
import com.r3.corda.lib.tokens.contracts.types.TokenPointer;
7+
import com.r3.corda.lib.tokens.contracts.types.TokenType;
8+
import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection;
9+
import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilities;
10+
import com.r3.corda.lib.tokens.workflows.internal.flows.distribution.UpdateDistributionListFlow;
11+
import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlow;
12+
import com.r3.corda.lib.tokens.workflows.internal.flows.finality.ObserverAwareFinalityFlowHandler;
13+
import com.r3.corda.lib.tokens.workflows.utilities.NotaryUtilities;
14+
import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilities;
15+
import kotlin.Pair;
16+
import net.corda.core.contracts.Amount;
17+
import net.corda.core.contracts.StateAndRef;
18+
import net.corda.core.flows.*;
19+
import net.corda.core.identity.Party;
20+
import net.corda.core.node.StatesToRecord;
21+
import net.corda.core.node.services.VaultService;
22+
import net.corda.core.transactions.SignedTransaction;
23+
import net.corda.core.transactions.TransactionBuilder;
24+
import net.corda.examples.spaceships.states.SpaceshipTokenType;
25+
import org.jetbrains.annotations.NotNull;
26+
27+
import java.util.Collections;
28+
import java.util.List;
29+
import java.util.Set;
30+
import java.util.UUID;
31+
import java.util.stream.Collectors;
32+
33+
/**
34+
* The contained flows complete an atomic swap (single transaction exchange between buyer and seller) of a spaceship
35+
* To achieve this the steps are:
36+
* 1. Buyer requests the value/sale price of the ship he wants to purchase identified by UUID
37+
* 2. Seller queries his inventory of ships and grabs the value of the associated UUID and sends to Buyer
38+
* 3. Buyer checks to see if he has the money to pay - if not throws exception which ends the flow.
39+
* 4. Seller sends the transaction of ownership (the tx that created his ownership token) and sends it to Buyer
40+
* 5. Buyer creates transaction with the required currency tokens going to seller, and the ship token going to buyer
41+
* 6. Transaction is signed and finalised
42+
*/
43+
public interface BuySpaceshipFlows {
44+
45+
@InitiatingFlow
46+
@StartableByRPC
47+
class BuySpaceshipInitiator extends FlowLogic<SignedTransaction> {
48+
49+
private final String shipId;
50+
private final Party seller;
51+
52+
public BuySpaceshipInitiator(String shipId, Party seller) {
53+
this.shipId = shipId;
54+
this.seller = seller;
55+
}
56+
57+
@Suspendable
58+
@Override
59+
@SuppressWarnings("unchecked")
60+
public SignedTransaction call() throws FlowException {
61+
Party notary = NotaryUtilities.getPreferredNotary(getServiceHub());
62+
FlowSession sellerSession = initiateFlow(seller);
63+
VaultService vaultService = getServiceHub().getVaultService();
64+
boolean processSale = false;
65+
Amount<TokenType> paymentAmount; // The amount we wish to pay (in any currency - we will check against conversion rates)
66+
67+
// (STEP 1 - Request Token ship value) see how much the seller's spaceship costs
68+
Amount<TokenType> shipValue = sellerSession.sendAndReceive(Amount.class, shipId).unwrap(it -> it);
69+
70+
paymentAmount = shipValue; // first we will try to pay in the native currency of the ship's value
71+
72+
// check if we have enough funds to afford this currency and amount
73+
// tokenBalance returns Amount<TokenType> which represents the quantity of this TokenType (currency) in our vault
74+
int fundsAvailable = QueryUtilities.tokenBalance(vaultService, shipValue.getToken()).compareTo(paymentAmount);
75+
76+
if (fundsAvailable >= 0) { // we have enough funds and will go through with the purchase
77+
processSale = true;
78+
} else { // we do NOT have enough, in THAT tokenType but check if we can pay in some other currency using exchange rate
79+
80+
// creates a set of all tokenTypes we are holding
81+
Set<TokenType> heldTokenTypes = vaultService.queryBy(FungibleToken.class).getStates().stream()
82+
.map(it -> it.getState().getData().getTokenType())
83+
.collect(Collectors.toSet());
84+
heldTokenTypes.remove(shipValue.getToken()); // remove this as it's already been checked
85+
86+
// iterate over all the different TokenTypes (currencies) we hold to see if any have enough value against
87+
// the listed exchange rate (defined in FlowHelpers interface)
88+
for (TokenType currentTokenType : heldTokenTypes) {
89+
paymentAmount = FlowHelpers.exchangeCurrency(shipValue, currentTokenType);
90+
int funds = QueryUtilities.tokenBalance(vaultService, currentTokenType).compareTo(paymentAmount);
91+
if (funds >= 0) {
92+
processSale = true;
93+
break;
94+
}
95+
}
96+
}
97+
98+
if (!processSale) throw new FlowException("Insufficient Funds to Buy the spaceship " + shipId);
99+
100+
/**
101+
* If the above exception isn't thrown, then we have enough tokens in 'some' currency to complete the sale
102+
*
103+
* (Step 2 - receive the transaction which generated the ShipToken and it's associated definition)
104+
*
105+
* This token is what the seller holds (the tx proposal will transfer it to the us/buyer) Note: we need the full transaction
106+
* because it also includes the reference state which the TokenPointer/TokenType refers to - both are required
107+
* for addMoveNonFungibleTokens when using an EvolvableTokenType.
108+
*/
109+
SignedTransaction shipTokenTransaction = subFlow(new ReceiveTransactionFlow(sellerSession));
110+
111+
/**
112+
* We record the transaction in OUR vault so that the TransactionBuilder can access it.
113+
* Important: make sure you have the argument StatesToRecord.ALL_VISIBLE as the default recordTransactions will only
114+
* record states in the transaction which are RELEVANT (i.e. which we participated in) this is not 'our' transaction
115+
* so we need to that argument ALL_VISIBLE.
116+
*/
117+
getServiceHub().recordTransactions(StatesToRecord.ALL_VISIBLE, Collections.singletonList(shipTokenTransaction));
118+
NonFungibleToken shipNFT = shipTokenTransaction.getCoreTransaction().outputsOfType(NonFungibleToken.class).get(0);
119+
TokenPointer<SpaceshipTokenType> shipTokenPointer = (TokenPointer<SpaceshipTokenType>) shipNFT.getTokenType();
120+
121+
// Gather the paymentAmount in Tokens on our side (the tx proposal will transfer these to the seller)
122+
Pair<List<StateAndRef<FungibleToken>>, List<FungibleToken>> selectedTokens = new DatabaseTokenSelection(getServiceHub())
123+
// here we are generating input and output states which send the correct amount to the seller, and any change back to buyer
124+
.generateMove(Collections.singletonList(new Pair<>(seller, paymentAmount)), getOurIdentity());
125+
126+
// Build the transaction which transfers the curreny tokens AND the spaceship token, in a single transaction
127+
TransactionBuilder txBuilder = new TransactionBuilder(notary);
128+
MoveTokensUtilities.addMoveNonFungibleTokens(txBuilder, getServiceHub(), shipTokenPointer, getOurIdentity());
129+
MoveTokensUtilities.addMoveTokens(txBuilder, selectedTokens.getFirst(), selectedTokens.getSecond());
130+
131+
SignedTransaction ptx = getServiceHub().signInitialTransaction(txBuilder);
132+
SignedTransaction stx = subFlow(new CollectSignaturesFlow(ptx, Collections.singletonList(sellerSession)));
133+
134+
// update the distribution list
135+
subFlow(new UpdateDistributionListFlow(stx));
136+
return subFlow(new ObserverAwareFinalityFlow(stx, Collections.singletonList(sellerSession)));
137+
}
138+
}
139+
140+
@InitiatedBy(BuySpaceshipInitiator.class)
141+
class BuySpaceshipResponder extends FlowLogic<Void> {
142+
private final FlowSession counterpartySession;
143+
144+
public BuySpaceshipResponder(FlowSession counterpartySession) {
145+
this.counterpartySession = counterpartySession;
146+
}
147+
148+
@Suspendable
149+
@Override
150+
public Void call() throws FlowException {
151+
// receive request for value of the given shipId
152+
String shipId = counterpartySession.receive(String.class).unwrap(it -> it);
153+
UUID shipUUID = UUID.fromString(shipId);
154+
155+
// // Get the state definition from vault to grab the value
156+
SpaceshipTokenType spaceshipTokenType = FlowHelpers.uuidToSpaceShipTokenType(getServiceHub().getVaultService(), shipUUID);
157+
158+
// (Step 1 - Respond with value) send back value
159+
counterpartySession.send(spaceshipTokenType.getValue());
160+
161+
StateAndRef<NonFungibleToken> spaceshipNFTStateAndRef = QueryUtilities.heldTokensByToken(getServiceHub().getVaultService(), spaceshipTokenType.toPointer())
162+
.getStates().get(0);
163+
164+
// (Step 2 - Send the corresponding TX representing our ownership so the buyer can build and propose the full transaction)
165+
SignedTransaction shipTokenTransaction = getServiceHub().getValidatedTransactions().getTransaction(spaceshipNFTStateAndRef.getRef().getTxhash());
166+
assert shipTokenTransaction != null;
167+
subFlow(new SendTransactionFlow(counterpartySession, shipTokenTransaction));
168+
169+
SignedTransaction stx = subFlow(new SignTransactionFlow(counterpartySession) {
170+
@Override
171+
protected void checkTransaction(@NotNull SignedTransaction stx) throws FlowException {
172+
// No checks for simplicity - but here you can check that you will actually receive
173+
// the correct amount of tokens before signing (among other possible checks).
174+
}
175+
});
176+
177+
subFlow(new ObserverAwareFinalityFlowHandler(counterpartySession));
178+
return null;
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)