Proj 17: Interledger and Ripple (15 pts.)

What you need:

Purpose

Ripple is the #3 cryptocurrency as of 12-28-17. It is intended to be used by banks, and includes sophisticated features to prevent money-laundering.

Interledger is the protocol used for payments between different ledgers.

The Original Tutorial

I am following this tutorial closely.

1. Install Node

If you are using a Mac, and don't already have XCode and Homebrew, follow this guide do do that.

Then, in a Terminal window, execute these commands to remove any old Node versions and install the latest Node.

brew install node

sudo chown -R $USER:$(id -gn $USER) /Users/$USER/.config

sudo npm install

2. Getting the Code

We are getting sample code with two components: a "shop" that sells letters for payment in XRP (Ripple cryptocurrency). Of course, the letters are not worth any money, and we're using the Ripple test chain, so the cryptocurrency is also worth no real money.

However, the skill of using Ripple is worth a lot.

In a Terminal, execute these commands.

cd

git clone https://github.com/interledger/tutorials
To compile it, execute these commands.
cd tutorials/letter-shop

npm install
You may see a warning, as shown below, but you should not get any errors.

3. Running the Shop: First Attempt

In a Terminal, execute this command.
node shop.js
You see an error message, as shown below, because we need to add account numbers to the code in the file "plugins.js".

4. Getting Ripple Addresses

In a Web browser, go to:

https://ripple.com/build/xrp-test-net/

Click the "Generate credentials" button. Copy your ADDRESS and SECRET values to a text file for later use, as shown below. These will be your SHOP credentials.

Click the "Generate credentials" button a second time. Copy your ADDRESS and SECRET values to a text file, also. These will be your CUSTOMER credentials.

5. Adding Credentials to plugins.js

In a Terminal, execute these commands.
cp plugins.js plugins.js.bak

nano plugins.js
Find the lines outlined in green in the image below, and add two slashes to the start of each line to comment the lines out.

Your code should now look like this:

Scroll down in the file and find the section outlined in green in the image below.

Delete the // from the start of each line.

Paste in the ADDRESS and SECRET and values into the lines, for both the SHOP and CUSTOMER.

Your code should now look like this:

Press Ctrl+X, Y, Enter to save the file.

6. Running the Shop: Second Attempt

In a Terminal, execute this command.
node shop.js
The code now proceeds past the line including "shop.js" and prints a message saying "== Starting the shop server ==", as shown below, but doesn't do anything further.

7. Connecting to the Ripple Account

In a Terminal, execute these commands.
cp shop.js shop.js.bak1

nano shop.js
The code ends with a comment saying "Do something...", as shown below.

We need to add code that connects to the Ripple account, so the shop can receive funds.

Paste this code at the end of the file:

console.log(` 1. Connecting to an account to accept payments...`)

plugin.connect().then(function () {
  // Get ledger and account information from the plugin
  const ledgerInfo = plugin.getInfo()
  const account = plugin.getAccount()

  console.log(`    - Connected to ledger: ${ledgerInfo.prefix}`)
  console.log(`    -- Account: ${account}`)
  console.log(`    -- Currency: ${ledgerInfo.currencyCode}`)
  console.log(`    -- CurrencyScale: ${ledgerInfo.currencyScale}`)

  // Convert our cost (10) into the right format given the ledger scale
  const normalizedCost = cost / Math.pow(10, parseInt(ledgerInfo.currencyScale))

  console.log(` 2. Starting web server to accept requests...`)
  console.log(`    - Charging ${normalizedCost} ${ledgerInfo.currencyCode}`)

  // Handle incoming web requests...

  // Handle incoming transfers...

})
Your code should now look like this:

Press Ctrl+X, Y, Enter to save the file.

8. Running the Shop: Third Attempt

In a Terminal, execute this command.
node shop.js
The shop connects to a ledger, and says it's starting a web server, but doesn't actually do that yet, as shown below.

Press Ctrl+C to stop the code.

9. Handling Web Requests

In a Terminal, execute these commands.
cp shop.js shop.js.bak2

nano shop.js
The code ends with a comment saying "// Handle incoming web requests..." and another saying "// Handle incoming transfers...", as shown below.

Now we'll add code that handles three types of Web requests.

