Make a car sale
- Reading time: 6 mins
- Discuss on Slack
In the previous chapter, you created your car token and implemented simple flows related to it. Here, you will consider what selling a car means in this context.
Sale and atomicity
In the previous exercise you relied on the basic flows of the Tokens SDK. Each time, you were served a fully formed transaction and were not given the opportunity to amend it before signature. In particular, in a few lines of code, Alice gave the car away to Bob. She did not sell it. For the transaction to be a sale, something would have to move “the other way”. Preferably atomically, meaning either everything passes, or nothing passes. For example, if Alice was selling her car to Bob; the transaction could have 2 types of tokens moving:
- Bob moving USD tokens to Alice.
- Alice moving the car token to Bob.
You want both of these moves to happen successfully or fail the transaction entirely. Consider a non-atomic workflow, which is what happens if we are not careful and use those ready flows that are provided by the SDK.
A non-atomic sale
Observe an untested example flow here.
The Initiator / seller proposes selling:
@NotNull private final TokenPointer<CarTokenType> car;
for the price saved in the CarTokenType, in this currency:
@NotNull private final IssuedTokenType issuedCurrency;
to this buyer:
@NotNull private final Party buyer;
Let’s go through the motions on the seller’s side first:
// Recall the price. final StateAndRef<CarTokenType> carInfo = car.getPointer().resolve(getServiceHub()); final long price = carInfo.getState().getData().getPrice(); // Create a session with the buyer. final FlowSession buyerSession = initiateFlow(buyer); // Send the car price. buyerSession.send(price); // Send the currency desired. buyerSession.send(issuedCurrency); // For simplicity we assume that the seller sends back the transaction that paid the seller. // Receive the payment tx. ReceiveTransactionFlow is the responder flow of a specialized DataVendingFlow // that sends a transaction and its history. final SignedTransaction payTx = subFlow(new ReceiveTransactionFlow(buyerSession)); // TODO check we were paid indeed. // Shall we continue or do we disappear with the money? // An exception could also happen here leaving the buyer without their money or car. // Move the car to the buyer. final PartyAndToken carForBuyer = new PartyAndToken(buyer, car); final QueryCriteria myCarCriteria = QueryUtilitiesKt.heldTokenAmountCriteria( car, getOurIdentity()); return subFlow(new MoveNonFungibleTokens(carForBuyer, Collections.emptyList(), myCarCriteria));
From the buyer’s side, which has a
// Receive the car price. final long price = sellerSession.receive(Long.class).unwrap(it -> it); // Receive the currency information. final IssuedTokenType issuedCurrency = sellerSession.receive(IssuedTokenType.class).unwrap(it -> it); // Pay seller. final QueryCriteria heldByMe = QueryUtilitiesKt.heldTokenAmountCriteria( issuedCurrency.getTokenType(), getOurIdentity()); final QueryCriteria properlyIssued = QueryUtilitiesKt.tokenAmountWithIssuerCriteria( issuedCurrency.getTokenType(), issuedCurrency.getIssuer()); final Amount<TokenType> currencyPrice = AmountUtilitiesKt.amount(price, issuedCurrency.getTokenType()); final PartyAndAmount<TokenType> amountForSeller = new PartyAndAmount<>(sellerSession.getCounterparty(), currencyPrice); final SignedTransaction payTx = subFlow(new MoveFungibleTokens( Collections.singletonList(amountForSeller), Collections.singletonList(sellerSession.getCounterparty()), properlyIssued.and(heldByMe), getOurIdentity())); // Inform seller. SendTransactionFlow is a specialized DataVendingFlow that sends a transaction and // its history. subFlow(new SendTransactionFlow(sellerSession, payTx)); //Receiving the car is taken care of by the auto-responder of MoveNonFungibleTokens. return null;
In this example of a non-atomic sale (bad!), it is very possible that an exception could happen inside the initiator right after the buyer flow completed successfully, i.e. after the buyer has moved their money to the seller. Not to mention that the buyer could be malicious and disappear, mid-flow with the money.
An atomic sale
Hello? IAEA? No, not that kind of atomic sale.
Due to the shortcomings of a non-atomic sale, and the fact that the high-level flows are not extensible, in cases like this we have to use the utility functions that are provided by the Tokens SDK, in order to assemble a single transaction that contains both sides of the trade.
You can dig on your own inside the high-level flows of the SDK to find out things like:
- There is a function named
- which itself calls
generateMovethat assembles inputs and outputs for your convenience. You might sense that it will be advantageous to use it on the buyer’s side, when assembling the dollar states for payment.
- There is a flow named
SendStateAndRefFlowthat allows a party to send a list of
StateAndRef<T>to any peer, including the historical chain of transactions that prove their validity. You might sense that it will be useful for the seller to send information about the car being sold, and the buyer to send information about those dollar states.
What are the things that such an atomic sale flow has to resolve?
- One party, the initiator, has to create the transaction, while the other party, the responder, verifies and signs it. Whether the initiator is the seller or the buyer, or either, has to be decided at some point. If may be useful to think of a signed offer that is open to acceptance, and acceptance that finalizes the sale.
- The seller has to provide the car’s
- The buyer has to verify that the provided
NonFungibleTokenindeed describes the desired car. Otherwise the seller could bait and switch and pass something else entirely.
- The buyer has to provide a list of
StateAndRef<FungibleToken>to cover the price in the agreed issued currency.
- The seller has to verify that the provided
FungibleTokens are of the right type and amount to cover the price. Because trust but verify…
- The responder has to verify that the transaction that it is asked to sign is indeed the transaction that is expected with the states that were mentioned earlier. Otherwise the initiator could bait and switch too, by asking for states information and asking to sign a different transaction.
This is information enough for you to try your hand at creating an atomic sale. Go!
In the next chapter, you will discover an example solution of an atomic sale.
A word on data vending
This is a fancy word for the act of exchanging data between peers. Why not just say
.receive? Sure, for regular types, send and receive is enough. So we use data vending to describe a complex send where more data has to be fetched.
You have seen an example in the non-atomic sale above. You used
SendTransactionFlow, which sends the whole chain of transactions that led to the one you want to send. Or rather it sends those that the recipient does not have yet. Same with
SendStateAndRef, for a list of
StateAndRefs. Don’t hesitate to use them.