-
Notifications
You must be signed in to change notification settings - Fork 166
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
460 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
--- | ||
id: adv_invokeconcepts | ||
title: Advanced - Additional doInvoke functions | ||
--- | ||
|
||
In this guide, we will discuss features of `doInvoke` that seem a little more complex. We will add an extra transaction to our invoke with `intents` and externalize the signing of our transaction - so we don't have to pass a user's private key to the config. | ||
|
||
## Intents - adding extra transactions | ||
In the previous `doInvoke` guide, we sent 1 GAS alongside our invocation as a fee. As described [here](http://docs.neo.org/en-us/sc/systemfees.html#smart-contract-fees), transactions with a cost under 10 GAS are essentially free. | ||
|
||
So our `gas` field should stay 0 if your calculated fee remains below 10 GAS. You can determine this cost with an `invokeScript` RPC as we did [here](basic_createscript.html), evaluating the `gas_consumed` field in the response object. | ||
|
||
There is a way to achieve this. We can add a transaction to ourselves as an intent, with a minimum amount of 0.00000001 GAS. This makes sure we don't have to send 1 GAS to our transaction (without that GAS being needed to consume the transaction), while making sure a transaction is registered and persisted to the blockchain. | ||
|
||
The config object that `doInvoke` needs allows for a child object name intents. We set up our intents array as follows: | ||
```js | ||
import Neon, { CONST } from '@cityofzion/neon-js'; | ||
|
||
const intents = [ | ||
{ | ||
assetId: CONST.ASSET_ID.GAS, | ||
value: 0.00000001, | ||
scriptHash: Neon.get.scriptHashFromAddress(account.address) | ||
} | ||
]; | ||
|
||
// Add to config | ||
config.intents = intents; | ||
|
||
Neon.doInvoke(config).then(res => { | ||
console.log(res); | ||
}); | ||
``` | ||
|
||
## signingFunction | ||
Right now we're adding a user's private key to the config object, which is sensitive information and should be handled carefully. | ||
One way to do so is to externalize the signing of the transaction in a separate function. Right now, `signingFunction` is already used to sign with the Ledger. | ||
|
||
Instead of sending a user's private key to the config object, we can send the public key and a function that will sign the transaction. | ||
This function, the signingFunction, will receive the transaction and public key as parameters. Now, we can provide logic that retrieves the private key from our user - using the public key to do so - and signs the transaction when we retrieve the key. | ||
|
||
> Do note that the signing function has to return a Promise! | ||
```js | ||
import Neon from '@cityofzion/neon-js'; | ||
|
||
function signTx(tx, publicKey) { | ||
// Sign tx and attach signature onto tx | ||
// The publicKey passed in is used as a check to ensure that the private and public keys match. | ||
|
||
return new Promise(resolve => | ||
resolve(Neon.sign(tx, privateKey)) | ||
); | ||
} | ||
|
||
const config = { | ||
net: "http://localhost:5000", | ||
script: Neon.create.script({ | ||
scriptHash: '5b7074e873973a6ed3708862f219a6fbf4d1c411', | ||
operation: 'balanceOf', | ||
args: [Neon.u.reverseHex('cef0c0fdcfe7838eff6ff104f9cdec2922297537')] | ||
}), | ||
address: account.address, | ||
publicKey: account.publicKey, | ||
signingFunction: signTx | ||
gas: 1 | ||
} | ||
|
||
Neon.doInvoke(config).then(res => { | ||
console.log(res); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
--- | ||
id: basic_invoke | ||
title: Basic - Invoking a Smart Contract | ||
--- | ||
|
||
In this tutorial you will learn the difference between a testinvoke - as used in the Create Smart Contract Script guide - and an actual invocation of a Smart Contract. You will learn to use the `doInvoke` function to persist your calls to the NEO Blockchain. | ||
|
||
This guide will continue on the previous guide, using the old [ICO Template](https://github.com/neo-project/examples-csharp/blob/master/ICO_Template/ICO_Template.cs). | ||
|
||
## testinvoke vs invocationTransaction | ||
In the previous guide, we used `Neon.rpc.Query.invokeScript` as an RPC call to invoke our script. What we've actually done is use the equivalent of neo-python's `testinvoke`, to literally test whether our invocation was successful. | ||
|
||
Unless we turn this into an `invocationTransaction` RPC call, this will not persist to the blockchain. To do this, the script will be turned into a transaction. This, in turn, has to be signed with the user's private key. | ||
|
||
## doInvoke | ||
Luckily, `doInvoke` handles this for us! It will configure our script as a transaction, sign it with our private key and ultimately send it via an `invocationTransaction` RPC call. | ||
|
||
To use the `doInvoke` function, we need the following minimal ingredients: | ||
* `net`: 'MainNet', 'TestNet' or an API (like neoscan or neon-wallet-db) | ||
* `script`: the Smart Contract script. You can use a VM script - created with `Neon.create.script` - or a ScriptParams object that looks like: | ||
|
||
```js | ||
{ | ||
scriptHash: '5b7074e873973a6ed3708862f219a6fbf4d1c411', | ||
operation: 'balanceOf', | ||
args: [Neon.u.reverseHex('cef0c0fdcfe7838eff6ff104f9cdec2922297537')] | ||
} | ||
``` | ||
|
||
* `account`: an `Account` object that has an address and private key stored. Alternatively, you can use `address` and `privateKey` directly to pass these values. | ||
* `gas`: the gas fee we will attach to the transaction (this has to be an integer!) | ||
|
||
This in turn will be stored in a configuration object | ||
|
||
```js | ||
const config = { | ||
net: "http://localhost:5000", | ||
script: Neon.create.script({ | ||
scriptHash: '5b7074e873973a6ed3708862f219a6fbf4d1c411', | ||
operation: 'balanceOf', | ||
args: [Neon.u.reverseHex('cef0c0fdcfe7838eff6ff104f9cdec2922297537')] | ||
}), | ||
address: account.address, | ||
privateKey: account.privateKey, | ||
gas: 1 | ||
} | ||
``` | ||
|
||
and ultimately we call doInvoke as a promise | ||
|
||
```js | ||
Neon.doInvoke(config).then(res => { | ||
console.log(res) | ||
}) | ||
``` | ||
|
||
In the console result, you will find: | ||
* the data of your config object | ||
* the balance of your account (which is queried from either neoscan or neon-wallet-db) | ||
* the signed transaction | ||
* the response from the JSON RPC | ||
* the url of the NEO node it communicated with | ||
|
||
A high-level example of a response: | ||
|
||
```js | ||
{ | ||
"address": /* the user's address */, | ||
"balance": { | ||
/* an overview of your balance */ | ||
}, | ||
"gas": 1, | ||
"net": "http://localhost:5000", | ||
"privateKey": /* the user's private key */, | ||
"response": { | ||
"id": 1234, | ||
"jsonrpc": "2.0", | ||
"result": true, | ||
"txid": "763d2d61703f59f014d05052e14c856175b464c59b18a80fa68cd40c71d5d369" | ||
}, | ||
"script": /* the script generated from Neon.create.script */, | ||
"tx": { | ||
/* a copy of the transaction doInvoke sent */ | ||
}, | ||
"url": /* the url of the NEO node the transaction was sent to */ | ||
} | ||
``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
|
||
import { createScript } from '../../../src/sc/core' | ||
import { createScript, generateDeployScript } from '../../../src/sc/core' | ||
import data from './data.json' | ||
|
||
describe('SC Core', function () { | ||
describe.only('SC Core', function () { | ||
describe('createScript', function () { | ||
it('single intent', () => { | ||
Object.keys(data).map(key => { | ||
|
@@ -47,4 +47,21 @@ describe('SC Core', function () { | |
result.should.equal(expected) | ||
}) | ||
}) | ||
|
||
describe('generateDeployScript', function () { | ||
it('generate deploy script', () => { | ||
const script = generateDeployScript({ | ||
script: '54c56b6c766b00527ac46c766b51527ac46c766b00c36c766b51c3936c766b52527ac46203006c766b52c3616c7566', | ||
name: 'Add', | ||
version: '1', | ||
author: 'Ethan Fast', | ||
email: '[email protected]', | ||
description: 'Add', | ||
returnType: 5, | ||
parameterList: '05' | ||
|
||
}) | ||
script.str.should.equal('034164640d7465737440746573742e636f6d0a457468616e2046617374013103416464005501052f54c56b6c766b00527ac46c766b51527ac46c766b00c36c766b51c3936c766b52527ac46203006c766b52c3616c756668134e656f2e436f6e74726163742e437265617465') | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.