commit
52cad4a04e
108
README.md
108
README.md
|
@ -23,9 +23,9 @@ Before executing this program, `config/config.json` must be setup and `npm insta
|
|||
- Host, port and protocol Ganache will use when forking the blockchain for gas estimations and other operations
|
||||
- Wallet account used for processing the transactions
|
||||
- Symmetric key used to receive the Whisper messages
|
||||
- Symmetric key used to send the heartbeats that notify the tokens and prices accepted
|
||||
- Accepted tokens information
|
||||
- Contract configuration
|
||||
|
||||
This program is configured with the default values for a embark installation run from 0
|
||||
|
||||
A `geth` node running whisper (via `-shh` option) is required. To execute the gas-relayer, you may use any of the following three methods.
|
||||
|
@ -45,27 +45,103 @@ The gas relayer service needs to be running, and configured correctly to process
|
|||
|
||||
|
||||
|
||||
## Additional notes
|
||||
How to send a message to this service (all accounts and privatekeys should be replaced by your own test data)
|
||||
## Using the gas relayer
|
||||
|
||||
### The relayer
|
||||
A node that wants to act as a relayer only needs to have a geth node with whisper enabled, and an account with ether to process the transactions. This account and node need to be configured in `./config/config.js`.
|
||||
|
||||
The relayer will be subscribed to receive messages in a specific symkey (this will change in the future to use ENS), and will reply back to both availability and transaction requests
|
||||
|
||||
### The user
|
||||
|
||||
#### Sending a message to the gas relayer network (all accounts and privatekeys should be replaced by your own test data)
|
||||
```
|
||||
shh.post({symKeyID: SYM_KEY, sig: WHISPER_KEY_ID, ttl: 1000, powTarget: 1, powTime: 20, topic: TOPIC_NAME, payload: PAYLOAD_BYTES});
|
||||
```
|
||||
- `SYM_KEY` must contain the whisper symmetric key used. It is shown on the console when running the service with `node`. With the provided configuration you can use the value:
|
||||
```
|
||||
0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b`
|
||||
shh.post({
|
||||
symKeyID: SYM_KEY, // If requesting availability
|
||||
pubKey: PUBLIC_KEY_ID, // If sending a transaction
|
||||
sig: WHISPER_KEY_ID,
|
||||
ttl: 1000,
|
||||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: TOPIC_NAME,
|
||||
payload: PAYLOAD_BYTES
|
||||
}).then(......)
|
||||
```
|
||||
- `symKeyID: SYM_KEY` must contain the whisper symmetric key used. It is shown on the console when running the service with `node`. With the provided configuration you can use the symmetric key `0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b`. Only used when asking for relayer availability.
|
||||
- `pubKey: PUBLIC_KEY_ID`. After asking for availability, once the user decides on a relayer, it needs to set the `pubKey` attribute with the relayer public key (received in the availability reply in the `sig` attribute of the message).
|
||||
- `WHISPER_KEY_ID` represents a keypair registered on your node, that will be used to sign the message. Can be generated with `web3W.shh.newKeyPair()`
|
||||
- `TOPIC_NAME` must contain one of the topic names generated based on converting the contract name to hex, and taking the first 8 bytes. For the provided configuration the following topics are available:
|
||||
- - IdentityGasRelay: `0x4964656e`
|
||||
- - SNTController: `0x534e5443`
|
||||
- `PAYLOAD_BYTES` a hex string that contains the identity/contract address to invoke and the web3 encoded abi function invocation plus parameters. If we were to execute `callGasRelayed(address,uint256,bytes,uint256,uint256,uint256,address,bytes)` (part of the IdentityGasRelay) in contract `0x692a70d2e424a56d2c6c27aa97d1a86395877b3a`, with these values: `"0x11223344556677889900998877665544332211",100,"0x00",1,10,20,"0x1122334455112233445511223344551122334455"`, "0x1122334455", `PAYLOAD_BYTES` can be prepared as follows:
|
||||
- `PAYLOAD_BYTES` a hex string that contains details on the operation to perform.
|
||||
|
||||
|
||||
#### Polling for gas relayers
|
||||
The first step is asking the relayers for their availability. The message payload needs to be the hex string representation of a JSON object with a specific structure:
|
||||
|
||||
```
|
||||
const payload = web3.utils.toHex({
|
||||
'contract': "0xContractToInvoke",
|
||||
'address': web3.eth.defaultAccount,
|
||||
'action': 'availability',
|
||||
'token': "0xGasTokenAddress",
|
||||
'gasPrice': 1234
|
||||
});
|
||||
```
|
||||
|
||||
- `contract` is the address of the contract that will perform the operation, in this case it can be an Identity, or the SNTController.
|
||||
- `address` The address that will sign the transactions. Normally it's `web3.eth.defaultAccount`
|
||||
- `gasToken`: token used for paying the gas cost
|
||||
- `gasPrice`: The gas price used for the transaction
|
||||
|
||||
This is a example code of how to send an 'availability' message:
|
||||
|
||||
```
|
||||
const whisperKeyPairID = await web3W.shh.newKeyPair();
|
||||
const jsonAbi = ABIOfIdentityGasRelay.filter(x => x.name == "callGasRelayed")[0];
|
||||
|
||||
const msgObj = {
|
||||
symKeyId: "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b",
|
||||
sig: whisperKeyPairID,
|
||||
ttl: 1000,
|
||||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: "0x4964656e",
|
||||
payload: web3.utils.toHex({
|
||||
'contract': "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
|
||||
'address': web3.eth.defaultAccount
|
||||
'action': 'availability',
|
||||
'gasToken': "0x744d70fdbe2ba4cf95131626614a1763df805b9e",
|
||||
'gasPrice': 40000000000 // 40 gwei equivalent in SNT
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
web3.shh.post(msgObj)
|
||||
.then((err, result) => {
|
||||
console.log(result);
|
||||
console.log(err);
|
||||
});
|
||||
```
|
||||
|
||||
When it replies, you need to extract the `sig` attribute to obtain the relayer's public key
|
||||
|
||||
#### Sending transaction details
|
||||
|
||||
Sending a transaction is similar to the previous operation, except that we send the message to an specific node, we use the action `transaction`, and also we send a `encodedFunctionCall` with the details of the transaction to execute.
|
||||
|
||||
From the list of relayers received via whisper messages, you need to extract the `message.sig` to obtain the `pubKey`. This value is used to send the transaction to that specific relayer.
|
||||
|
||||
`encodedFunCall` is the hex data used obtained from `web3.eth.abi.encodeFunctionCall` for the specific function we want to invoke.
|
||||
|
||||
If we were to execute `callGasRelayed(address,uint256,bytes,uint256,uint256,uint256,address,bytes)` (part of the IdentityGasRelay) in contract `0x692a70d2e424a56d2c6c27aa97d1a86395877b3a`, with these values: `"0x11223344556677889900998877665544332211",100,"0x00",1,10,20,"0x1122334455112233445511223344551122334455"`, "0x1122334455", `PAYLOAD_BYTES` can be prepared as follows:
|
||||
|
||||
```
|
||||
// The following values are created obtained when polling for relayers
|
||||
const whisperKeyPairID = await web3W.shh.newKeyPair();
|
||||
const relayerPubKey = "0xRELAYER_PUBLIC_KEY_HERE";
|
||||
// ...
|
||||
// ...
|
||||
const jsonAbi = ABIOfIdentityGasRelay.find(x => x.name == "callGasRelayed");
|
||||
|
||||
const funCall = web3.eth.abi.encodeFunctionCall(jsonAbi,
|
||||
[
|
||||
|
@ -80,7 +156,7 @@ const funCall = web3.eth.abi.encodeFunctionCall(jsonAbi,
|
|||
]);
|
||||
|
||||
const msgObj = {
|
||||
symKeyID: "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b",
|
||||
pubKey: relayerPubKey,
|
||||
sig: whisperKeyPairID,
|
||||
ttl: 1000,
|
||||
powTarget: 1,
|
||||
|
@ -88,8 +164,9 @@ const msgObj = {
|
|||
topic: "0x4964656e",
|
||||
payload: web3.utils.toHex({
|
||||
'contract': "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a",
|
||||
'encodedFunctionCall': funCall,
|
||||
'address': web3.eth.defaultAccount
|
||||
'action': 'transaction',
|
||||
'encodedFunctionCall': funCall,
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -100,4 +177,9 @@ web3.shh.post(msgObj)
|
|||
console.log(err);
|
||||
});
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
#### Valid operations
|
||||
TODO
|
||||
|
||||
|
|
|
@ -275,7 +275,7 @@
|
|||
"error",
|
||||
"never"
|
||||
],
|
||||
"valid-jsdoc": "error",
|
||||
"valid-jsdoc": "off",
|
||||
"vars-on-top": "off",
|
||||
"wrap-iife": "error",
|
||||
"wrap-regex": "error",
|
||||
|
|
|
@ -21,11 +21,6 @@ module.exports = {
|
|||
"powTime": 1000
|
||||
}
|
||||
},
|
||||
|
||||
"heartbeat": {
|
||||
"enabled": true,
|
||||
"symKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b"
|
||||
},
|
||||
|
||||
"tokens": {
|
||||
"0x0000000000000000000000000000000000000000": {
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
/**
|
||||
* Configuration Settings related to contracts
|
||||
*/
|
||||
class ContractSettings {
|
||||
|
||||
/**
|
||||
* @param {object} config - Configuration object obtained from `./config/config.js`
|
||||
* @param {object} web3 - Web3 object already configured
|
||||
* @param {object} eventEmitter - Event Emitter
|
||||
*/
|
||||
constructor(config, web3, eventEmitter){
|
||||
this.tokens = config.tokens;
|
||||
this.topics = [];
|
||||
|
@ -12,11 +20,17 @@ class ContractSettings {
|
|||
this.pendingToLoad = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process configuration file
|
||||
*/
|
||||
process(){
|
||||
this._setTokenPricePlugin();
|
||||
this._processContracts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set price plugin for token
|
||||
*/
|
||||
_setTokenPricePlugin(){
|
||||
for(let token in this.tokens){
|
||||
if(this.tokens[token].pricePlugin !== undefined){
|
||||
|
@ -26,16 +40,30 @@ class ContractSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed tokens
|
||||
* @return {object} - Dictionary with allowed tokens (address as key)
|
||||
*/
|
||||
getTokens(){
|
||||
return this.tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token by address
|
||||
* @param {string} - Token address
|
||||
* @return {object} - Token details
|
||||
*/
|
||||
getToken(token){
|
||||
const tokenObj = this.tokens[token];
|
||||
tokenObj.address = token;
|
||||
return tokenObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token by symbol
|
||||
* @param {string} - Token symbol
|
||||
* @return {object} - Token details
|
||||
*/
|
||||
getTokenBySymbol(symbol){
|
||||
for(let token in this.tokens){
|
||||
if(this.tokens[token].symbol == symbol){
|
||||
|
@ -46,14 +74,28 @@ class ContractSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contract by topicName
|
||||
* @param {string} topicName - Topic name that represents a contract
|
||||
* @return {object} - Contract details
|
||||
*/
|
||||
getContractByTopic(topicName){
|
||||
return this.contracts[topicName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the topic based on the contract's name
|
||||
* @param {string} contractName - Name of the contract as it appears in the configuration
|
||||
* @return {string} - Topic
|
||||
*/
|
||||
getTopicName(contractName){
|
||||
return this.web3.utils.toHex(contractName).slice(0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set contract's bytecode in the configuration
|
||||
* @param {string} topicName - Topic name that represents a contract
|
||||
*/
|
||||
async _obtainContractBytecode(topicName){
|
||||
if(this.contracts[topicName].isIdentity) return;
|
||||
|
||||
|
@ -71,6 +113,10 @@ class ContractSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract function details based on topicName
|
||||
* @param {string} topicName - Topic name that represents a contract
|
||||
*/
|
||||
_extractFunctions(topicName){
|
||||
const contract = this.getContractByTopic(topicName);
|
||||
|
||||
|
@ -90,6 +136,9 @@ class ContractSettings {
|
|||
this.contracts[topicName] = contract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process contracts and setup the settings object
|
||||
*/
|
||||
_processContracts(){
|
||||
for(let contractName in this.contracts){
|
||||
// Obtaining the abis
|
||||
|
@ -105,8 +154,7 @@ class ContractSettings {
|
|||
|
||||
// Obtaining strategy
|
||||
if(this.contracts[topicName].strategy){
|
||||
const strategy = require(this.contracts[topicName].strategy);
|
||||
this.contracts[topicName].strategy = new strategy(this.web3, this.config, this, this.contracts[topicName]);
|
||||
this.contracts[topicName].strategy = this.buildStrategy(this.contracts[topicName].strategy, topicName);
|
||||
}
|
||||
|
||||
this._obtainContractBytecode(topicName);
|
||||
|
@ -114,6 +162,16 @@ class ContractSettings {
|
|||
this._extractFunctions(topicName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create strategy object based on source code and topicName
|
||||
* @param {string} strategyFile - Souce code path of strategy to build
|
||||
* @param {string} topicName - Hex string that represents a contract's topic
|
||||
*/
|
||||
buildStrategy(strategyFile, topicName){
|
||||
const strategy = require(strategyFile);
|
||||
return new strategy(this.web3, this.config, this, this.contracts[topicName]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
/**
|
||||
* Message Processor to analyze and execute strategies based on input objects
|
||||
*/
|
||||
class MessageProcessor {
|
||||
|
||||
/**
|
||||
* @param {object} config - Configuration object obtained from `./config/config.js`
|
||||
* @param {object} settings - Settings obtained from parsing the configuration object
|
||||
* @param {object} web3 - Web3 object already configured
|
||||
* @param {object} events - Event emitter
|
||||
*/
|
||||
constructor(config, settings, web3, events){
|
||||
this.config = config;
|
||||
this.settings = settings;
|
||||
|
@ -7,14 +16,20 @@ class MessageProcessor {
|
|||
this.events = events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input message content
|
||||
* @param {object} contract - Object obtained from the settings based on the message topic
|
||||
* @param {object} input - Object obtained from a message.
|
||||
* @returns {object} State of validation
|
||||
*/
|
||||
async _validateInput(contract, input){
|
||||
console.info("Processing request to: %s, %s", input.contract, input.functionName);
|
||||
console.info("Processing '%s' request to contract: %s", input.action, input.contract);
|
||||
|
||||
if(contract == undefined){
|
||||
return {success: false, message: 'Unknown contract'};
|
||||
}
|
||||
|
||||
if(!contract.functionSignatures.includes(input.functionName)){
|
||||
if(input.functionName && !contract.functionSignatures.includes(input.functionName)){
|
||||
return {success: false, message: 'Function not allowed'};
|
||||
}
|
||||
|
||||
|
@ -37,7 +52,15 @@ class MessageProcessor {
|
|||
return {success: true};
|
||||
}
|
||||
|
||||
async process(contract, input, reply){
|
||||
/**
|
||||
* Process strategy and return validation result
|
||||
* @param {object} contract - Object obtained from the settings based on the message topic
|
||||
* @param {object} input - Object obtained from a message.
|
||||
* @param {function} reply - Function to reply a message
|
||||
* @param {object} strategy - Strategy to apply. If undefined, it will use a strategy based on the contract
|
||||
* @returns {object} State of validation
|
||||
*/
|
||||
async processStrategy(contract, input, reply, strategy){
|
||||
const inputValidation = await this._validateInput(contract, input);
|
||||
if(!inputValidation.success){
|
||||
// TODO Log?
|
||||
|
@ -45,16 +68,33 @@ class MessageProcessor {
|
|||
return;
|
||||
}
|
||||
|
||||
let validationResult;
|
||||
if(strategy || contract.strategy){
|
||||
let validationResult;
|
||||
if(strategy){
|
||||
validationResult = await strategy.execute(input, reply);
|
||||
} else {
|
||||
validationResult = await contract.strategy.execute(input, reply);
|
||||
}
|
||||
|
||||
if(contract.strategy){
|
||||
validationResult = await contract.strategy.execute(input, reply);
|
||||
if(!validationResult.success){
|
||||
reply(validationResult.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process strategy and based on its result, send a transaction to the blockchain
|
||||
* @param {object} contract - Object obtained from the settings based on the message topic
|
||||
* @param {object} input - Object obtained from a message.
|
||||
* @param {function} reply - function to reply a message
|
||||
* @returns {undefined}
|
||||
*/
|
||||
async processTransaction(contract, input, reply){
|
||||
const validationResult = await this.processStrategy(contract, input, reply);
|
||||
|
||||
let p = {
|
||||
from: this.config.node.blockchain.account,
|
||||
to: input.contract,
|
||||
|
@ -79,7 +119,7 @@ class MessageProcessor {
|
|||
try {
|
||||
const receipt = await this.web3.eth.sendTransaction(p);
|
||||
// TODO: parse events
|
||||
return reply("Transaction mined", receipt);
|
||||
reply("Transaction mined", receipt);
|
||||
} catch(err){
|
||||
reply("Couldn't mine transaction: " + err.message);
|
||||
// TODO log this?
|
||||
|
|
|
@ -4,9 +4,6 @@ const config = require('../config/config.js');
|
|||
const ContractSettings = require('./contract-settings');
|
||||
const MessageProcessor = require('./message-processor');
|
||||
|
||||
// IDEA: A node should call an API (probably from a status node) to register itself as a
|
||||
// token gas relayer.
|
||||
|
||||
console.info("Starting...");
|
||||
const events = new EventEmitter();
|
||||
|
||||
|
@ -59,52 +56,39 @@ events.on('setup:complete', async (settings) => {
|
|||
// Verifying relayer balance
|
||||
await verifyBalance();
|
||||
|
||||
shhOptions.symKeyId = await web3.shh.addSymKey(config.node.whisper.symKey);
|
||||
shhOptions.kId = await web3.shh.newKeyPair();
|
||||
|
||||
const symKeyID = await web3.shh.addSymKey(config.node.whisper.symKey);
|
||||
const pubKey = await web3.shh.getPublicKey(shhOptions.kId);
|
||||
|
||||
// Listening to whisper
|
||||
// Individual subscriptions due to https://github.com/ethereum/web3.js/issues/1361
|
||||
// once this is fixed, we'll be able to use an array of topics and a single subs for symkey and a single subs for privKey
|
||||
console.info(`Sym Key: ${config.node.whisper.symKey}`);
|
||||
console.info(`Relayer Public Key: ${pubKey}`);
|
||||
console.info("Topics Available:");
|
||||
for(let contract in settings.contracts) {
|
||||
console.info("- %s: %s [%s]", settings.getContractByTopic(contract).name, contract, Object.keys(settings.getContractByTopic(contract).allowedFunctions).join(', '));
|
||||
shhOptions.topics = [contract];
|
||||
events.emit('server:listen', shhOptions, settings);
|
||||
|
||||
// Listen to public channel - Used for reporting availability
|
||||
events.emit('server:listen', Object.assign({symKeyID}, shhOptions), settings);
|
||||
|
||||
// Listen to private channel - Individual transactions
|
||||
events.emit('server:listen', Object.assign({privateKeyID: shhOptions.kId}, shhOptions), settings);
|
||||
}
|
||||
|
||||
/*
|
||||
if(config.heartbeat.enabled){
|
||||
|
||||
web3.shh.addSymKey(config.heartbeat.symKey)
|
||||
.then(heartbeatSymKeyId => {
|
||||
|
||||
for(let tokenAddress in settings.getTokens()){
|
||||
|
||||
let heartbeatPayload = settings.getToken(tokenAddress);
|
||||
heartbeatPayload.address = tokenAddress;
|
||||
|
||||
setInterval(() => {
|
||||
web3.shh.post({
|
||||
symKeyID: heartbeatSymKeyId,
|
||||
sig: keyId,
|
||||
ttl: config.node.whisper.ttl,
|
||||
powTarget:config.node.whisper.minPow,
|
||||
powTime: config.node.whisper.powTime,
|
||||
topic: web3.utils.toHex("relay-heartbeat-" + heartbeatPayload.symbol).slice(0, 10),
|
||||
payload: web3.utils.toHex(JSON.stringify(heartbeatPayload))
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(-1);
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
}
|
||||
});
|
||||
}*/
|
||||
});
|
||||
|
||||
const reply = (message) => (text, receipt) => {
|
||||
const replyFunction = (message) => (text, receipt) => {
|
||||
if(message.sig !== undefined){
|
||||
console.log(text);
|
||||
|
||||
let payloadContent;
|
||||
if(typeof text === 'object'){
|
||||
payloadContent = {...text, receipt};
|
||||
} else {
|
||||
payloadContent = {text, receipt};
|
||||
}
|
||||
|
||||
web3.shh.post({
|
||||
pubKey: message.sig,
|
||||
sig: shhOptions.kId,
|
||||
|
@ -112,7 +96,7 @@ const reply = (message) => (text, receipt) => {
|
|||
powTarget:config.node.whisper.minPow,
|
||||
powTime: config.node.whisper.powTime,
|
||||
topic: message.topic,
|
||||
payload: web3.utils.fromAscii(JSON.stringify({message:text, receipt}, null, " "))
|
||||
payload: web3.utils.fromAscii(JSON.stringify(payloadContent, null, " "))
|
||||
}).catch(console.error);
|
||||
}
|
||||
};
|
||||
|
@ -121,9 +105,7 @@ const extractInput = (message) => {
|
|||
let obj = {
|
||||
contract: null,
|
||||
address: null,
|
||||
functionName: null,
|
||||
functionParameters: null,
|
||||
payload: null
|
||||
action: null
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -131,9 +113,15 @@ const extractInput = (message) => {
|
|||
let parsedObj = JSON.parse(msg);
|
||||
obj.contract = parsedObj.contract;
|
||||
obj.address = parsedObj.address;
|
||||
obj.functionName = parsedObj.encodedFunctionCall.slice(0, 10);
|
||||
obj.functionParameters = "0x" + parsedObj.encodedFunctionCall.slice(10);
|
||||
obj.payload = parsedObj.encodedFunctionCall;
|
||||
obj.action = parsedObj.action;
|
||||
if(obj.action == 'transaction'){
|
||||
obj.functionName = parsedObj.encodedFunctionCall.slice(0, 10);
|
||||
obj.functionParameters = "0x" + parsedObj.encodedFunctionCall.slice(10);
|
||||
obj.payload = parsedObj.encodedFunctionCall;
|
||||
} else if(obj.action == 'availability') {
|
||||
obj.gasToken = parsedObj.gasToken;
|
||||
obj.gasPrice = parsedObj.gasPrice;
|
||||
}
|
||||
} catch(err){
|
||||
console.error("Couldn't parse " + message);
|
||||
}
|
||||
|
@ -144,7 +132,7 @@ const extractInput = (message) => {
|
|||
|
||||
events.on('server:listen', (shhOptions, settings) => {
|
||||
let processor = new MessageProcessor(config, settings, web3, events);
|
||||
web3.shh.subscribe('messages', shhOptions, (error, message) => {
|
||||
web3.shh.subscribe('messages', shhOptions, async (error, message) => {
|
||||
if(error){
|
||||
console.error(error);
|
||||
return;
|
||||
|
@ -152,9 +140,30 @@ events.on('server:listen', (shhOptions, settings) => {
|
|||
|
||||
verifyBalance(true);
|
||||
|
||||
processor.process(settings.getContractByTopic(message.topic),
|
||||
extractInput(message),
|
||||
reply(message));
|
||||
const input = extractInput(message);
|
||||
const reply = replyFunction(message);
|
||||
let validationResult;
|
||||
|
||||
switch(input.action){
|
||||
case 'transaction':
|
||||
processor.processTransaction(settings.getContractByTopic(message.topic),
|
||||
input,
|
||||
reply);
|
||||
break;
|
||||
case 'availability':
|
||||
validationResult = await processor.processStrategy(settings.getContractByTopic(message.topic),
|
||||
input,
|
||||
reply,
|
||||
settings.buildStrategy("./strategy/AvailabilityStrategy", message.topic)
|
||||
);
|
||||
if(validationResult.success) reply(validationResult.message);
|
||||
|
||||
break;
|
||||
default:
|
||||
reply("unknown-action");
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
const Strategy = require('./BaseStrategy');
|
||||
|
||||
/**
|
||||
* Class representing a strategy to validate an 'availability' request.
|
||||
* @extends Strategy
|
||||
*/
|
||||
class AvailabilityStrategy extends Strategy {
|
||||
|
||||
/**
|
||||
* Process availability strategy
|
||||
* @param {object} input - Object obtained from an 'availability' request. It expects an object with this structure `{contract, address, action, gasToken, gasPrice}`
|
||||
* @returns {object} Status of validation, and minimum price
|
||||
*/
|
||||
execute(input){
|
||||
// Verifying if token is allowed
|
||||
const token = this.settings.getToken(input.gasToken);
|
||||
if(token == undefined) return {success: false, message: "Token not allowed"};
|
||||
|
||||
// TODO: Validate gasPrice, and return the minPrice accepted
|
||||
const minPrice = 0.00;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: {
|
||||
message: "Available",
|
||||
minPrice: minPrice
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = AvailabilityStrategy;
|
|
@ -2,7 +2,17 @@ const ganache = require("ganache-cli");
|
|||
const Web3 = require('web3');
|
||||
const erc20ABI = require('../../abi/ERC20Token.json');
|
||||
|
||||
/**
|
||||
* Abstract class used for validation strategies
|
||||
*/
|
||||
class BaseStrategy {
|
||||
|
||||
/**
|
||||
* @param {object} web3 - Web3 object already configured
|
||||
* @param {object} config - Configuration object obtained from `./config/config.js`
|
||||
* @param {object} settings - Settings obtained from parsing the configuration object
|
||||
* @param {object} contract - Object obtained from the settings based on the message topic
|
||||
*/
|
||||
constructor(web3, config, settings, contract){
|
||||
this.web3 = web3;
|
||||
this.settings = settings;
|
||||
|
@ -10,6 +20,12 @@ class BaseStrategy {
|
|||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the balance in tokens or ETH from an address
|
||||
* @param {string} address - ETH address to obtain the balance from
|
||||
* @param {object} token - Obtained from `settings.getToken(tokenSymbol)`
|
||||
* @returns {web3.utils.BN} Balance
|
||||
*/
|
||||
async getBalance(address, token){
|
||||
// Determining balances of token used
|
||||
if(token.symbol == "ETH"){
|
||||
|
@ -21,6 +37,11 @@ class BaseStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Parameters Function
|
||||
* @param {object} input - Object obtained from an `transaction` request.
|
||||
* @returns {function} Function that simplifies accessing contract functions' parameters
|
||||
*/
|
||||
_obtainParametersFunc(input){
|
||||
const parameterList = this.web3.eth.abi.decodeParameters(this.contract.allowedFunctions[input.functionName].inputs, input.functionParameters);
|
||||
return function(parameterName){
|
||||
|
@ -28,6 +49,11 @@ class BaseStrategy {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate gas using web3
|
||||
* @param {object} input - Object obtained from an `transaction` request.
|
||||
* @returns {web3.utils.toBN} Estimated gas fees
|
||||
*/
|
||||
async _estimateGas(input){
|
||||
let p = {
|
||||
from: this.config.node.blockchain.account,
|
||||
|
@ -40,6 +66,8 @@ class BaseStrategy {
|
|||
|
||||
/**
|
||||
* Simulate transaction using ganache. Useful for obtaining events
|
||||
* @param {object} input - Object obtained from an `transaction` request.
|
||||
* @returns {object} Simulated transaction receipt
|
||||
*/
|
||||
async _simulateTransaction(input){
|
||||
let web3Sim = new Web3(ganache.provider({
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
const Strategy = require('./BaseStrategy');
|
||||
const erc20ABI = require('../../abi/ERC20Token.json');
|
||||
|
||||
/**
|
||||
* Class representing a strategy to validate a `transaction` request when the topic is related to Identities.
|
||||
* @extends Strategy
|
||||
*/
|
||||
class IdentityStrategy extends Strategy {
|
||||
|
||||
/**
|
||||
* Validates if the contract being invoked represents an Identity instance
|
||||
* @param {object} input - Object obtained from a `transaction` request.
|
||||
* @returns {bool} Valid instance or not
|
||||
*/
|
||||
async _validateInstance(input){
|
||||
const instanceCodeHash = this.web3.utils.soliditySha3(await this.web3.eth.getCode(input.contract));
|
||||
const kernelVerifSignature = this.web3.utils.soliditySha3(this.contract.kernelVerification).slice(0, 10);
|
||||
|
@ -15,6 +24,11 @@ class IdentityStrategy extends Strategy {
|
|||
return this.web3.eth.abi.decodeParameter('bool', verificationResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Identity strategy
|
||||
* @param {object} input - Object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}`
|
||||
* @returns {object} Status of validation and estimated gas
|
||||
*/
|
||||
async execute(input){
|
||||
if(this.contract.isIdentity){
|
||||
let validInstance = await this._validateInstance(input);
|
||||
|
|
|
@ -3,9 +3,17 @@ const Strategy = require('./BaseStrategy');
|
|||
const TransferSNT = "0x916b6511";
|
||||
const ExecuteGasRelayed = "0x754e6ab0";
|
||||
|
||||
|
||||
/**
|
||||
* Class representing a strategy to validate a `transaction` request when the topic is related to SNTController.
|
||||
* @extends Strategy
|
||||
*/
|
||||
class SNTStrategy extends Strategy {
|
||||
|
||||
/**
|
||||
* Process SNTController strategy
|
||||
* @param {object} input - Object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}`
|
||||
* @returns {object} Status of validation and estimated gas
|
||||
*/
|
||||
async execute(input){
|
||||
const params = this._obtainParametersFunc(input);
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ class ApproveAndCallGasRelayed extends Component {
|
|||
gasLimit: 0,
|
||||
gasToken: "0x0000000000000000000000000000000000000000",
|
||||
signature: '',
|
||||
relayer: '',
|
||||
transactionError: '',
|
||||
messagingError: '',
|
||||
submitting: false
|
||||
|
@ -93,7 +94,7 @@ class ApproveAndCallGasRelayed extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
sendMessage = event => {
|
||||
obtainRelayers = event => {
|
||||
event.preventDefault();
|
||||
|
||||
const {web3, kid, skid} = this.props;
|
||||
|
@ -103,6 +104,50 @@ class ApproveAndCallGasRelayed extends Component {
|
|||
submitting: true
|
||||
});
|
||||
this.props.clearMessages();
|
||||
|
||||
try {
|
||||
const sendOptions = {
|
||||
ttl: 1000,
|
||||
sig: kid,
|
||||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: this.state.topic,
|
||||
symKeyID: skid,
|
||||
payload: web3.utils.toHex({
|
||||
'contract': this.props.identityAddress,
|
||||
'address': web3.eth.defaultAccount,
|
||||
'action': 'availability',
|
||||
'gasToken': this.state.gasToken,
|
||||
'gasPrice': this.state.gasPrice
|
||||
})
|
||||
};
|
||||
|
||||
web3.shh.post(sendOptions)
|
||||
.then(() => {
|
||||
this.setState({submitting: false});
|
||||
console.log("Message sent");
|
||||
return true;
|
||||
});
|
||||
} catch(error){
|
||||
this.setState({messagingError: error.message, submitting: false});
|
||||
}
|
||||
}
|
||||
|
||||
sendTransaction = event => {
|
||||
event.preventDefault();
|
||||
|
||||
const {web3, kid} = this.props;
|
||||
|
||||
let relayer = this.state.relayer;
|
||||
if(relayer == '' && this.props.relayers.length == 1){
|
||||
relayer = this.props.relayers[0];
|
||||
}
|
||||
|
||||
this.setState({
|
||||
messagingError: '',
|
||||
submitting: true
|
||||
});
|
||||
this.props.clearMessages();
|
||||
|
||||
|
||||
try {
|
||||
|
@ -124,11 +169,12 @@ class ApproveAndCallGasRelayed extends Component {
|
|||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: this.state.topic,
|
||||
symKeyID: skid,
|
||||
pubKey: relayer,
|
||||
payload: web3.utils.toHex({
|
||||
'contract': this.props.identityAddress,
|
||||
'encodedFunctionCall': funCall,
|
||||
'address': web3.eth.defaultAccount
|
||||
'address': web3.eth.defaultAccount,
|
||||
'action': 'transaction',
|
||||
'encodedFunctionCall': funCall
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -283,19 +329,10 @@ class ApproveAndCallGasRelayed extends Component {
|
|||
</Card>
|
||||
|
||||
{ this.state.messagingError && <MySnackbarContentWrapper variant="error" message={this.state.messagingError} /> }
|
||||
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title="2. Message" />
|
||||
<CardHeader title="2. Find Available Relayers" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
id="signature"
|
||||
label="Signed Message"
|
||||
value={this.state.signature}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
id="symKey"
|
||||
label="Symmetric Key"
|
||||
|
@ -317,8 +354,48 @@ class ApproveAndCallGasRelayed extends Component {
|
|||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" color="primary" onClick={this.sendMessage} disabled={this.state.submitting}>
|
||||
Send Message
|
||||
<Button size="small" color="primary" onClick={this.obtainRelayers} disabled={this.state.submitting}>
|
||||
Send "availability" Message
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title="3. Generate Transaction" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
id="relayer"
|
||||
label="Relayer"
|
||||
value={this.state.relayer}
|
||||
onChange={this.handleChange('relayer')}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select
|
||||
SelectProps={{
|
||||
native: true
|
||||
}}
|
||||
>
|
||||
{
|
||||
this.props.relayers.length > 0 ?
|
||||
this.props.relayers.map((r, i) => <option key={i} value={r}>Relayer #{i+1}: {r}</option>)
|
||||
:
|
||||
<option></option>
|
||||
}
|
||||
</TextField>
|
||||
<TextField
|
||||
id="signature"
|
||||
label="Signed Message"
|
||||
value={this.state.signature}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" color="primary" onClick={this.sendTransaction} disabled={this.state.submitting}>
|
||||
Send "transaction" Message
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
|
@ -334,7 +411,8 @@ ApproveAndCallGasRelayed.propTypes = {
|
|||
web3: PropTypes.object,
|
||||
kid: PropTypes.string,
|
||||
skid: PropTypes.string,
|
||||
clearMessages: PropTypes.func
|
||||
clearMessages: PropTypes.func,
|
||||
relayers: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ApproveAndCallGasRelayed);
|
||||
|
|
|
@ -27,7 +27,8 @@ class Body extends Component {
|
|||
nonce: '0',
|
||||
kid: null,
|
||||
skid: null,
|
||||
message: ''
|
||||
message: '',
|
||||
relayers: []
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -45,18 +46,31 @@ class Body extends Component {
|
|||
web3js.shh.addSymKey(config.relaySymKey)
|
||||
.then((skid) => {
|
||||
this.setState({kid, skid});
|
||||
|
||||
web3js.shh.subscribe('messages', {
|
||||
"privateKeyID": kid,
|
||||
"ttl": 1000,
|
||||
"minPow": 0.1,
|
||||
"powTime": 1000
|
||||
}, (error, message) => {
|
||||
console.log(message);
|
||||
console.log(message);
|
||||
|
||||
const msg = web3js.utils.toAscii(message.payload);
|
||||
const msgObj = JSON.parse(msg);
|
||||
|
||||
if(msgObj.message == 'Available'){
|
||||
// found a relayer
|
||||
console.log("Relayer available: " + message.sig);
|
||||
|
||||
let relayers = this.state.relayers;
|
||||
relayers.push(message.sig);
|
||||
relayers = relayers.filter((value, index, self) => self.indexOf(value) === index);
|
||||
this.setState({relayers});
|
||||
}
|
||||
|
||||
if(error){
|
||||
console.error(error);
|
||||
} else {
|
||||
this.setState({message: web3js.utils.toAscii(message.payload)});
|
||||
this.setState({message: msg});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -104,7 +118,7 @@ class Body extends Component {
|
|||
}
|
||||
|
||||
render(){
|
||||
const {tab, identityAddress, nonce, web3js, message, kid, skid} = this.state;
|
||||
const {tab, identityAddress, nonce, web3js, message, kid, skid, relayers} = this.state;
|
||||
|
||||
return <Fragment>
|
||||
<Tabs value={tab} onChange={this.handleChange}>
|
||||
|
@ -112,8 +126,8 @@ class Body extends Component {
|
|||
<Tab label="Approve and Call" />
|
||||
<Tab label="Deploy" />
|
||||
</Tabs>
|
||||
{tab === 0 && <Container><CallGasRelayed clearMessages={this.clearMessages} web3={web3js} kid={kid} skid={skid} nonce={nonce} identityAddress={identityAddress} /></Container>}
|
||||
{tab === 1 && <Container><ApproveAndCallGasRelayed clearMessages={this.clearMessages} web3={web3js} kid={kid} skid={skid} nonce={nonce} identityAddress={identityAddress} /></Container>}
|
||||
{tab === 0 && <Container><CallGasRelayed clearMessages={this.clearMessages} web3={web3js} kid={kid} skid={skid} nonce={nonce} identityAddress={identityAddress} relayers={relayers} /></Container>}
|
||||
{tab === 1 && <Container><ApproveAndCallGasRelayed clearMessages={this.clearMessages} web3={web3js} kid={kid} skid={skid} nonce={nonce} identityAddress={identityAddress} relayers={relayers} /></Container>}
|
||||
{tab === 2 && <Container>Item Three</Container>}
|
||||
<Divider />
|
||||
<Container>
|
||||
|
|
|
@ -28,7 +28,8 @@ class Body extends Component {
|
|||
nonce: '0',
|
||||
kid: null,
|
||||
skid: null,
|
||||
message: ''
|
||||
message: '',
|
||||
relayers: []
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -53,7 +54,21 @@ class Body extends Component {
|
|||
"minPow": 0.1,
|
||||
"powTime": 1000
|
||||
}, (error, message) => {
|
||||
console.log(message);
|
||||
console.log(message);
|
||||
|
||||
const msg = web3js.utils.toAscii(message.payload);
|
||||
const msgObj = JSON.parse(msg);
|
||||
|
||||
if(msgObj.message == 'Available'){
|
||||
// found a relayer
|
||||
console.log("Relayer available: " + message.sig);
|
||||
|
||||
let relayers = this.state.relayers;
|
||||
relayers.push(message.sig);
|
||||
relayers = relayers.filter((value, index, self) => self.indexOf(value) === index);
|
||||
this.setState({relayers});
|
||||
}
|
||||
|
||||
if(error){
|
||||
console.error(error);
|
||||
} else {
|
||||
|
@ -90,15 +105,15 @@ class Body extends Component {
|
|||
}
|
||||
|
||||
render(){
|
||||
const {tab, walletAddress, nonce, web3js, message, kid, skid} = this.state;
|
||||
const {tab, walletAddress, nonce, web3js, message, kid, skid, relayers} = this.state;
|
||||
|
||||
return <Fragment>
|
||||
<Tabs value={tab} onChange={this.handleChange}>
|
||||
<Tab label="Transfer SNT" />
|
||||
<Tab label="Execute" />
|
||||
</Tabs>
|
||||
{tab === 0 && <Container><TransferSNT clearMessages={this.clearMessages} web3={web3js} kid={kid} skid={skid} nonce={nonce} /></Container>}
|
||||
{tab === 1 && <Container><Execute clearMessage={this.clearMessages} web3={web3js} kid={kid} skid={skid} nonce={nonce} /></Container>}
|
||||
{tab === 0 && <Container><TransferSNT clearMessages={this.clearMessages} web3={web3js} kid={kid} skid={skid} nonce={nonce} relayers={relayers} /></Container>}
|
||||
{tab === 1 && <Container><Execute clearMessages={this.clearMessages} web3={web3js} kid={kid} skid={skid} nonce={nonce} relayers={relayers} /></Container>}
|
||||
<Divider />
|
||||
<Container>
|
||||
<Status message={message} nonceUpdateFunction={this.updateNonce} nonce={nonce} walletAddress={walletAddress} />
|
||||
|
|
|
@ -45,6 +45,7 @@ class CallGasRelayed extends Component {
|
|||
payload: '',
|
||||
message: '',
|
||||
web3js: null,
|
||||
relayer: '',
|
||||
transactionError: '',
|
||||
messagingError: '',
|
||||
submitting: false
|
||||
|
@ -97,7 +98,7 @@ class CallGasRelayed extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
sendMessage = event => {
|
||||
obtainRelayers = event => {
|
||||
event.preventDefault();
|
||||
|
||||
const {web3, kid, skid} = this.props;
|
||||
|
@ -108,6 +109,50 @@ class CallGasRelayed extends Component {
|
|||
});
|
||||
this.props.clearMessages();
|
||||
|
||||
try {
|
||||
const sendOptions = {
|
||||
ttl: 1000,
|
||||
sig: kid,
|
||||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: this.state.topic,
|
||||
symKeyID: skid,
|
||||
payload: web3.utils.toHex({
|
||||
'contract': this.props.identityAddress,
|
||||
'address': web3.eth.defaultAccount,
|
||||
'action': 'availability',
|
||||
'gasToken': this.state.gasToken,
|
||||
'gasPrice': this.state.gasPrice
|
||||
})
|
||||
};
|
||||
|
||||
web3.shh.post(sendOptions)
|
||||
.then(() => {
|
||||
this.setState({submitting: false});
|
||||
console.log("Message sent");
|
||||
return true;
|
||||
});
|
||||
} catch(error){
|
||||
this.setState({messagingError: error.message, submitting: false});
|
||||
}
|
||||
}
|
||||
|
||||
sendTransaction = event => {
|
||||
event.preventDefault();
|
||||
|
||||
const {web3, kid} = this.props;
|
||||
|
||||
let relayer = this.state.relayer;
|
||||
if(relayer == '' && this.props.relayers.length == 1){
|
||||
relayer = this.props.relayers[0];
|
||||
}
|
||||
|
||||
this.setState({
|
||||
messagingError: '',
|
||||
submitting: true
|
||||
});
|
||||
this.props.clearMessages();
|
||||
|
||||
try {
|
||||
let jsonAbi = IdentityGasRelay._jsonInterface.filter(x => x.name == "callGasRelayed")[0];
|
||||
let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [
|
||||
|
@ -126,14 +171,15 @@ class CallGasRelayed extends Component {
|
|||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: this.state.topic,
|
||||
symKeyID: skid,
|
||||
pubKey: relayer,
|
||||
payload: web3.utils.toHex({
|
||||
'contract': this.props.identityAddress,
|
||||
'encodedFunctionCall': funCall,
|
||||
'address': web3.eth.defaultAccount
|
||||
'address': web3.eth.defaultAccount,
|
||||
'action': 'transaction',
|
||||
'encodedFunctionCall': funCall
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
web3.shh.post(sendOptions)
|
||||
.then(() => {
|
||||
this.setState({submitting: false});
|
||||
|
@ -266,19 +312,10 @@ class CallGasRelayed extends Component {
|
|||
</Card>
|
||||
|
||||
{ this.state.messagingError && <MySnackbarContentWrapper variant="error" message={this.state.messagingError} /> }
|
||||
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title="2. Message" />
|
||||
<CardHeader title="2. Find Available Relayers" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
id="signature"
|
||||
label="Signed Message"
|
||||
value={this.state.signature}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
id="symKey"
|
||||
label="Symmetric Key"
|
||||
|
@ -300,8 +337,49 @@ class CallGasRelayed extends Component {
|
|||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" color="primary" onClick={this.sendMessage} disabled={this.state.submitting}>
|
||||
Send Message
|
||||
<Button size="small" color="primary" onClick={this.obtainRelayers} disabled={this.state.submitting}>
|
||||
Send "availability" Message
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
|
||||
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title="3. Generate Transaction" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
id="relayer"
|
||||
label="Relayer"
|
||||
value={this.state.relayer}
|
||||
onChange={this.handleChange('relayer')}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select
|
||||
SelectProps={{
|
||||
native: true
|
||||
}}
|
||||
>
|
||||
{
|
||||
this.props.relayers.length > 0 ?
|
||||
this.props.relayers.map((r, i) => <option key={i} value={r}>Relayer #{i+1}: {r}</option>)
|
||||
:
|
||||
<option></option>
|
||||
}
|
||||
</TextField>
|
||||
<TextField
|
||||
id="signature"
|
||||
label="Signed Message"
|
||||
value={this.state.signature}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" color="primary" onClick={this.sendTransaction} disabled={this.state.submitting}>
|
||||
Send "transaction" Message
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
|
@ -316,7 +394,8 @@ CallGasRelayed.propTypes = {
|
|||
web3: PropTypes.object,
|
||||
kid: PropTypes.string,
|
||||
skid: PropTypes.string,
|
||||
clearMessages: PropTypes.func
|
||||
clearMessages: PropTypes.func,
|
||||
relayers: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(CallGasRelayed);
|
||||
|
|
|
@ -41,6 +41,7 @@ class Execute extends Component {
|
|||
msgSent: '',
|
||||
payload: '',
|
||||
message: '',
|
||||
relayer: '',
|
||||
web3js: null,
|
||||
transactionError: '',
|
||||
messagingError: '',
|
||||
|
@ -99,11 +100,60 @@ class Execute extends Component {
|
|||
TestContract.methods.val().call().then(value => console.log({message: "TestContract.val(): " + value}));
|
||||
}
|
||||
|
||||
sendMessage = async event => {
|
||||
obtainRelayers = async event => {
|
||||
event.preventDefault();
|
||||
|
||||
const {web3, kid, skid} = this.props;
|
||||
|
||||
this.setState({
|
||||
messagingError: '',
|
||||
submitting: true
|
||||
});
|
||||
|
||||
this.props.clearMessages();
|
||||
|
||||
const accounts = await web3.eth.getAccounts();
|
||||
|
||||
|
||||
try {
|
||||
const sendOptions = {
|
||||
ttl: 1000,
|
||||
sig: kid,
|
||||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: this.state.topic,
|
||||
symKeyID: skid,
|
||||
payload: web3.utils.toHex({
|
||||
'contract': SNTController.options.address,
|
||||
'address': accounts[2],
|
||||
'action': 'availability',
|
||||
'gasToken': STT.options.address,
|
||||
'gasPrice': this.state.gasPrice
|
||||
})
|
||||
};
|
||||
|
||||
web3.shh.post(sendOptions)
|
||||
.then(() => {
|
||||
this.setState({submitting: false});
|
||||
console.log("Message sent");
|
||||
return true;
|
||||
});
|
||||
} catch(error){
|
||||
this.setState({messagingError: error.message, submitting: false});
|
||||
}
|
||||
}
|
||||
|
||||
sendTransaction = async event => {
|
||||
event.preventDefault();
|
||||
|
||||
const {web3, kid} = this.props;
|
||||
|
||||
let relayer = this.state.relayer;
|
||||
if(relayer == '' && this.props.relayers.length == 1){
|
||||
relayer = this.props.relayers[0];
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
messagingError: '',
|
||||
submitting: true
|
||||
|
@ -129,10 +179,11 @@ class Execute extends Component {
|
|||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: this.state.topic,
|
||||
symKeyID: skid,
|
||||
pubKey: relayer,
|
||||
payload: web3.utils.toHex({
|
||||
'contract': SNTController.options.address,
|
||||
'address': accounts[2],
|
||||
'action': 'transaction',
|
||||
'encodedFunctionCall': funCall
|
||||
})
|
||||
};
|
||||
|
@ -228,19 +279,10 @@ class Execute extends Component {
|
|||
</Card>
|
||||
|
||||
{ this.state.messagingError && <MySnackbarContentWrapper variant="error" message={this.state.messagingError} /> }
|
||||
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title="2. Message" />
|
||||
<CardHeader title="2. Find Available Relayers" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
id="signature"
|
||||
label="Signed Message"
|
||||
value={this.state.signature}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
id="symKey"
|
||||
label="Symmetric Key"
|
||||
|
@ -262,8 +304,47 @@ class Execute extends Component {
|
|||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" color="primary" onClick={this.sendMessage} disabled={this.state.submitting}>
|
||||
Send Message
|
||||
<Button size="small" color="primary" onClick={this.obtainRelayers} disabled={this.state.submitting}>
|
||||
Send "availability" Message
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title="3. Generate Transaction" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
id="relayer"
|
||||
label="Relayer"
|
||||
value={this.state.relayer}
|
||||
onChange={this.handleChange('relayer')}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select
|
||||
SelectProps={{
|
||||
native: true
|
||||
}}
|
||||
>
|
||||
{
|
||||
this.props.relayers.length > 0 ?
|
||||
this.props.relayers.map((r, i) => <option key={i} value={r}>Relayer #{i+1}: {r}</option>)
|
||||
:
|
||||
<option></option>
|
||||
}
|
||||
</TextField>
|
||||
<TextField
|
||||
id="signature"
|
||||
label="Signed Message"
|
||||
value={this.state.signature}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" color="primary" onClick={this.sendTransaction} disabled={this.state.submitting}>
|
||||
Send "transaction" Message
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
|
@ -278,7 +359,8 @@ Execute.propTypes = {
|
|||
web3: PropTypes.object,
|
||||
kid: PropTypes.string,
|
||||
skid: PropTypes.string,
|
||||
clearMessages: PropTypes.func
|
||||
clearMessages: PropTypes.func,
|
||||
relayers: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Execute);
|
||||
|
|
|
@ -41,6 +41,7 @@ class TransferSNT extends Component {
|
|||
msgSent: '',
|
||||
payload: '',
|
||||
message: '',
|
||||
relayer: '',
|
||||
web3js: null,
|
||||
transactionError: '',
|
||||
messagingError: '',
|
||||
|
@ -97,11 +98,57 @@ class TransferSNT extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
sendMessage = async event => {
|
||||
obtainRelayers = async event => {
|
||||
event.preventDefault();
|
||||
|
||||
const {web3, kid, skid} = this.props;
|
||||
|
||||
this.setState({
|
||||
messagingError: '',
|
||||
submitting: true
|
||||
});
|
||||
this.props.clearMessages();
|
||||
|
||||
const accounts = await web3.eth.getAccounts();
|
||||
|
||||
try {
|
||||
const sendOptions = {
|
||||
ttl: 1000,
|
||||
sig: kid,
|
||||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: this.state.topic,
|
||||
symKeyID: skid,
|
||||
payload: web3.utils.toHex({
|
||||
'contract': SNTController.options.address,
|
||||
'address': accounts[2],
|
||||
'action': 'availability',
|
||||
'gasToken': this.state.gasToken,
|
||||
'gasPrice': this.state.gasPrice
|
||||
})
|
||||
};
|
||||
|
||||
web3.shh.post(sendOptions)
|
||||
.then(() => {
|
||||
this.setState({submitting: false});
|
||||
console.log("Message sent");
|
||||
return true;
|
||||
});
|
||||
} catch(error){
|
||||
this.setState({messagingError: error.message, submitting: false});
|
||||
}
|
||||
}
|
||||
|
||||
sendTransaction = async event => {
|
||||
event.preventDefault();
|
||||
|
||||
const {web3, kid} = this.props;
|
||||
|
||||
let relayer = this.state.relayer;
|
||||
if(relayer == '' && this.props.relayers.length == 1){
|
||||
relayer = this.props.relayers[0];
|
||||
}
|
||||
|
||||
this.setState({
|
||||
messagingError: '',
|
||||
submitting: true
|
||||
|
@ -126,10 +173,11 @@ class TransferSNT extends Component {
|
|||
powTarget: 1,
|
||||
powTime: 20,
|
||||
topic: this.state.topic,
|
||||
symKeyID: skid,
|
||||
pubKey: relayer,
|
||||
payload: web3.utils.toHex({
|
||||
'contract': SNTController.options.address,
|
||||
'address': accounts[2],
|
||||
'action': 'transaction',
|
||||
'encodedFunctionCall': funCall
|
||||
})
|
||||
};
|
||||
|
@ -214,19 +262,10 @@ class TransferSNT extends Component {
|
|||
</Card>
|
||||
|
||||
{ this.state.messagingError && <MySnackbarContentWrapper variant="error" message={this.state.messagingError} /> }
|
||||
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title="2. Message" />
|
||||
<CardHeader title="2. Find Available Relayers" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
id="signature"
|
||||
label="Signed Message"
|
||||
value={this.state.signature}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
id="symKey"
|
||||
label="Symmetric Key"
|
||||
|
@ -248,8 +287,49 @@ class TransferSNT extends Component {
|
|||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" color="primary" onClick={this.sendMessage} disabled={this.state.submitting}>
|
||||
Send Message
|
||||
<Button size="small" color="primary" onClick={this.obtainRelayers} disabled={this.state.submitting}>
|
||||
Send "availability" Message
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
|
||||
|
||||
<Card className={classes.card}>
|
||||
<CardHeader title="3. Generate Transaction" />
|
||||
<CardContent>
|
||||
<TextField
|
||||
id="relayer"
|
||||
label="Relayer"
|
||||
value={this.state.relayer}
|
||||
onChange={this.handleChange('relayer')}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
select
|
||||
SelectProps={{
|
||||
native: true
|
||||
}}
|
||||
>
|
||||
{
|
||||
this.props.relayers.length > 0 ?
|
||||
this.props.relayers.map((r, i) => <option key={i} value={r}>Relayer #{i+1}: {r}</option>)
|
||||
:
|
||||
<option></option>
|
||||
}
|
||||
</TextField>
|
||||
<TextField
|
||||
id="signature"
|
||||
label="Signed Message"
|
||||
value={this.state.signature}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
readOnly: true
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button size="small" color="primary" onClick={this.sendTransaction} disabled={this.state.submitting}>
|
||||
Send "transaction" Message
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
|
@ -264,7 +344,8 @@ TransferSNT.propTypes = {
|
|||
web3: PropTypes.object,
|
||||
kid: PropTypes.string,
|
||||
skid: PropTypes.string,
|
||||
clearMessages: PropTypes.func
|
||||
clearMessages: PropTypes.func,
|
||||
relayers: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(TransferSNT);
|
||||
|
|
Loading…
Reference in New Issue