Paste this code after the "// Handle incoming web requests" line:

 // Handle incoming web requests
  http.createServer(function (req, res) {
    // Browsers are irritiating and often probe for a favicon, just ignore
    if (req.url.startsWith(`/favicon.ico`)) {
      res.statusCode = 404
      res.end()
      return
    }

    console.log(`    - Incoming request to: ${req.url}`)
    const requestUrl = url.parse(req.url)

    if (requestUrl.path === `/`) {
      // Request for a letter with no attached fulfillment

      // Respond with a 402 HTTP Status Code (Payment Required)
      res.statusCode = 402

      // Generate a preimage and its SHA256 hash,
      // which we'll use as the fulfillment and condition, respectively, of the
      // conditional transfer.
      const fulfillment = crypto.randomBytes(32)
      const condition = sha256(fulfillment)

      // Get the letter that we are selling
      const letter = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
        .split('')[(Math.floor(Math.random() * 26))]

      console.log(`    - Generated letter (${letter}) ` +
      `at http://localhost:8000${req.url}${base64url(fulfillment)}`)

      // Store the fulfillment (indexed by condition) to use when we get paid
      fulfillments[base64url(condition)] = fulfillment

      // Store the letter (indexed by the fulfillment) to use when the customer
      // requests it
      letters[base64url(fulfillment)] = letter

      console.log(`    - Waiting for payment...`)

      res.setHeader(`Pay`, `${cost} ${account} ${base64url(condition)}`)

      res.end(`Please send an Interledger payment of` +
          ` ${normalizedCost} ${ledgerInfo.currencyCode} to ${account}` +
          ` using the condition ${base64url(condition)}\n` +
        `> node ./pay.js ${account} ${cost} ${base64url(condition)}`)
    } else {
      // Request for a letter with the fulfillment in the path

      // Get fulfillment from the path
      const fulfillmentBase64 = requestUrl.path.substring(1)

      // Lookup the letter we stored previously for this fulfillment
      const letter = letters[fulfillmentBase64]

      if (!letter) {
        // We have no record of a letter that was issued for this fulfillment

        // Respond with a 404 HTTP Status Code (Not Found)
        res.statusCode = 404

        console.log('     - No letter found for fulfillment: ' +
                                                      fulfillmentBase64)

        res.end(`Unrecognized fulfillment.`)
      } else {
        // Provide the customer with their letter
        res.end(`Your letter: ${letter}`)

        console.log(` 5. Providing paid letter to customer ` +
                                 `for fulfillment ${fulfillmentBase64}`)
      }
    }
  }).listen(8000, function () {
    console.log(`    - Listening on http://localhost:8000`)
    console.log(` 3. Visit http://localhost:8000 in your browser ` +
                                                        `to buy a letter`)
  })
  
The end of the file should now look like this:

Press Ctrl+X, Y, Enter to save the file.

10. Running the Shop: Fourth Attempt

In a Terminal, execute this command.
node shop.js
The shop connects to a ledger, and says it's starting a web server, as shown below.

11. Paying for a Letter: First Atttempt

Open a Web browser and go to:

http://localhost:8000

You see a message telling you what command to execute, as highlighted in the image below.

Open a new terminal window and execute these commands:

cd

cd tutorials/letter-shop
Now copy the command from the browser window and execute execute it in the Terminal window, as shown below.

The message "== Starting the payment client == " appears, but nothing further happens.

As before, this is because there is code missing from the payment client.

12. Connecting to an Account

Execute this command:
nano pay.js
There are only a few lines of code, with a "//Do something..." comment at the end, as shown below.

At the bottom, paste in this code:

console.log(` 1. Connecting to an account to send payments...`)

plugin.connect().then(function () {
  const ledgerInfo = plugin.getInfo()
  const account = plugin.getAccount()
  console.log(`    - Connected to ledger: ${ledgerInfo.prefix}`)
  console.log(`    -- Account: ${account}`)
  console.log(`    -- Currency: ${ledgerInfo.currencyCode}`)
  console.log(`    -- CurrencyScale: ${ledgerInfo.currencyScale}`)

  // Make payment...

  // Listen for fulfillments...

})
There are only a few lines of code, with a "//Do something..." comment at the end, as shown below.

Your code should now look like the image below.

Press Ctrl+X, Y, Enter to save the file.

13. Paying for a Letter: Second Atttempt

In a Terminal, execute the command you copied from your browser again.

The client connects to an account, but then just hangs, as shown below.

Press Ctrl+C to stop the client.

14. Connecting to an Account

There's more code missing from the pay.js file, so we'll add it now.

Execute this command:

nano pay.js
Near the bottom, after the "// Make payment..." line, paste in this code:
console.log(` 2. Making payment to ${destinationAddress} ` +
                                        `using condition: ${condition}`)

  // Send the transfer
  plugin.sendTransfer({
    to: destinationAddress,
    amount: destinationAmount,
    executionCondition: condition,
    id: uuid(),
    from: plugin.getAccount(),
    ledger: plugin.getInfo().prefix,
    ilp: base64url(IlpPacket.serializeIlpPayment({
      amount: destinationAmount,
      account: destinationAddress
    })),
    expiresAt: new Date(new Date().getTime() + 1000000).toISOString()
  }).then(function () {
    console.log('    - Transfer prepared, waiting for fulfillment...')
  }, function (err) {
    console.error(err.message)
  })
Your code should now look like the image below.

Press Ctrl+X, Y, Enter to save the file.

15. Paying for a Letter: Third Attempt

In a Terminal, execute the command you copied from your browser again.

The client makes the payment, but stops with a "waiting for fulfillment" message, as shown below.

Press Ctrl+C to stop the client.

