/
Open in Online IDE

Make a car sale

Create a flow to sell a car


Reading Time: 6 min

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:

  1. Bob moving USD tokens to Alice.
  2. 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

warn icon

Don't do that, this is only for your own edification.

Observe an untested example flow here.

  1. 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;
  2. 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));
  3. From the buyer's side, which has a FlowSession sellerSession:

    // 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.

warn icon

Remember not to use the code above that is presented as an example of what not to do.

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 addMoveFungibleTokens
  • which itself calls generateMove that 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 SendStateAndRefFlow that 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 StateAndRef<NonFungibleToken>.
  • The buyer has to verify that the provided NonFungibleToken indeed 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 .send and .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.

support icon

Get 3 months access to the authors and experts who created this training.

  • Expert instructors will review your code and help you to refine it.
  • One-on-one support and mentoring from expert instructors.
  • Collaboration with fellow students in a dedicated Training Slack channel.
Discuss on Slack
Rate this Page
Would you like to add a message?
Submit
Thank you for your Feedback!