Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 108 additions & 88 deletions .ai/categories/smart-contracts.md

Large diffs are not rendered by default.

196 changes: 108 additions & 88 deletions .ai/categories/tooling.md

Large diffs are not rendered by default.

196 changes: 108 additions & 88 deletions .ai/pages/smart-contracts-libraries-ethers-js.md

Large diffs are not rendered by default.

17 changes: 6 additions & 11 deletions .ai/site-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -8477,7 +8477,7 @@
],
"raw_md_url": "https://raw.githubusercontent.com/polkadot-developers/polkadot-docs/master/.ai/pages/smart-contracts-libraries-ethers-js.md",
"html_url": "https://docs.polkadot.com/smart-contracts/libraries/ethers-js/",
"preview": "!!! smartcontract \"PolkaVM Preview Release\" PolkaVM smart contracts with Ethereum compatibility are in **early-stage development and may be unstable or incomplete**. ## Introduction",
"preview": "[Ethers.js](https://docs.ethers.org/v6/){target=\\_blank} is a lightweight library that enables interaction with Ethereum Virtual Machine (EVM)-compatible blockchains through JavaScript. Ethers is widely used as a toolkit to establish connections and read and write blockchain data. This article demonstrates using Ethers.js to interact and deploy smart contracts to Polkadot Hub.",
"outline": [
{
"depth": 2,
Expand Down Expand Up @@ -8514,11 +8514,6 @@
"title": "Compile Contracts",
"anchor": "compile-contracts"
},
{
"depth": 3,
"title": "Install the Revive Library",
"anchor": "install-the-revive-library"
},
{
"depth": 3,
"title": "Sample Storage Smart Contract",
Expand Down Expand Up @@ -8546,12 +8541,12 @@
}
],
"stats": {
"chars": 20457,
"words": 2333,
"headings": 13,
"estimated_token_count_total": 4474
"chars": 21413,
"words": 2435,
"headings": 12,
"estimated_token_count_total": 4762
},
"hash": "sha256:c74a28d8d62369591c5734535136508db3d1f7380e486fd214f98d433cafd6e7",
"hash": "sha256:121e52bd405e70bde9288d65557266fbaa0dbca290bdfebc67de8105d9672785",
"token_estimator": "heuristic-v1"
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const { ethers } = require('ethers');
const { readFileSync } = require('fs');
const { join } = require('path');

const artifactsDir = join(__dirname, '../contracts');

