namillum
Version:
Bubble Protocol SDK
336 lines (287 loc) • 10.3 kB
Markdown
---
sidebar_position: 5
---
# Summary
We've met all the objectives set out in 300 lines of easy to read and understand Javascript and now have a form of primitive automation.
- [x] Use zilliqa-js to deploy a contract.
- [x] Use zilliqa-js to interact with and press the button.
- [x] Use zilliqa-js to get the mutable state of our contract.
- [x] Use zilliqa-js to listen for events in our contract.
```js
const { BN, Long, bytes, units } = require("@zilliqa-js/util");
const { Zilliqa } = require("@zilliqa-js/zilliqa");
const { StatusType, MessageType } = require("@zilliqa-js/subscriptions");
const {
fromBech32Address,
toBech32Address,
getAddressFromPrivateKey,
} = require("@zilliqa-js/crypto");
// these values can change depending on what network you are connecting on
const zilliqa = new Zilliqa("https://dev-api.zilliqa.com");
// webhook to listen to events emited back to clients
const websocket = "wss://dev-ws.zilliqa.com";
const chainId = 333;
const msgVersion = 1;
const VERSION = bytes.pack(chainId, msgVersion);
// use a dotenv file for storing private keys, do not commit this directly to your repo
const privateKey = "";
// mutable state field name to monitor
const state_field_to_monitor = "total_count_clicks";
zilliqa.wallet.addByPrivateKey(privateKey);
const address_from_pk = getAddressFromPrivateKey(privateKey);
// Application Definition
// DEPLOY a contract,
// SEND a transaction
// LISTEN to events emitted
// MONITOR mutable state
async function DeploySendListenMonitor() {
deployed_contract_base_16 = await DeployButtonContract();
bech_32_bystr = toBech32Address(deployed_contract_base_16);
console.log(`got ${bech_32_bystr} from ${deployed_contract_base_16}`);
await Promise.all([
ListenForEvents(deployed_contract_base_16),
PressTheButton(bech_32_bystr),
]);
}
// Runs Application
DeploySendListenMonitor();
// Deploys a contract using a private key, returns the deployed_contract_base_16 of the contract deployed
async function DeployButtonContract() {
try {
// Get Balance
const balance = await zilliqa.blockchain.getBalance(address_from_pk);
// Get Minimum Gas Price from blockchain
const minGasPrice = await zilliqa.blockchain.getMinimumGasPrice();
// Account balance (See note 1)
console.log(`Your account balance is:`);
console.log(balance.result);
console.log(`Current Minimum Gas Price: ${minGasPrice.result}`);
const myGasPrice = units.toQa("2000", units.Units.Li); // Gas Price that will be used by all transactions
console.log(`My Gas Price ${myGasPrice.toString()}`);
const isGasSufficient = myGasPrice.gte(new BN(minGasPrice.result)); // Checks if your gas price is less than the minimum gas price
console.log(`Is the gas price sufficient? ${isGasSufficient}`);
// Deploy a contract
console.log(`Deploying a new contract....`);
const code = `
scilla_version 0
library MyFirstButton
let uint128_zero = Uint128 0
let uint128_one = Uint128 1
let empty_bystr = 0x1111111111111111111111111111111111111111
(* Error exception *)
type Error =
| NotContractOwner
| NotUniqueClicker
let make_error =
fun (result : Error) =>
let result_code =
match result with
| NotContractOwner => Int32 -1
| NotUniqueClicker => Int32 -2
end
in
{ _exception : "Error"; code : result_code }
(*
* Create a scilla contract which models a button that can be pressed by anyone.
* If you are the recent button presser you may not press the button again.
* When the button is pressed: Increment a counter and set the caller of the button press to be the most recent address.
* The owner of the button has the ability to reset the counter to zero
*)
contract MyFirstButton
(
contract_owner: ByStr20
)
field current_clicker : ByStr20 = empty_bystr
field total_count_clicks : Uint128 = uint128_zero
(*
: Emit Errors
*)
procedure ThrowError(err : Error)
e = make_error err;
throw e
end
(*
: Throws an error if '_sender' is not 'contract_owner'
*)
procedure IsContractOwner()
is_contract_owner = builtin eq contract_owner _sender;
match is_contract_owner with
| True =>
(* No Operation - Continue contract execution *)
| False =>
err = NotContractOwner;
ThrowError err
end
end
(*
Dev: Increments 'current_clicker' by 'uint128_one'
*)
procedure IncrementCounter()
previous_click_count <- total_count_clicks;
new_click_count = builtin add previous_click_count uint128_one;
total_count_clicks := new_click_count;
e = {_eventname : "IncrementCounterSuccess"; pcc:previous_click_count; ncc: new_click_count };
event e
end
(*
Dev: Throws an error if the current '_sender' is the previous 'current_clicker'
*)
procedure IsPreviousClicker(new_clicker: ByStr20)
previous_clicker <- current_clicker;
is_previous_clicker = builtin eq previous_clicker _sender;
match is_previous_clicker with
| True =>
err = NotUniqueClicker;
ThrowError err
| False =>
(* No Operation - Continue contract execution *)
end
end
(*
Dev: Sets 'new_clicker' as 'current_clicker'
*)
procedure SetNewClicker(new_clicker: ByStr20)
current_clicker := new_clicker;
e = {_eventname : "NewClickerState"; nc:new_clicker};
event e
end
(*
Dev: Resets 'current_clicker' to 'uint128_zero'
*)
procedure ContractOwnerResetButton()
total_count_clicks := uint128_zero
end
(*
Dev: Sets 'new_clicker' as current_clicker
*)
transition PressTheButton()
IsPreviousClicker _sender;
SetNewClicker _sender;
IncrementCounter;
e = {_eventname : "PressTheButtonSuccess"; button_presser : _sender };
event e
end
(*
Dev: Sets 'new_clicker' as current_clicker
*)
transition OwnerResetButton()
IsContractOwner;
ContractOwnerResetButton
end
`;
const init = [
// this parameter is mandatory for all init arrays
{
vname: "_scilla_version",
type: "Uint32",
value: "0",
},
{
vname: "contract_owner",
type: "ByStr20",
value: `${address_from_pk}`,
},
];
const contract = zilliqa.contracts.new(code, init);
// Deploy the contract.
// Also notice here we have a default function parameter named toDs as mentioned above.
// A contract can be deployed at either the shard or at the DS. Always set this value to false.
const [deployTx, deployedContract] = await contract.deployWithoutConfirm(
{
version: VERSION,
gasPrice: myGasPrice,
gasLimit: Long.fromNumber(10000),
},
false
);
// process confirm
console.log(`The transaction id is:`, deployTx.id);
console.log(`Waiting transaction be confirmed`);
const confirmedTxn = await deployTx.confirm(deployTx.id);
console.log(`The transaction status is:`);
console.log(confirmedTxn.receipt);
if (confirmedTxn.receipt.success === true) {
console.log(`Contract address is: 0x${deployedContract.address}`);
return "0x" + deployedContract.address;
}
} catch (err) {
console.log(err);
}
}
// Listen for events from a contract - errors aren't caught
async function ListenForEvents(deployed_contract_base_16) {
const subscriber = zilliqa.subscriptionBuilder.buildEventLogSubscriptions(
websocket,
{
addresses: [deployed_contract_base_16],
}
);
console.log("Listener started");
subscriber.emitter.on(MessageType.EVENT_LOG, async (event) => {
console.log("get new event log: ", JSON.stringify(event)); // this will emit 2/3 times before event emitted
const current_button_click_count =
await zilliqa.blockchain.getSmartContractSubState(
deployed_contract_base_16,
state_field_to_monitor
);
console.log(current_button_click_count.result);
console.log("");
});
await subscriber.start();
}
// Calls the previously deployed contract transition
async function PressTheButton(bech_32_bystr) {
try {
const balance = await zilliqa.blockchain.getBalance(address_from_pk);
const minGasPrice = await zilliqa.blockchain.getMinimumGasPrice();
const myGasPrice = units.toQa("2000", units.Units.Li);
const isGasSufficient = myGasPrice.gte(new BN(minGasPrice.result));
const deployedContract = zilliqa.contracts.at(bech_32_bystr);
console.log(`Pressing the button...`);
const callTx = await deployedContract.callWithoutConfirm(
"PressTheButton", // transition name
[], // no vnames
{
version: VERSION,
amount: new BN(0),
gasPrice: myGasPrice,
gasLimit: Long.fromNumber(8000),
},
false
);
console.log(`Waiting transaction be confirmed`);
const confirmedTxn = await callTx.confirm(callTx.id);
//console.log(`The transaction status is:`);
//console.log(confirmedTxn.receipt);
if (confirmedTxn.receipt.success === true) {
console.log(`Button pressed by : ${address_from_pk}`);
}
} catch (err) {
console.log(err);
}
}
```
```js
Your account balance is:
{ balance: '1990534999999850', nonce: 307 }
Current Minimum Gas Price: 2000000000
My Gas Price 2000000000
Is the gas price sufficient? true
Deploying a new contract....
The transaction id is: 1548964c7d852fdb5c2b606a62aba83a5e89ce3ec047c3eac8986b0943e10da7
Waiting transaction be confirmed
The transaction status is:
{ cumulative_gas: 401, epoch_num: '3726858', success: true }
Contract address is: 0x60d5edfe4bcff11bde238c153853bcf358654b41
got zil1vr27mljtelc3hh3r3s2ns5au7dvx2j6pa6p9k0 from 0x60d5edfe4bcff11bde238c153853bcf358654b41
Listener started
Pressing the button...
Waiting transaction be confirmed
get new event log: {"query":"EventLog"}
{ total_count_clicks: '0' }
get new event log: {"query":"EventLog","value":[{"address":"60d5edfe4bcff11bde238c153853bcf358654b41","event_logs":[{"_eventname":"PressTheButtonSuccess","params":[{"type":"ByStr20","value":"0x428a2aa43456fe7fd2de66e48c1fbf372ec10eae","vname":"button_presser"}]},{"_eventname":"IncrementCounterSuccess","params":[{"type":"Uint128","value":"0","vname":"pcc"},{"type":"Uint128","value":"1","vname":"ncc"}]},{"_eventname":"NewClickerState","params":[{"type":"ByStr20","value":"0x428a2aa43456fe7fd2de66e48c1fbf372ec10eae","vname":"nc"}]}]}]}
{ total_count_clicks: '1' }
Button pressed by : 0x428A2aA43456FE7fd2De66E48C1fBf372eC10eAE
get new event log: {"query":"EventLog"}
{ total_count_clicks: '1' }
```