Executing public & private transactions on JPMorgan’s Quorum with Web3

By Lee Ruian, Thomas
This guide is intended for anyone interested in experimenting with Quorum. It is an introduction to deploying contracts and sending both public and private Quorum transactions using the web3.js library.
This article will cover:
- Formatting your smart contract
- Setting up your network / infrastructure with Chainstack
- Quorum Public Transactions
- Quorum Private Transactions
To better illustrate the mentioned features, we will introduce a simplified use case that covers a working implementation combining IoT and blockchain to monitor the participants’ storage facility temperature.
Background
A group of storage companies has decided to form a storage consortium to share information and automate processes on the blockchain. In this case, they have decided to use Quorum. In this tutorial, we will cover two use cases: public and private transactions.
Transactions are created by different parties to interact with one another within the consortium they belong to, each transaction will either deploy a contract or execute functions within that contract to upload data to the network. These updates will then be replicated across all nodes in the consortium.
Public transactions are designed to be publicly viewable by all parties within the consortium. Private transactions, on the other hand, provides an additional layer of privacy. It enables transactions and contracts to be accessible only by organisations that have been granted permission to do so.
We will use the same smart contract for both use cases to better explain how public and private transactions work.
Smart contract
Below is a simple smart contract that I’ve created for this use case. It has a public variable temperature, which can be modified using the set
method and fetched using the get
method.
pragma solidity ^0.4.25;
contract TemperatureMonitor {
int8 public temperature;
function set(int8 temp) public {
temperature = temp;
}
function get() view public returns (int8) {
return temperature;
}
}
For the contract to work with web3.js, it has to be first formatted into its respective ABI and bytecode formats. Using the function below called formatContract
compiles the contract using Ethereum’s solc-js compiler.
function formatContract() {
const path = './contracts/temperatureMonitor.sol';
const source = fs.readFileSync(path,'UTF8');
return solc.compile(source, 1).contracts[':TemperatureMonitor'];
}
The formatted contract should look something like this:
// interace
[
{
constant: true,
inputs: [],
name: ‘get’,
outputs: [Array],
payable: false,
stateMutability: ‘view’,
type: ‘function’
},
{
constant: true,
inputs: [],
name: ‘temperature’,
outputs: [Array],
payable: false,
stateMutability: ‘view’,
type: ‘function’
},
{
constant: false,
inputs: [Array],
name: ‘set’,
outputs: [],
payable: false,
stateMutability: ‘nonpayable’,
type: ‘function’
}
]
// bytecode
0x608060405234801561001057600080fd5b50610104806100206000396000f30060
806040526004361060525763ffffffff7c0100000000000000000000000000000000
0000000000000000000000006000350416636d4ce63c81146057578063adccea1214
6082578063faee13b9146094575b600080fd5b348015606257600080fd5b50606960
ae565b60408051600092830b90920b8252519081900360200190f35b348015608d57
600080fd5b50606960b7565b348015609f57600080fd5b5060ac60043560000b60c0
565b005b60008054900b90565b60008054900b81565b6000805491810b60ff1660ff
199092169190911790555600a165627a7a72305820af0086d55a9a4e6d52cb6b3967
afd764ca89df91b2f42d7bf3b30098d222e5c50029
Now that the contract is formatted and ready, we will move on to set up the blockchain infrastructure to deploy the contract.
Deploying the Quorum nodes
Deploying blockchain nodes requires deep technical expertise, especially when it comes to synchronising of nodes within a network through the command line interface (CLI). I believe that most people have had a tough time setting up, maintaining or troubleshooting their own blockchain networks.
Manual deployment is tedious for anyone who does not have the experience, interest, or the time to execute a long list of dependencies and protocol configurations. For such developers, I highly recommend using a blockchain-platform-as-a-service that time setting up, maintaining or troubleshooting their own blockchain.
The company that I work at, Chainstack, does that for you. The chainstack platform takes away the pain and frustration of learning how to quickly set up your decentralised network and maintaining the blockchain nodes. The platform is cloud-agnostic and multi-protocol. It is literally a one-stop solution that helps you not only experiment with multiple cloud and protocol configurations, but also launch and maintain a production grade blockchain network.
The Quorum explorer feature on Chainstack provides a better view of how blockchain and smart contracts work. Below are screenshots on how I used Chainstack to set up the Quorum Raft network with 3 nodes for this use case.
For starters, let’s create our first project. You can name it what you wish.


