Giter Site home page Giter Site logo

solidity's Introduction

The Captain's Mess

useful image

There are so many Ethereum Oracles out there. But I needed a more simple and straight forward one for my projects. Thus, I created my own one. Hi, I'm CaptainJS and my nodejs container ship just left the harbor. Be my Seaman and start invoking JavaScript directly from Ethereum's Solidity. Here's how to do it...

The Captain needs 0.55 ETH every month to keep his kubernetes containership up and running. Feel free to donate to Mainnet-Address 0xc52f52057f6bf5ecc75d704cb85510f4e1ba207b.

The Seaman's Examples

The SeamansExamples contract contains some code which you will see in the further chapters. There's a demo instance running at Ethereum's mainnet at address 0xfcd53089c3de49fa8c6cc8330cd9f49e84b01cd6 (ROPSTEN: 0x2c53859c18da0e286161f1649e6a5fdabcb9bb98) waiting for your tests.

useful image

Use Case #1: The Simple Callback (aka "ring the ship's bell")

In some cases it is necessary to have a mechanism to call the contract back. For example a fond which withdraws its budget every year to specific people. But Solidity can't call itself.

Therefore extend from usingCaptainJS and start coding...

Use a unique integer job Id to identify your callback. Invoke RingShipsBell with enough gas and receive the callback in method RingRing. That's it.

To make sure that RingRing will only be called by the captain himself and not a pirate, add onlyCaptainsOrdersAllowed to its declaration.

At the moment the captain can only accept bell rings with a maximum of 48 hours.