const createProvider = (providerConfig) => {
return new ethers.JsonRpcProvider(providerConfig.rpc, {
chainId: providerConfig.chainId,
Expand All @@ -13,7 +15,7 @@ const createWallet = (mnemonic, provider) => {
return ethers.Wallet.fromPhrase(mnemonic).connect(provider);
};

const loadContractAbi = (contractName, directory = __dirname) => {
const loadContractAbi = (contractName, directory = artifactsDir) => {
const contractPath = join(directory, `${contractName}.json`);
const contractJson = JSON.parse(readFileSync(contractPath, 'utf8'));
return contractJson.abi || contractJson; // Depending on JSON structure
Expand Down Expand Up @@ -69,9 +71,9 @@ const providerConfig = {
chainId: 420420422,
};

const mnemonic = 'INSERT_MNEMONIC';
const mnemonic = 'INSERT_MNEMONIC'
const contractName = 'Storage';
const contractAddress = 'INSERT_CONTRACT_ADDRESS';
const contractAddress = 'INSERT_CONTRACT_ADDRESS'
const newNumber = 42;

interactWithStorageContract(
Expand All @@ -80,4 +82,4 @@ interactWithStorageContract(
mnemonic,
providerConfig,
newNumber,
);
);
81 changes: 58 additions & 23 deletions .snippets/code/smart-contracts/libraries/ethers-js/compile.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,71 @@
const { compile } = require('@parity/resolc');
const { readFileSync, writeFileSync } = require('fs');
const solc = require('solc');
const { readFileSync, writeFileSync, mkdirSync, existsSync } = require('fs');
const { basename, join } = require('path');

const compileContract = async (solidityFilePath, outputDir) => {
const ensureDir = (dirPath) => {
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
}
};

const compileContract = (solidityFilePath, abiDir, artifactsDir) => {
try {
// Read the Solidity file
const source = readFileSync(solidityFilePath, 'utf8');

// Construct the input object for the compiler
const fileName = basename(solidityFilePath);

// Construct the input object for the Solidity compiler
const input = {
[basename(solidityFilePath)]: { content: source },
language: 'Solidity',
sources: {
[fileName]: {
content: source,
},
},
settings: {
outputSelection: {
'*': {
'*': ['abi', 'evm.bytecode'],
},
},
},
};

console.log(`Compiling contract: ${basename(solidityFilePath)}...`);

console.log(`Compiling contract: ${fileName}...`);
// Compile the contract
const out = await compile(input);

for (const contracts of Object.values(out.contracts)) {
for (const [name, contract] of Object.entries(contracts)) {
console.log(`Compiled contract: ${name}`);
const output = JSON.parse(solc.compile(JSON.stringify(input)));

// Check for errors
if (output.errors) {
const errors = output.errors.filter(error => error.severity === 'error');
if (errors.length > 0) {
console.error('Compilation errors:');
errors.forEach(err => console.error(err.formattedMessage));
return;
}
// Show warnings
const warnings = output.errors.filter(error => error.severity === 'warning');
warnings.forEach(warn => console.warn(warn.formattedMessage));
}

// Ensure output directories exist
ensureDir(abiDir);
ensureDir(artifactsDir);

// Process compiled contracts
for (const [sourceFile, contracts] of Object.entries(output.contracts)) {
for (const [contractName, contract] of Object.entries(contracts)) {
console.log(`Compiled contract: ${contractName}`);

// Write the ABI
const abiPath = join(outputDir, `${name}.json`);
const abiPath = join(abiDir, `${contractName}.json`);
writeFileSync(abiPath, JSON.stringify(contract.abi, null, 2));
console.log(`ABI saved to ${abiPath}`);

// Write the bytecode
const bytecodePath = join(outputDir, `${name}.polkavm`);
writeFileSync(
bytecodePath,
Buffer.from(contract.evm.bytecode.object, 'hex'),
);
const bytecodePath = join(artifactsDir, `${contractName}.bin`);
writeFileSync(bytecodePath, contract.evm.bytecode.object);
console.log(`Bytecode saved to ${bytecodePath}`);
}
}
Expand All @@ -41,6 +75,7 @@ const compileContract = async (solidityFilePath, outputDir) => {
};

const solidityFilePath = join(__dirname, '../contracts/Storage.sol');
const outputDir = join(__dirname, '../contracts');
const abiDir = join(__dirname, '../abis');
const artifactsDir = join(__dirname, '../artifacts');

compileContract(solidityFilePath, outputDir);
compileContract(solidityFilePath, abiDir, artifactsDir);
31 changes: 14 additions & 17 deletions .snippets/code/smart-contracts/libraries/ethers-js/deploy.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Deploy an EVM-compatible smart contract using ethers.js
const { writeFileSync, existsSync, readFileSync } = require('fs');
const { join } = require('path');
const { ethers, JsonRpcProvider } = require('ethers');

const codegenDir = join(__dirname);
const scriptsDir = __dirname;
const artifactsDir = join(__dirname, '../contracts');

// Creates an Ethereum provider with specified RPC URL and chain details
// Creates a provider with specified RPC URL and chain details
const createProvider = (rpcUrl, chainId, chainName) => {
const provider = new JsonRpcProvider(rpcUrl, {
chainId: chainId,
Expand All @@ -17,9 +17,8 @@ const createProvider = (rpcUrl, chainId, chainName) => {
// Reads and parses the ABI file for a given contract
const getAbi = (contractName) => {
try {
return JSON.parse(
readFileSync(join(codegenDir, `${contractName}.json`), 'utf8'),
);
const abiPath = join(artifactsDir, `${contractName}.json`);
return JSON.parse(readFileSync(abiPath, 'utf8'));
} catch (error) {
console.error(
`Could not find ABI for contract ${contractName}:`,
Expand All @@ -32,12 +31,10 @@ const getAbi = (contractName) => {
// Reads the compiled bytecode for a given contract
const getByteCode = (contractName) => {
try {
const bytecodePath = join(
codegenDir,
'../contracts',
`${contractName}.polkavm`,
);
return `0x${readFileSync(bytecodePath).toString('hex')}`;
const bytecodePath = join(artifactsDir, `${contractName}.bin`);
const bytecode = readFileSync(bytecodePath, 'utf8').trim();
// Add 0x prefix if not present
return bytecode.startsWith('0x') ? bytecode : `0x${bytecode}`;
} catch (error) {
console.error(
`Could not find bytecode for contract ${contractName}:`,
Expand All @@ -49,7 +46,6 @@ const getByteCode = (contractName) => {

const deployContract = async (contractName, mnemonic, providerConfig) => {
console.log(`Deploying ${contractName}...`);

try {
// Step 1: Set up provider and wallet
const provider = createProvider(
Expand All @@ -73,10 +69,11 @@ const deployContract = async (contractName, mnemonic, providerConfig) => {
const address = await contract.getAddress();
console.log(`Contract ${contractName} deployed at: ${address}`);

const addressesFile = join(codegenDir, 'contract-address.json');
const addressesFile = join(scriptsDir, 'contract-address.json');
const addresses = existsSync(addressesFile)
? JSON.parse(readFileSync(addressesFile, 'utf8'))
: {};

addresses[contractName] = address;
writeFileSync(addressesFile, JSON.stringify(addresses, null, 2), 'utf8');
} catch (error) {
Expand All @@ -85,11 +82,11 @@ const deployContract = async (contractName, mnemonic, providerConfig) => {
};

const providerConfig = {
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io',
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io', #TODO: replace to `https://services.polkadothub-rpc.com/testnet` when ready
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a TODO comment left in production code that should be addressed or removed before merging. If the URL change is pending, consider documenting this separately or creating a follow-up issue.

The line reads:

rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io', #TODO: replace to `https://services.polkadothub-rpc.com/testnet` when ready

Either complete the change or remove the TODO comment.

Suggested change
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io', #TODO: replace to `https://services.polkadothub-rpc.com/testnet` when ready
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io',

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In JavaScript, this should use // for comments instead of #. The current syntax is invalid JavaScript.

Change:

rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io', #TODO: replace to `https://services.polkadothub-rpc.com/testnet` when ready

To:

rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io', // TODO: replace to `https://services.polkadothub-rpc.com/testnet` when ready
Suggested change
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io', #TODO: replace to `https://services.polkadothub-rpc.com/testnet` when ready
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io', // TODO: replace to `https://services.polkadothub-rpc.com/testnet` when ready

Copilot uses AI. Check for mistakes.
chainId: 420420422,
name: 'polkadot-hub-testnet',
};

const mnemonic = 'INSERT_MNEMONIC';
const mnemonic = 'evoke moment pluck misery cheese boy era fresh useful frame resemble cinnamon';
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mnemonic phrase is hardcoded in the deployment script. This is a security risk as it exposes private keys in the codebase. The placeholder INSERT_MNEMONIC should be kept instead, or the script should read from environment variables.

Replace:

const mnemonic = 'evoke moment pluck misery cheese boy era fresh useful frame resemble cinnamon';

With:

const mnemonic = 'INSERT_MNEMONIC';

Or better yet, use environment variables:

const mnemonic = process.env.MNEMONIC || 'INSERT_MNEMONIC';
Suggested change
const mnemonic = 'evoke moment pluck misery cheese boy era fresh useful frame resemble cinnamon';
const mnemonic = process.env.MNEMONIC || 'INSERT_MNEMONIC';

Copilot uses AI. Check for mistakes.

deployContract('Storage', mnemonic, providerConfig);
deployContract('Storage', mnemonic, providerConfig);
17 changes: 17 additions & 0 deletions code/smart-contracts/libraries/ethers-js/Storage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract Storage {
uint256 private storedNumber;

event NumberUpdated(uint256 newValue);

function store(uint256 num) public {
storedNumber = num;
emit NumberUpdated(num);
}

function retrieve() public view returns (uint256) {
return storedNumber;
}
}
Loading
Loading