A default node will be created together with the network. We then proceed to add two more nodes to the network.



Now that the infrastructure is ready, we will go into depth explaining code, on how to deploy smart contracts and execute transactions using web3.js.
Public transactions
Background: Localised temperatures are a tremendous influence in cutting costs for heat-sensitive storage facilities, especially for sub-zero requirements. By enabling companies to share the ambient temperature of their geographical locations in real time and recording them on an immutable ledger, business participants are able to decide which area is optimal for heat-sensitive storage facilities without extended periods of market research.

We will execute 3 different tasks as shown on the diagram above:
1. Deploying smart contract through Node1
const contractAddress = await deployContract(raft1Node);
console.log(`Contract address after deployment: ${contractAddress}`);
2. Setting the temperature on Node2. This should update the temperature to 3 degrees.
const status = await setTemperature(raft2Node, contractAddress, 3);
console.log(`Transaction status: ${status}`);
3. Node3 retrieves the temperature from the smart contract; it should return 3 degrees.
const temp = await getTemperature(raft3Node, contractAddress);
console.log(‘Retrieved contract Temperature’, temp);
Below we will go into more details on how to execute public transactions on Quorum nodes with web3.js.
Initiate web3 instance with RPC for the 3 nodes:
const raft1Node = new Web3(
new Web3.providers.HttpProvider(process.env.RPC1), null, {
transactionConfirmationBlocks: 1,
},
);
const raft2Node = new Web3(
new Web3.providers.HttpProvider(process.env.RPC2), null, {
transactionConfirmationBlocks: 1,
},
);
const raft3Node = new Web3(
new Web3.providers.HttpProvider(process.env.RPC3), null, {
transactionConfirmationBlocks: 1,
},
);
Next we’ll deploy the smart contract:
// returns the default account from the Web3 instance initiated previously
function getAddress(web3) {
return web3.eth.getAccounts().then(accounts => accounts[0]);
}
// Deploys the contract using contract's interface and node's default address
async function deployContract(web3) {
const address = await getAddress(web3);
// initiate contract with contract's interface
const contract = new web3.eth.Contract(
temperatureMonitor.interface
);
return contract.deploy({
// deploy contract with contract's bytecode
data: temperatureMonitor.bytecode,
})
.send({
from: address,
gas: '0x2CD29C0',
})
.on('error', console.error)
.then((newContractInstance) => {
// returns deployed contract address
return newContractInstance.options.address;
});
}
web3.js provides two methods to interact with the contract: call
and send
. We can update the contract’s temperature by executing the set
method using web3’s send
method.
// get contract deployed previously
async function getContract(web3, contractAddress) {
const address = await getAddress(web3);
return web3.eth.Contract(
temperatureMonitor.interface,
contractAddress, {
defaultAccount: address,
}
);
}
// calls contract set method to update contract's temperature
async function setTemperature(web3, contractAddress, temp) {
const myContract = await getContract(web3, contractAddress);
return myContract.methods.set(temp).send({}).then((receipt) => {
return receipt.status;
});
}
Lastly, we will use web3’s call
method to fetch the contract’s temperature. Note that the call
method runs on the local node, hence no transaction on the blockchain will be created.
// calls contract get method to retrieve contract's temperature
async function getTemperature(web3, contractAddress) {
const myContract = await getContract(web3, contractAddress);
return myContract.methods.get().call().then(result => result);
}
Now we are ready to run the full public.js, which should show the following results.
// Execute public script
node public.js
Contract address after deployment: 0xf46141Ac7D6D6E986eFb2321756b5d1e8a25008F
Transaction status: true
Retrieved contract Temperature 3
Next, as shown below, we can access records via the Quorum explorer on Chainstack. All 3 nodes have interacted with the ledger, and the transactions were updated as expected:
- The first transaction deployed the contract
- The second transaction set the contract’s temperature to 3 degrees.
- Getting temperature reading interacts only with the local node which does not update the network, hence no block / transaction was created.

Private transactions
Background: A common business requirement is secured data encryption. Take an example where a supermarket rents storage solutions from a vendor for storage of perishables such as seafood:
- The vendor will transmit temperature readings every 30 seconds from its IoT devices for the supermarket to monitor.
- These readings should only be accessible between the supermarket and the vendor in the consortium network.