contract SeamansExamples is usingCaptainJS {
   ...
   uint constant SHIPS_BELL_RING = 3;

   function CallbackExample() public {
        RingShipsBell(
            SHIPS_BELL_RING, /* give the job a unique ID */
            20, /* minutes from now */ 
            DEFAULT_GAS_UNITS, /* use default gas */
            DEFAULT_GAS_PRICE /* use default gas price */
        );    
    }

    function RingRing(uint UniqueIdentifier) external onlyCaptainsOrdersAllowed {
        // OK. It worked and we got a result
        ...
    }
    

Use Case #2: The Simple JavaScript Job

Now let's look at a simple JavaScript job. JavaScript's mathjs library has multiple useful functions such as the conversion of centimeter to inch.

In order to use this library you need to call the Run function and hand over the JavaScript code that is necessary to convert centimeters to inch.

The JavaScript code you want to submit must be written the following way:

module.exports = function(CaptainJSIn) { /* here goes your code */ } 

CaptainJS will invoke your code within a container by calling the default function. CaptainJSIn will contain your inputs of the JavaScript function. Then CaptainJS will return the result of your code. If your JavaScript code was successful: CaptainsResult will be invoked. The return result is always a string.

If your JavaScript code was not successful or its result couldn't be send back (it failed or there was not enough gas or whatever happened) then CaptainsError will be invoked.

To make sure that both CaptainsResult and CaptainsError will only be called by the captain himself and not a pirate, add onlyCaptainsOrdersAllowed to its declaration.

A runtime slice has a duration of 10 seconds and it includes the download and install of all required npm modules. At the moment 6 runtime slices are the maximum.

Here's the complete code snippet:

contract SeamansExamples is usingCaptainJS {
    ...
    uint constant CENTIMETER_JOB = 1;

    function CentimeterToInchExample(string Centimeter) public {
        Run(
            CENTIMETER_JOB,  /* give the job a unique ID */
            /* JavaScript code I want to execute: */
            "module.exports = function(CaptainJSIn) { var math = require('mathjs'); return math.eval(CaptainJSIn + ' cm to inch'); }", 
            Centimeter, /* Input parameter which will result in CaptainJSIn (see above) */
            "mathjs",  /* Nodejs libraries we need */
            /* we need a maximum of 2 runtime slices */
            3, /* we need a maximum of 3 runtime slices */
            DEFAULT_GAS_UNITS, /* use default gas units */ 
            DEFAULT_GAS_PRICE /* we will transfer the default gas price for return */
        );    
    }

    function CaptainsResult(uint UniqueJobIdentifier, string Result) external onlyCaptainsOrdersAllowed {
        // analyse the return results
        if(UniqueJobIdentifier == CENTIMETER_JOB) {
            // OK. It worked and we got a result
            ...
        } else 
        
        if(UniqueJobIdentifier == WOLFRAMALPHA_JOB) {
            // OK. It worked and we got another result
            ...
        }
    }
    
    function CaptainsError(uint UniqueJobIdentifier, string Error) external onlyCaptainsOrdersAllowed {
        // analyse the return results
        if(UniqueJobIdentifier == CENTIMETER_JOB) {
            // OK. It didn't work :-/
        }
    }

}    

(I have a special mathjs function in use case #6)

Use Case #3: The Heavy JavaScript Job

Now let's look at a more complex JavaScript job. You want to ask WolframAlpha anything it knows about a country like France. Therefore you design your code the same way like you did in Use Case #2.

To query WolframAlpha you use JavaScript's axios library. The default function must be async so that you can wait for a result when you invoke axios.get(...). axios will return a JSON object but we need to flatten it to a string:

module.exports = async function(CaptainJSIn) { 
    const axios = require('axios');
    const WAlpha = await axios.get('http://www.wolframalpha.com/queryrecognizer/query.jsp?appid=DEMO&mode=Default&i=' + CaptainJSIn + '&output=json');          
    return JSON.stringify(WAlpha.data);
}

Again, CaptainJS will invoke your code within a container by calling this default function. CaptainJSIn will contain your inputs such as "France".

Again, if your JavaScript code was successful: CaptainsResult will be invoked. Otherwise CaptainsError will be invoked.

And because Solidity sometimes is such a crappy programming language you will use a very expensive concat function to make your JavaScript code more readable.

Here's the complete code snippet.

contract SeamansExamples is usingCaptainJS {
    ...
    uint constant WOLFRAMALPHA_JOB = 2;
    
    function WolframAlphaExample(string Country) public {
        Run(
            WOLFRAMALPHA_JOB, /* give the job a unique ID */            
            concat ( /* JavaScript code I want to execute: */
                "module.exports = async function(CaptainJSIn) { ",
                "   const axios = require('axios'); ",
                "   const WAlpha = await axios.get('http://www.wolframalpha.com/queryrecognizer/query.jsp?appid=DEMO&mode=Default&i=' + CaptainJSIn + '&output=json'); ",          
                "   return JSON.stringify(WAlpha.data); ",
                "}"
            ),
            Country, /* Input parameter which will result in CaptainJSIn (see above) */
            "axios, mathjs", /* Nodejs libraries we need */
            3, /* we need a maximum of 3 runtime slices */
            200000, /* use 200,000 gas units */
            DEFAULT_GAS_PRICE /* use default gas price */
        );    
    }
      
    ...
}    

Use Case #4: The JSON, XML/XPath or HTML/jQuery Request

A classic oracle request is a simple JSON, XML/XPath or HTML/jQuery request. Instead of writing a full-blown JavaScript code which does the query for you the newest release has predefined queries.

To invoke a simple query just use the Run method in the same way as you did it in the previous 2 use cases. But instead of submitting JavaScript code send an URL that has either a html:, xml: or json: prefix. The input parameter of the Run method is either a JSON, XPath or jQuery expression. Typically 1 runtime slice is enough.

If your JavaScript code was successful: CaptainsResult will be invoked. Otherwise CaptainsError will be invoked.

(The client libraries now include a test module to see if your code works - before you submitted to the blockchain)

    function HTMLqueryExample() public {
        Run(
            HTML_QUERY_EXAMPLE,  /* give the job a unique ID */
            /* url needs to start with html: */
            "html:http://www.amazon.co.uk/gp/product/1118531647",
            /* Input parameter is the jQuery. Result will be stored in QUERY_RESULT variable */ 
            "$('span.inlineBlock-display span.a-color-price').each(function(i, element) {var el = $(this); QUERY_RESULT = el.text(); })", 
            "",  /* no modules required */
            1, /* queries are fast */
            DEFAULT_GAS_UNITS, /* use default gas units */ 
            DEFAULT_GAS_PRICE /* we will transfer the default gas price for return */
        );    
    }
    
    function JSONqueryExample() public {
        Run(
            JSON_QUERY_EXAMPLE,  /* give the job a unique ID */
            /* url needs to start with json: */
            "json:https://api.kraken.com/0/public/Ticker?pair=ETHUSD",
            /* Input parameter is the JSON path */ 
            "result.XETHZUSD.a[0]", 
            "",  /* no modules required */
            1, /* queries are fast */
            DEFAULT_GAS_UNITS, /* use default gas units */ 
            DEFAULT_GAS_PRICE /* we will transfer the default gas price for return */
        );    
    }

Use Case #5: Encrypting your data

For those seamen who like to encrypt their data which is stored in the blockchain I've added an encryption module to the latest release. This allows you to send encrypted data to the Captain's NodeJS containers. There it will be decrypted and executed. (The string result will not be send encrypted back)

For example you want to send a mail after a transaction happened then you could make use of nodemailer, login to your mail account and send the mail. In this case your code will look similar to this one:

module.exports = async function(CaptainJSIn) { 
    var nodemailer = require("nodemailer");
    var transport = nodemailer.createTransport({
        host: "smtp-mail.outlook.com", // hostname
        secureConnection: false, // TLS requires secureConnection to be false
        port: 587, // port for secure SMTP
        tls: {
        ciphers:'SSLv3'
        },
        auth: {
            user: "[email protected]",
            pass: "mysecretpassword"
        }
    });
    var mailOptions = {
        to: '[email protected]', // list of receivers
        subject: "you're fired", // Subject line
        text: "Dear Donald, ..."
    };
    
    // send mail with defined transport object
    transport.sendMail(mailOptions, function(error, info){});
}

The bad side of the story is, that your account details will be stored within your code. And these account details will be stored in Ethereum's blockchain forever. That's bad. But there's hope!

In the client libraries I've added the https://github.com/CaptainJavaScript/Seaman-Client/blob/master/CaptainJS-Encryption.js which help you to encrypt your data before you include them in your contract:

  • just simply add your module to a file like MailSample.js
  • encrypt the file using EncryptFile from CaptainJS-Encryption.js
  • add the encrypted code to your contract

Encrypt your code:

var ENC = require("./CaptainJS-Encryption.js");
async function RUN() { 
    await ENC.EncryptFile(false, "MailSample.js", "EncryptedMailSample.txt", 
        () => { console.log("Success!"); },
        (ERROR) => { console.log(ERROR); }
    );
}
RUN();

And add your encrypted code to the Solidity-based contract:

function HTMLqueryExample() public {
    Run(
        ENCRYPTED_MAIL_EXAMPLE,
        "crypt:8366268bd167a9f8318f99c71d0f489d0372b545735c2e10303c47bad2507e933171f72f...",
        "", 
        "",  
        1,
        DEFAULT_GAS_UNITS,  
        DEFAULT_GAS_PRICE 
    );    
}

The Captain will never read your code! It just gets executed in a fresh new container. In other words: if you encrypt your data then it will stay encrypted until it's executed in a container (and the container will vanish after execution). The encryption uses the public/private key of the Captain's Ethereum contracts. This is the only way nobody else can decrypt your data. Allthough the captain could read your encrypted data for private purposes, he doesn't do it. That's a promise and a proof of trust.

Use Case #6: The MathJS Shortcut

I'm an old man and I remember the old Basic meets Assembler days that made my hair become grey. Math in Solidity is hell. Why not simply use MathJS? Why re-code everything what already exists??? Here's the trick:

contract MathDemo is usingCaptainJS {
    ...
    function DoLog2_Example() public {
        Run(
            1, /* give the job a unique ID */
            "math:log2(16)", /* just call the mathjs-function after math: */
            "",
            "",
            1, /* it's fast: 1 slice is enough */
            DEFAULT_GAS_UNITS, /* use default gas units */ 
            DEFAULT_GAS_PRICE /* we will transfer the default gas price for return */
        );    
    }

    function CaptainsResult(uint UniqueJobIdentifier, string Result) external onlyCaptainsOrdersAllowed {
        // continue with the Log2-result
        uint Log2Result = parseInt(Result);
        ...
    }
    
    ...

}    

Use Case #7: The 1-line of code

Sometimes it is too much effort for me to write a full JavaScript module. Sometimes I just want to execute 1-line of code.

Here's how to do it:

contract MathDemo is usingCaptainJS {
    ...
    function DoLog2_Example() public {
        Run(
            1, /* give the job a unique ID */
            "eval:var a = CaptainJSIn + 2; CaptainJSOut = a;", /* just add eval: keyword */
            "",
            "",
            1, /* it's fast: 1 slice is enough */
            DEFAULT_GAS_UNITS, /* use default gas units */ 
            DEFAULT_GAS_PRICE /* we will transfer the default gas price for return */
        );    
    }
    ...
}    

The captain then will transform this into a JavaScript module before he's executing it:

module.exports = async function(CaptainJSIn) { 
   var CaptainJSOut = ''; 
   var a = 1 + 2; CaptainJSOut = a;return CaptainJSOut;
}

Use Case 8: IPFS

If you want to upload or download files to an IPFS server than just use the ipfs: command:

   ...
   Run( 
      jobId, 
      "ipfs:add:*server_url*:*server_port*:*base64_encoded_file_content*",
      "*file_name*",
      ...
   );

and:

   ...
   Run( 
      jobId, 
      "ipfs:cat:*server_url*:*server_port*:*hash_value*",
      "",
      ...
   );

The :add: command will add the file to the selected IPFS drive. The ๐Ÿฑ command will get the file.

Voucher Codes

There's both a ROPSTEN and MAINNET version available. Both are equal. Inside your usingCaptainJS there is a voucher context included. Just add your voucher code by calling ActivateVoucher from your derived Solidity code.

Just check Captain's Twitter account for vouchers.

 function UseVoucher() public {
        ActivateVoucher("MobyDick");
 }

Fees & Budget Transfer

The Captain just rented his container ship. To pay his ship he needed to set these prices. Prices may change over time and will be updated both here + via Twitter.

  • RingShipsBell(...):
    • if you have a voucher code then you transfer your gas budget for the callback
    • otherwise the full price needs to be payed
    • PricePerSubmission = 20 szabo;
    • PricePerBellRing = 6 szabo;
        if(HasVoucher) 
            return GasForCallback * GasPriceInWei;
        else
            return PricePerBellRing + PricePerSubmission + (GasForCallback * GasPriceInWei);
  • Run(...):
    • if you have a promo code then you transfer your gas budget for the callback
    • otherwise the full price needs to be payed
    • PricePerSubmission = 20 szabo;
    • PricePerSlice = 50 szabo; // 1 slice = 10 seconds
        if(HasVoucher) 
            return GasForCallback * GasPriceInWei;
        else
            return PricePerSubmission + (RuntimeSlices * PricePerSlice) + (GasForCallback * GasPriceInWei);

What If?

  • what if you don't transfer enough gas?

    • then your job won't fail directly.
    • the captain will try his best to get the job back on the blockchain
    • he will calculate the budget you submitted when calling Run(...) as budget = 350000 * max possible gas_price
    • if your job result then still doesn't go through, then good night
  • what if you transfer too much gas and a too high gas price?

    • then the captain will order a bottle of rum on your expense
  • what if it a callback is delayed (ie. it is invoked after 2 days instead of 2 hours)

    • the captain then will argue that the concept of blockchain isn't made for exacted invocations
    • be mentally prepared then
    • transfer a higher budget next time by increasing gas and gas price
  • what if a JavaScript code never gets executed?

    • relax
    • the captain then will shout "impossible!" and try its best by informing you via CaptainsError invocation
  • what if you change code within usingCaptainJS?

    • you won't be keelhauled :-)
    • try it, improve it!
  • what if you submit bad code?

    • we don't like pirates!
    • this is not nice!

Client Libraries

I uploaded a JavaScript test client at https://github.com/CaptainJavaScript/Seaman-Client

solidity's People

Contributors

captainjavascript avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.