This commit is contained in:
2026-02-19 02:14:33 +01:00
commit 4f1c803b81
18 changed files with 5778 additions and 0 deletions

32
.gitignore vendored Normal file
View File

@@ -0,0 +1,32 @@
# Node modules
/node_modules
# Compilation output
/dist
# pnpm deploy output
/bundle
# Hardhat Build Artifacts
/artifacts
# Deployments by students
/ignition/deployments
# Hardhat compilation (v2) support directory
/cache
# Typechain output
/types
# Hardhat coverage reports
/coverage
# Environment variables
.env
# Exam archives
*.zip
# MacOS specific files
.DS_Store

24
README.md Normal file
View File

@@ -0,0 +1,24 @@
# Lab 1
This is a Viem and Hardhat 3 project. To get started, please read the instructions below.
## Getting started
To start the lab, run:
```shell
npm install
npm run start
```
## Finishing up
When you're done, run the following command to generate a zip archive.
```shell
npm run zip
```

15
contracts/Counter.sol Normal file
View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Counter {
uint256 public x;
function inc() public {
x++;
}
function incBy(uint256 by) public {
require(by > 0, "incBy: increment should be positive");
x += by;
}
}

View File

@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Subscription {
// your code goes here...
}

14
exam/common.js Normal file
View File

@@ -0,0 +1,14 @@
import fs from 'fs';
import { execSync } from 'child_process';
export function installDeps() {
// check if node_modules exists
if (fs.existsSync('./node_modules')) {
return;
}
// install dependencies
console.log('Installing dependencies...');
execSync('npm install', { stdio: 'inherit' });
console.log('Dependencies installed');
}

54
exam/exam.js Normal file
View File

@@ -0,0 +1,54 @@
import fs from 'fs';
import path from 'path';
import { installDeps } from './common.js';
const SERVER_PORT = 3018;
async function main() {
installDeps();
const parentDirectory = process.cwd();
const indexHtml = fs.readFileSync(path.join(parentDirectory, 'exam', 'index.html'), 'utf8');
const examTheory = fs.readFileSync(path.join(parentDirectory, 'exam', 'theory.json'), 'utf8');
let examTheoryData = JSON.parse(examTheory);
// import express and start the server
const express = await import('express');
const app = express.default();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get('/', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
res.send(indexHtml);
});
app.get('/theory', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.json(examTheoryData);
});
app.post('/theory-answer', (req, res) => {
const questionId = req.body.questionId;
const answer = req.body.answer;
const index = examTheoryData.findIndex(q => q.id === questionId);
if (index !== -1) {
examTheoryData[index].answer = answer;
fs.writeFileSync(path.join(parentDirectory, 'exam', 'theory.json'), JSON.stringify(examTheoryData, null, 2));
res.status(200).json({ success: true });
} else {
res.status(404).json({ success: false, error: 'Question not found' });
}
});
const open = await import('open');
app.listen(SERVER_PORT, () => {
console.log(`Server is running on port ${SERVER_PORT}`);
open.default(`http://localhost:${SERVER_PORT}/`);
});
}
main();

425
exam/index.html Normal file
View File