We will execute 4 different tasks as shown on the diagram above:
I will use the same 3 nodes from the previous scenario (Public transaction) to demonstrate Quorum’s private transaction feature: Supermarket deploys a smart contract that is private between Supermarket and Storage Facility. External Party will not have the permission to access the smart contract.
We call the get
and set
methods from both Storage Facility and External Party to demonstrate Quorum’s private transaction feature.
1. Deploying private contract for Supermarket and Storage Facility through Supermarket:
const contractAddress = await deployContract(
raft1Node,
process.env.PK2,
);
console.log(`Contract address after deployment: ${contractAddress}`);
2. Set temperature from External Party (external node) and fetch temperature:
The transaction will be successful despite not having access to the contract, but the setTemperature method will not mutate the temperature.
// Attempts to set Contract temperature to 10, this will not mutate contract's temperature
await setTemperature(
raft3Node,
contractAddress,
process.env.PK1,
10,
);
// This returns null
const temp = await getTemperature(raft3Node, contractAddress);
console.log(`[Node3] temp retrieved after updating contract from external nodes: ${temp}`);
3. Set
temperature from Storage Facility (internal node) and fetch temperature:
The temperature from the smart contract in this scenario should return 12.Note that Storage Facility has permissioned access to the contract.
// Updated Contract temperature to 12 degrees
await setTemperature(
raft2Node,
contractAddress,
process.env.PK1,
12,
);
// This returns 12
const temp2 = await getTemperature(raft2Node, contractAddress);
console.log(`[Node2] temp retrieved after updating contract from internal nodes: ${temp2}`);
4. Fetch Temperature from External Party (external node)
On step 3, the temperature was set to 12, but External Party has no access to the contract. The returned value should still be null.
// This returns null
const temp3 = await getTemperature(raft3Node, contractAddress);
console.log(`[Node3] temp retrieved from external nodes after update ${temp}`);
Below we will go into more details on how to execute private transactions on Quorum nodes with web3.js. Since most of the code are similar, I will only highlight the parts that are different from executing public transactions.
Note that any contract uploaded to the ledger is immutable, hence permissioned access has to be granted to the respective nodes by including its public key on contract deployment, not after.
async function deployContract(web3, publicKey) {
const address = await getAddress(web3);
const contract = new web3.eth.Contract(
temperatureMonitor.interface,
);
return contract.deploy({
data: temperatureMonitor.bytecode,
})
.send({
from: address,
gas: ‘0x2CD29C0’,
// Grant Permission to Contract by including nodes public keys
privateFor: [publicKey],
})
.then((contract) => {
return contract.options.address;
});
}
Similar to contract deployment, transactions are made private by including network participants’ public key on execution.
async function setTemperature(web3, contractAddress, publicKey, temp) {
const address = await getAddress(web3);
const myContract = await getContract(web3, contractAddress);
return myContract.methods.set(temp).send({
from: address,
// Grant Permission by including nodes public keys
privateFor: [publicKey],
}).then((receipt) => {
return receipt.status;
});
}
Now we are ready to run the full private.js, which should shows the following results.
node private.js
Contract address after deployment: 0x85dBF88B4dfa47e73608b33454E4e3BA2812B21D
[Node3] temp retrieved after updating contract from external nodes: null
[Node2] temp retrieved after updating contract from internal nodes: 12
[Node3] temp retrieved from external nodes after update null
Looking at the Quorum explorer below, you can see that 3 transactions were created.
- Deploying the Contract from Supermarket
- Executing the
SetTemperature
method from External party - Executing the
SetTemperature
method from Storage Facility

As you can see, both transactions went through, but only the transaction executed from Storage Facility managed to update the temperature on the contract. Hence, private transactions ensure the immutability of the data by internal parties and yet do not expose the data to external observers.
I’ve attended quite a number of blockchain events in Singapore, but most of the talks are not very developer focused. Speaking to fellow developers at these meetups, I’ve realised that most of them are looking for something more technical, guides that can help them get started with writing their own smart contract, deploy them on the network, experiment and learn from it.
Hence, I’ve written this article to share what I’ve learned at Chainstack, with the hope of helping fellow developers out there get started setting up their very own sandbox.
Explore Chainstack
Join our community of innovators
- To learn more about Chainstack, visit our Knowledge Center or join our Discord server and Telegram group.
- Sign up for a free Developer account, or explore the options offered by Growth or Business plans here.
- Take a look at our pricing tiers using a handy calculator to estimate usage and number of nodes.
Have you already explored what you can achieve with Chainstack? Get started for free today.