Look at your original Terminal window running the shop. It's still "Waiting for payment...", as shown below.

16. Finding Your CUSTOMER Account Number

In the available Terminal window, execute this command.
cat plugins.js
The CUSTOMER account number appears at the end of the output, as highlighted in the image below.

17. Finding Your Transfer on the Ledger

We sent the transaction to the Ripple testnet, but it was never received. To see why, we can use the API with curl.

In the available Terminal window, execute this command, replacing YOUR-SENDING-ADDRESS with your own CUSTOMER address:

curl -X POST -d '{ "method": "account_objects", "params": [{"ledger_index":"validated", "account": "YOUR-SENDING-ADDRESS", "type": "escrow"}]}' https://client.altnet.rippletest.net:51234
As shown below, the response is in JSON format, ending with

"status":"success","validated":true

So the transaction was created on the ledger, but never received.

18. Accepting the Payment

There's more code missing from the shop.js file, so we'll add it now.

When a payment is received, an "incoming_prepare" event occurs. The shop.js code needs a handler for that event.

Execute this command:

nano shop.js
Near the bottom, after the "//Handle incoming transfers..." line, paste in this code:
// Handle incoming payments
  plugin.on('incoming_prepare', function (transfer) {
    if (parseInt(transfer.amount) < 10) {
      // Transfer amount is incorrect
      console.log(`    - Payment received for the wrong amount ` +
                                        `(${transfer.amount})... Rejected`)

      const normalizedAmount = transfer.amount /
                            Math.pow(10, parseInt(ledgerInfo.currencyScale))

      plugin.rejectIncomingTransfer(transfer.id, {
        code: 'F04',
        name: 'Insufficient Destination Amount',
        message: `Please send at least 10 ${ledgerInfo.currencyCode},` +
                  `you sent ${normalizedAmount}`,
        triggered_by: plugin.getAccount(),
        triggered_at: new Date().toISOString(),
        forwarded_by: [],
        additional_info: {}
      })
    } else {
      // Lookup fulfillment from condition attached to incoming transfer
      const fulfillment = fulfillments[transfer.executionCondition]

      if (!fulfillment) {
        // We don't have a fulfillment for this condition
        console.log(`    - Payment received with an unknown condition: ` +
                                              `${transfer.executionCondition}`)

        plugin.rejectIncomingTransfer(transfer.id, {
          code: 'F05',
          name: 'Wrong Condition',
          message: `Unable to fulfill the condition:  ` +
                                              `${transfer.executionCondition}`,
          triggered_by: plugin.getAccount(),
          triggered_at: new Date().toISOString(),
          forwarded_by: [],
          additional_info: {}
        })
      }

      console.log(` 4. Accepted payment with condition ` +
                                              `${transfer.executionCondition}.`)
      console.log(`    - Fulfilling transfer on the ledger ` +
                                 `using fulfillment: ${base64url(fulfillment)}`)

      // The ledger will check if the fulfillment is correct and
      // if it was submitted before the transfer's rollback timeout
      plugin.fulfillCondition(transfer.id, base64url(fulfillment))
        .catch(function () {
          console.log(`    - Error fulfilling the transfer`)
        })
      console.log(`    - Payment complete`)
    }
  })
The end of your code should now look like the image below.

Press Ctrl+X, Y, Enter to save the file.

19. Adding the Event Listener to the Client

There's also code missing from the pay.js file, so we'll add it now.

Execute this command:

nano pay.js
Near the bottom, after the "// Listen for fulfillments..." line, paste in this code:
  // Handle fulfillments
  plugin.on('outgoing_fulfill', function (transferId, fulfillmentBase64) {
    console.log('    - Transfer executed. Got fulfillment: ' +
                                                      fulfillmentBase64)
    console.log(` 3. Collect your letter at ` +
                           `http://localhost:8000/${fulfillmentBase64}`)
    plugin.disconnect()
    process.exit()
  })
The end of your code should now look like the image below.

Press Ctrl+X, Y, Enter to save the file.

20. Buying a Letter

Open a Web browser and go to:

http://localhost:8000

Copy the command, as shown below.

In an available terminal window, and execute the command.

cd

cd tutorials/letter-shop
Now copy the command from the browser window and execute execute it in the Terminal window, as shown below.

The purchase completes and a URL appears, as shown below.

Open that URL in a browser. Your letter appears, as shown below.

Saving a Screen Image

Make sure the "Your letter: " message is visible, as shown above.

YOU MUST SUBMIT A FULL-SCREEN IMAGE FOR FULL CREDIT!

Save the image with the filename "YOUR NAME Proj 17", replacing "YOUR NAME" with your real name.

21. Viewing the Completed Purchase

The Terminal window running the shop now shows the completed purchase, as shown below.

Turning in your Project

Email the images to cnit.141@gmail.com with the subject line: Proj 17 from YOUR NAME.

Sources

Installing MultiChain

Posted 12-28-17 by Sam Bowne