Neon-js: Calling a transaction to store values in a smart contract

in #neo7 years ago (edited)

The "Hello World!" Smart Contract example for neo is storing a key/value pair "Hello", "World" inside a smart. To get neon-js to invoke this contract, it takes a few steps. Here I want to document a how to invoke a smart contract and write to something on the neo blockchain.

First let's start with the smart contract:

using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services.Neo;
using System;
using System.Numerics;

namespace ContractStorage
{
    public class ContractStorage : SmartContract {
        public static object Main(string operation, object[] args) {
            if (operation == "addStorageRequest")
                return AddStorageRequest(args);
            if (operation == "getPendingRequest")
                return GetPendingRequest(args);
            else
                return "Error: Invalid Parameter";
        }

        private static object AddStorageRequest(object[] args) {
            byte[] key = (byte[])args[0];
            byte[] value = (byte[])args[1];
            byte[] storedRequests = Storage.Get(Storage.CurrentContext, key);
            if (storedRequests.Length == 0) {
                Storage.Put(Storage.CurrentContext, key, value);
                return true;
            }
            else
                return false;
        }

        private static object GetPendingRequest(object[] args) {
            byte[] key = (byte[])args[0];
            byte[] storageValue = Storage.Get(Storage.CurrentContext, key);
            return storageValue;
         }
    }
}

So pretty basic. Two methods, one for saving a key/value pair, one for getting a value based on the key. When we deploy the smart contract, we have to make sure, to click the Need Storage checkbox. This is how my Deploy contract dialog looked: https://imgur.com/Y2sz1Jg (image embeddding didn't wokr for me :( ). For testing I used the following private docker setup: https://gist.github.com/slipo/f18f1a0b5e6adb7b0bf172b93379d891 

Now what do we need to invoke the transcation:

  1. scriptHash of your smart contract. This can be found in the ContractStorage.abi.json in the build folder. Important: without the 0x prefix. neon-js expects 40 chars, else it throws an error without explanation (stand 3.2.1)
  2. privatekey & password from our wallet. We can generate these from the WIF in neon see: https://imgur.com/BfeB4te . The used values should match the docker image.

What do we need to do to invoke the smart contract:

  1. ask for the balance of the account
  2. define our intents
  3. define our invokes (what we want to do)
  4. create the script
  5. create the transaction
  6. sign the transaction
  7. send the transaction

The following code is written in angular 4 and typescript. I will leave the angular parts out. 

The first method stores something in the Smart Contract:

SMARTCONTRACTSCRIPTHASH = '2b57048da0deb5cdd138302ca40b82f660bf5060'; // without 0x!
PRIVATEKEY: string = "6PYKN6jd6wGE4dR2FPz7BehZfZ7qZoSHDYzwF6QCMAGAAD2q6B5XjnvNVm"; // from the neon wallet
PASSWORD: string = 'passpharse'; // from the neon wallet
setKey: string = "Stefan";
setValue: string = "Test";