@@ -0,0 +1,425 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css"
>
<style>
[data-theme="dark"] {
--pico-background-color: #282a36; /* Custom color */
}
.app-header {
border-bottom: 1px solid var(--pico-muted-border-color);
box-shadow: 0 1px 0 var(--pico-muted-border-color);
}
[data-theme="light"] .app-header {
background-color: #fcfcfc;
}
[data-theme="dark"] .app-header {
background-color: rgba(9, 21, 21, 0.6);
}
[data-theme="dark"] .sidebar {
border-right: 3px solid rgba(9, 21, 21, 0.6);
}
.app-header nav {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 1.5rem;
}
.app-header hgroup {
margin: 0;
}
.app-header #theme-icon {
cursor: pointer;
user-select: none;
margin-left: 25px;
}
.app-root {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.app-body {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar {
width: 315px;
flex-shrink: 0;
padding: 1.5rem;
border-right: 1px solid var(--pico-muted-border-color);
overflow-y: auto;
}
.sidebar nav {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: stretch;
gap: 0;
}
.sidebar .sidebar-button {
text-align: left;
width: 100%;
padding: 0.75rem 1rem 0.75rem 0;
border-bottom: 1px solid var(--pico-muted-border-color);
text-decoration: none !important;
}
[data-theme="dark"] hgroup>:not(:first-child):last-child {
--pico-color: revert;
}
.main-content {
flex: 1;
padding: 1.5rem;
}
.main-content section.hidden {
display: none !important;
}
.main-content section p.mt-3 {
margin-top: 3rem;
}
.main-content section .theory-question:first-of-type {
margin-top: 2.5rem;
}
.main-content section .theory-question {
border-top: 1px solid var(--pico-muted-border-color);
padding-top: 1.5rem;
padding-bottom: 1rem;
margin-bottom: 0;
margin-top: 0;
}
.main-content section .theory-question p.question {
font-weight: 500;
margin-bottom: 0.5rem;
}
.main-content section hgroup p {
padding-top: 5px;
}
/* Mobile: hide sidebar, show desktop-only message */
@media (max-width: 768px) {
.sidebar {
display: none;
}
.main-content {
display: none;
}
.mobile-message {
display: flex !important;
}
}
.mobile-message {
display: none;
flex: 1;
align-items: center;
justify-content: center;
padding: 2rem;
text-align: center;
}
.mobile-message article {
max-width: 28rem;
}
</style>
<title>Lab 1</title>
</head>
<body>
<div class="app-root">
<header class="app-header">
<nav>
<ul>
<li>
<hgroup>
<h2>Lab 1</h2>
<p>Distributed Systems and Blockchain</p>
</hgroup>
</li>
</ul>
<ul>
<li>
<details class="dropdown">
<summary>
Documentation
</summary>
<ul>
<li><a href="https://api.next.code-camp.org/cdn/viem-docs.pdf" target="_blank">Viem</a></li>
<li><a href="https://api.next.code-camp.org/cdn/solidity-docs.pdf" target="_blank">Solidity</a></li>
</ul>
</details>
</li>
<li>
<span id="theme-icon" aria-hidden="true" onclick="cycleTheme()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 32 32" fill="currentColor" class="icon-theme-toggle "><clipPath id="theme-toggle-cutout"><path d="M0-11h25a1 1 0 0017 13v30H0Z"></path></clipPath><g clip-path="url(#theme-toggle-cutout)"><circle cx="16" cy="16" r="8.4"></circle><path d="M18.3 3.2c0 1.3-1 2.3-2.3 2.3s-2.3-1-2.3-2.3S14.7.9 16 .9s2.3 1 2.3 2.3zm-4.6 25.6c0-1.3 1-2.3 2.3-2.3s2.3 1 2.3 2.3-1 2.3-2.3 2.3-2.3-1-2.3-2.3zm15.1-10.5c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zM3.2 13.7c1.3 0 2.3 1 2.3 2.3s-1 2.3-2.3 2.3S.9 17.3.9 16s1-2.3 2.3-2.3zm5.8-7C9 7.9 7.9 9 6.7 9S4.4 8 4.4 6.7s1-2.3 2.3-2.3S9 5.4 9 6.7zm16.3 21c-1.3 0-2.3-1-2.3-2.3s1-2.3 2.3-2.3 2.3 1 2.3 2.3-1 2.3-2.3 2.3zm2.4-21c0 1.3-1 2.3-2.3 2.3S23 7.9 23 6.7s1-2.3 2.3-2.3 2.4 1 2.4 2.3zM6.7 23C8 23 9 24 9 25.3s-1 2.3-2.3 2.3-2.3-1-2.3-2.3 1-2.3 2.3-2.3z"></path></g></svg>
</span>
</li>
</ul>
</nav>
</header>
<div class="app-body">
<aside class="sidebar">
<nav>
<a href="#theory" class="sidebar-button">Theory</a>
<a href="#viem" class="sidebar-button">Coding - Viem</a>
<a href="#hardhat" class="sidebar-button">Coding - Hardhat</a>
</nav>
</aside>
<main class="main-content container" id="main-content">
<section id="theory">
<hgroup>
<h2>Theory</h1>
<p>Answer the following questions to the best of your ability. Each question has exactly one correct answer. There are no negative marks for incorrect answers.</p>
</hgroup>
<div id="theory-questions"></div>
</section>
<section id="viem" class="hidden">
<h2>Coding - Viem: Transaction Explorer</h1>
<p>In the repository you downloaded, there is a directory called <strong>/scripts</strong>. Inside, there is a file called <strong>transaction-explorer.ts</strong>, which is used to analyze a transaction on the main Ethereum network. Your job is to complete the script so that it prints the following information for each transaction:</p>
<ul>
<li>The transaction hash</li>
<li>The block number</li>
<li>The transaction sender (as an address)</li>
<li>The current balance (in ETH) of the sender (as read from the blockchain)</li>
<li>The transaction receiver (as an address)</li>
<li>The current balance (in ETH) of the receiver (as read from the blockchain)</li>
<li>The transaction amount (in ETH)</li>
<li>The transaction fee, i.e. the effective gas price multiplied by the gas used (shown in both ETH and Gwei)</li>
<li>The transaction status (for example, if it's successful or reverted)</li>
</ul>
<p class="mt-3">You can test the script by running the following command:</p>
<pre><code>npx tsx scripts/transaction-explorer.ts</code></pre>
<p class="mt-3">Here are two example transactions (one successful and one failed), which you can use to test your script:</p>
<div class="overflow-auto">
<table>
<thead>
<tr>
<th>Transaction Hash</th>
<th>Etherscan Link</th>
</tr>
</thead>
<tbody>
<tr>
<td style="word-break: break-all;">0x078a1bbbf7cb870db6fc02e7eaf843600754a6758a062cd85fe24ee065ac4e9c</td>
<td><a href="https://etherscan.io/tx/0x078a1bbbf7cb870db6fc02e7eaf843600754a6758a062cd85fe24ee065ac4e9c" target="_blank">View on Etherscan</a></td>
</tr>
</tbody>
<tbody>
<tr>
<td style="word-break: break-all;">0xf87645aa0740109eb9bd8a59525572faf39d508681c7ffd073f763fb94ceea58</td>
<td><a href="https://etherscan.io/tx/0xf87645aa0740109eb9bd8a59525572faf39d508681c7ffd073f763fb94ceea58" target="_blank">View on Etherscan</a></td>
</tr>
</tbody>
</table>
</div>
<p class="mt-3">Hint: you can start with the getTransaction function from the Viem library, and analyze its output with console.log(). Note that you will need to use additional functions to retrieve all the required information.</p>
</section>
<section id="hardhat" class="hidden">
<h2>Coding - Hardhat: Subscription Contract</h1>
<p>In the repository you downloaded, there is a directory called <strong>/contracts</strong> (for storing smart contracts), and a directory called <strong>/test</strong> (for test files). In the first directory, there is a file called <strong>Subscription.sol</strong> (where you need to write a smart contract in Solidity); while in the second directory, there is a file called <strong>Subscription.ts</strong> (where you need to write tests for the contract).</p>
<p>The contract is meant to be used by a company to track lifetime subscriptions for their customers. It needs to implement the following functionality:</p>
<ul>
<li>Ability for accounts to purchase a standard subscription for 1 ETH.</li>
<li>Ability for accounts who already have a subscription to upgrade to a premium subscription for +5 ETH.</li>
<li>Ability for the creator of the contract to withdraw the funds from the contract.</li>
<li>A view function to get the current subscription status of an address (which should return either: "standard", "premium", or "none").</li>
<li>The contract should validate the inputs and payments appropriately, and return errors if something is not correct.</li>
</ul>
<p class="mt-3">You can build the contracts by running the following command:</p>
<pre><code>npx hardhat build</code></pre>
<p class="mt-3">Once you start adding tests, you can run them with the following command:</p>
<pre><code>npx hardhat test</code></pre>
<p class="mt-3">Hint: you have an example contract and tests (see Counter.sol and Counter.ts) to help you get started. To optimize storage and gas, you should use an integer to store the subscription type in a mapping (or use two mappings which store a boolean).</p>
</section>
</main>
<div class="mobile-message">
<article>
<header>
<strong>Desktop required</strong>
</header>
<p>This exam layout needs a larger screen. Please open it on a desktop computer.</p>
</article>
</div>
</div>
</div>
<script>
(function () {
function setupTheme() {
var THEME_KEY = 'dsb-exam-theme';
var html = document.documentElement;
var icon = document.getElementById('theme-icon');
function applyTheme(theme) {
html.setAttribute('data-theme', theme);
try { localStorage.setItem(THEME_KEY, theme); } catch (e) {}
}
function cycleTheme(ev) {
ev.preventDefault();
var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
applyTheme(next);
}
try {
var saved = localStorage.getItem(THEME_KEY);
if (saved === 'light' || saved === 'dark') applyTheme(saved);
} catch (e) {}
if (icon) icon.addEventListener('click', cycleTheme);
}
function setupSidebar() {
var sidebar = document.querySelector('.sidebar');
var sidebarButtons = sidebar.querySelectorAll('.sidebar-button');
sidebarButtons.forEach(function(button) {
button.addEventListener('click', function() {
var target = button.getAttribute('href');
var targetSection = document.querySelector(target);
var sections = document.querySelectorAll('.main-content section');
if (targetSection) {
document.body.style.opacity = 0;
sections.forEach(function(section) {
section.classList.add('hidden');
});
targetSection.classList.remove('hidden');
setTimeout(function() {
window.scrollTo(0, 0);
document.body.style.opacity = 1;
}, 50);
}
});
});
}
function setupTheory() {
function setupTheoryCheckboxes() {
var theoryCheckboxes = document.querySelectorAll('.theory-question input[type="checkbox"]');
theoryCheckboxes.forEach(function(checkbox) {
checkbox.addEventListener('change', function() {
var question = checkbox.closest('.theory-question');
var questionId = question.getAttribute('id').split('-')[1];
var answer = checkbox.getAttribute('name').split('-')[2];
var otherCheckboxes = question.querySelectorAll('input[type="checkbox"]');
otherCheckboxes.forEach(function(otherCheckbox) {
const otherAnswer = otherCheckbox.getAttribute('name').split('-')[2];
if (otherAnswer !== answer) {
otherCheckbox.checked = false;
}
});
checkbox.checked = true;
fetch('/theory-answer', {
method: 'POST',
body: JSON.stringify({
questionId: Number(questionId),
answer: answer
}),
headers: {
'Content-Type': 'application/json'
}
})
.then(function(response) {
if (!response.ok) {
alert('Failed to submit answer.');
}
})
.catch(function(error) {
alert('Failed to submit answer.');
});
});
});
}
fetch('/theory')
.then(response => response.json())
.then(function(data) {
var theory = document.querySelector('#theory');
var html = '';
for (var i = 0; i < data.length; i++) {
var questionId = data[i].id;
var question = data[i].question;
var options = data[i].options;
var answer = data[i].answer;
html += '<div class="theory-question" id="question-' + questionId + '">';
html += '<p class="question">' + (i + 1) + '. ' + question + "</p>";
const sortedOptionKeys = Object.keys(options).sort();
html += '<fieldset>';
sortedOptionKeys.forEach(function(optionKey) {
html += '<label>';
html += '<input type="checkbox" name="question-' + questionId + '-' + optionKey + '"' + (answer === optionKey ? ' checked' : '') +'>';
html += ' ' + options[optionKey];
html += '</label>';
});
html += '</fieldset>';
html += '</div>';
}
document.querySelector('#theory-questions').innerHTML = html;
setupTheoryCheckboxes();
});
}
setupTheme();
setupSidebar();
setupTheory();
})();
</script>
</body>
</html>

122
exam/theory.json Normal file
View File

@@ -0,0 +1,122 @@
[
{
"id": 1,
"question": "What is a distributed system?",
"options": {
"A": "A single powerful computer running multiple programs",
"B": "A collection of multiple computers that appears as one system",
"C": "A system that only runs on cloud providers",
"D": "A decentralized blockchain network"
}
},
{
"id": 2,
"question": "What is horizontal scaling?",
"options": {
"A": "Adding more CPU and RAM to a single machine",
"B": "Replacing hardware with faster SSDs",
"C": "Adding more machines to improve performance",
"D": "Upgrading the operating system"
}
},
{
"id": 3,
"question": "Which is true about distributed systems compared to monolithic systems?",
"options": {
"A": "They are more resilient but more complex",
"B": "They always have a single point of failure",
"C": "They cannot scale horizontally",
"D": "They require downtime for maintenance"
}
},
{
"id": 4,
"question": "Which statement about distributed vs decentralized systems is correct?",
"options": {
"A": "Every distributed system is decentralized",
"B": "A decentralized system must be distributed",
"C": "A distributed system must have no central authority",
"D": "They are identical concepts"
}
},
{
"id": 5,
"question": "According to the Fallacies of Distributed Computing, which of these is a false assumption developers often make?",
"options": {
"A": "The network is reliable",
"B": "Latency is zero",
"C": "Bandwidth is infinite",
"D": "All of the above"
}
},
{
"id": 6,
"question": "In Proof of Work (PoW), miners:",
"options": {
"A": "Stake their coins",
"B": "Solve cryptographic puzzles",
"C": "Vote on transactions",
"D": "Approve all transactions"
}
},
{
"id": 7,
"question": "What is the smallest unit of Bitcoin?",
"options": {
"A": "Wei",
"B": "Gwei",
"C": "Ether",
"D": "Satoshi"
}
},
{
"id": 8,
"question": "What is Gas in Ethereum?",
"options": {
"A": "A blockchain explorer",
"B": "A wallet feature",
"C": "The fee required to execute transactions",
"D": "A mining pool"
}
},
{
"id": 9,
"question": "A crypto wallet actually stores:",
"options": {
"A": "The coins themselves",
"B": "A copy of the blockchain",
"C": "Private and public keys",
"D": "Mining rewards"
}
},
{
"id": 10,
"question": "With synchronous communication:",
"options": {
"A": "The sender does not wait",
"B": "The sender waits for a response",
"C": "All messages go through a broker",
"D": "There is no temporal coupling"
}
},
{
"id": 11,
"question": "In basic Pub/Sub, when a message is published to a topic:",
"options": {
"A": "The broker deletes it immediately",
"B": "The producer processes it",
"C": "Only one subscriber receives the message (Point-to-Point)",
"D": "All subscribers interested in that topic receive a copy"
}
},
{
"id": 12,
"question": "What is exponential backoff used for?",
"options": {
"A": "Waiting progressively longer between retries",
"B": "Synchronizing clocks",
"C": "Reducing memory usage",
"D": "Increasing blockchain throughput"
}
}
]

62
exam/zip.js Normal file
View File

@@ -0,0 +1,62 @@
import fs from 'fs';
import path from 'path';
import { installDeps } from './common.js';
import packageJson from '../package.json' with { type: 'json' };
async function main() {
installDeps();
const prompts = await import('prompts');
console.log(`Lets create a zip archive for your submission.`);
const { studentId } = await prompts.default({
type: 'text',
name: 'studentId',
message: 'What is your student ID?',
format: (value) => value.trim().replace(/[^A-Za-z0-9]/g, ''),
});
if (!studentId) {
console.error('Student ID is required');
process.exit(1);
}
const parentDirectory = process.cwd();
const archiveName = `${packageJson.name}-${studentId}.zip`;
const zipPath = path.join(parentDirectory, archiveName);
if (fs.existsSync(zipPath)) {
console.log(`Removing existing archive at ${zipPath}`);
fs.unlinkSync(zipPath);
}
const archiver = await import('archiver');
const archive = archiver.default('zip');
archive.on('error', (err) => {
console.error(err);
throw err;
});
archive.glob('**/*', {
cwd: parentDirectory,
ignore: [
'node_modules/**',
'.git/**',
'artifacts/**',
'cache/**',
'.env',
'.DS_Store',
'*.zip',
'exam/common.js',
'exam/exam.js',
'exam/index.html',
'exam/zip.js'
]
});
archive.pipe(fs.createWriteStream(zipPath));
await archive.finalize();
console.log();
console.log(`Archive created: ${zipPath}`);
}
main();

49
hardhat.config.ts Normal file
View File

@@ -0,0 +1,49 @@
import "dotenv/config";
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { configVariable, defineConfig } from "hardhat/config";
export default defineConfig({
plugins: [hardhatToolboxViemPlugin],
solidity: {
profiles: {
default: {
version: "0.8.28",
},
production: {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
},
},
networks: {
hardhatMainnet: {
type: "edr-simulated",
chainType: "l1",
},
hardhatOp: {
type: "edr-simulated",
chainType: "op",
},
sepolia: {
type: "http",
chainType: "l1",
url: configVariable("SEPOLIA_RPC_URL"),
accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
},
next: {
type: "http",
chainId: 1337,
chainType: "l1",
url: "https://eth.code-camp.org",
accounts: {
mnemonic: configVariable("MNEMONICS"),
},
},
},
});

View File

@@ -0,0 +1,6 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("CounterModule", (m) => {
const counter = m.contract("Counter");
return { counter };
});

4841
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "dsb-lab1",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node exam/exam.js",
"zip": "node exam/zip.js"
},
"devDependencies": {
"@nomicfoundation/hardhat-ignition": "^3.0.7",
"@nomicfoundation/hardhat-toolbox-viem": "^5.0.2",
"@types/node": "^22.19.11",
"@types/prompts": "^2.4.9",
"forge-std": "github:foundry-rs/forge-std#v1.9.4",
"hardhat": "^3.1.8",
"typescript": "~5.8.0",
"viem": "^2.46.1"
},
"dependencies": {
"archiver": "^7.0.1",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"open": "^11.0.0",
"prompts": "^2.4.2"
}
}

View File

@@ -0,0 +1,32 @@
import prompts from 'prompts';
async function main() {
const { transactionHash }: { transactionHash: `0x${string}` } = await prompts({
type: 'text',
name: 'transactionHash',
message: 'Enter the transaction hash to explore:',
});
if (!transactionHash) {
console.error('Transaction hash is required');
return;
}
console.log(`Transaction hash: ${transactionHash}`);
console.log(`========================================`);
console.log(`Block number: ...`);
console.log(`Sender: ...`);
console.log(`Sender balance: ...`);
console.log(`Recipient: ...`);
console.log(`Recipient balance: ...`);
console.log(`Amount: ...`);
console.log(`Transaction fee: ...`);
console.log(`Transaction status: ...`);
}
main();

45
test/Counter.ts Normal file
View File

@@ -0,0 +1,45 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { network } from "hardhat";
describe("Counter", async function () {
const { viem } = await network.connect();
const publicClient = await viem.getPublicClient();
it("The value should match the sum of the increments", async function () {
const counter = await viem.deployContract("Counter");
await counter.write.inc();
// run a series of increments
// 3+...+10 = 52
for (let i = 3n; i <= 10n; i++) {
await counter.write.incBy([i]);
}
// read the value
const value = await counter.read.x();
assert.equal(value, 1n + 52n);
});
it("Demo reading from a public client with Viem", async function () {
const counter = await viem.deployContract("Counter");
await counter.write.inc();
await counter.write.inc();
await counter.write.inc();
// read the value
const value = await publicClient.readContract({
address: counter.address,
abi: counter.abi,
functionName: "x",
});
assert.equal(value, 3n);
});
});

5
test/Subscription.ts Normal file
View File

@@ -0,0 +1,5 @@
import { describe, it } from "node:test";
describe("Subscription", async function () {
// your tests go here...
})

5
test/accounts/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export const account1 = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
export const account2 = '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
export const account3 = '0xcccccccccccccccccccccccccccccccccccccccc';
export const account4 = '0xdddddddddddddddddddddddddddddddddddddddd';
export const account5 = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';

13
tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */
{
"compilerOptions": {
"lib": ["es2023"],
"module": "node16",
"target": "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "node16",
"outDir": "dist"
}
}