public addStorageRequest() {
  // create the account object
  let net = 'http://neo-privnet:5000'; // this is a different net (part) then the one rpc where we query the 
  let account = new Neon.wallet.Account(this.PRIVATEKEY);
  account.decrypt(this.PASSWORD);
  let fromAddrScriptHash = Neon.wallet.getScriptHashFromAddress(account.address);

  // get our balnce (needed for transaction)
  Neon.api.neonDB.getBalance(net, account.address)
    .then((balance: Neon.wallet.Balance) => {
    // create or intents (someone got a link for a good explanation?)
    let intents: Neon.tx.TransactionOutput[] = [{
      assetId: Neon.CONST.ASSET_ID.GAS,
      value: new Neon.u.Fixed8(1),  // I gueesed this :)
      scriptHash: this.SMARTCONTRACTSCRIPTHASH
    }];
    // the interesting part: what do we want to do :)
    let invoke: Neon.sc.scriptParams = {
      scriptHash: this.SMARTCONTRACTSCRIPTHASH,
      operation: 'addStorageRequest',
      args: [
        Neon.sc.ContractParam.string(this.setKey),
        Neon.sc.ContractParam.string(this.setValue)
      ]
    }

    // create a script from our parameters
    let sb = new Neon.sc.ScriptBuilder();
    sb.emitAppCall(invoke.scriptHash, invoke.operation, invoke.args, false);
    let script = sb.str;
    // create a transaction object
    let unsignedTx = Neon.tx.Transaction.createInvocationTx(balance, intents, script, 3, { version: 1 });

    // sing the transaction object (we write something to the blockchain!)
    let signedTx = Neon.tx.signTransaction(unsignedTx, account.privateKey);

    // convert the transaction to hx so we can send it in an query
    let hexTx = Neon.tx.serializeTransaction(signedTx);

    // send the transaction to our net
    return Neon.rpc.queryRPC('http://neo-privnet:30333', {
      method: 'sendrawtransaction',
      params: [hexTx],
      id: 1
    }); // here we could listen to the response with then/catch
  });

That covers the addStorageRequest part. Now what do we need to do to get the stored value back out again. luckily this is a bit easier, since now we don't need to invoke a transaction (because we don't save anything to the blockchain). There might be some usecases where we also sign get requests to the blockchain, so we have prove that we requested this data, here I don't find it necessary.

Now we just need the scripthash of our contract and can use a simpler scriptbuilder method.

getKey: string = "Stefan";
hideGetDetails = true;
getRequestResult: any = undefined;
getRequestResultDetails: any = undefined;
public getPendingRequest() {
  // create our script
  let props: Neon.sc.scriptParams = {
    scriptHash: this.SMARTCONTRACTSCRIPTHASH,
    operation: 'getPendingRequest',
    args: [
      Neon.sc.ContractParam.string(this.getKey),
    ],
    useTailCall: true
  }
  let vmScript = Neon.sc.createScript(props);
  // invoke the script
  Neon.rpc.Query.invokeScript(vmScript)
    .execute('http://neo-privnet:30333')
   .then((res: any) => {
      this.getRequestResultDetails = res;
      if (res.result.state === "HALT, BREAK" && // "HALT, BREAK" means it was ok (source?)
        !!res.result.stack["0"]) {
        // if we stacked the parameters correctly we get the result on postion 0
        // if you e.g. provided to many input paramters they get returned to you and are on pos 0, 1, ...
        let hexValue = res.result.stack["0"].value;
        let result = "";
        for (var i = 0; i < hexValue.length; i += 2) {
          result += String.fromCharCode(parseInt(hexValue.substr(i, 2), 16));
        }
        this.getRequestResult = result;
      }
    }, (errpr) => {
      debugger;
    });
}

Calling this should return you the value, you just saved in your smart contract! (it takes a block to be registerd, so up to 15sec (Source?))

The full code will be  publish later in a github repository (along with other demos). However this question got some interest in the discord group so i kinda rushed it. And it's currently 2am and I have to get up again at 7 for work, so please excuse typos and other mistakes.

If you like to help, please post sources of the marked parts in the comments or add other suggestions. Anything is welcome :) 


Update Question: how can I make the code not have  a sace after each line, deleting it doesn't work :(

Sort:  
Loading...

Cool writeup! You can switch from the markdown editor to raw html on the top right when editing, and delete the extra <br> tags

Thanks, but if I copy the HTML form my editor, it says I have to remove the spans... and it generates a lot fo spans... so won't update it anymore :D

The code above sends 1 Gas to the smart contract each time. After some comments in discord the intent should look like this:

const intents: Neon.tx.TransactionOutput[] = [{
assetId: Neon.CONST.ASSET_ID.GAS,
value: new Neon.u.Fixed8(0.00000001),
scriptHash: account.scriptHash
}];

It sends the minuman amount of Gas to itself, so the smartcontract gets executed

Congratulations @kern.pro! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

You made your First Comment

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

Upvote this notification to help all Steemit users. Learn why here!