Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standalone smart contracts deployment #8

Merged
merged 1 commit into from
Jan 21, 2025
Merged
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
1 change: 1 addition & 0 deletions src/templateConfigs/react-solidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const config: TemplateConfig = {
/^frontend\/src.*/,
/^frontend\/package\.json/,
/^package\.json/,
/^pnpm-lock\.yaml/,
],
instructions: `
${c.primary("Step 1: ")}${c.secondary("start remixd environment")}
Expand Down
24 changes: 18 additions & 6 deletions templates/react-solidity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,29 @@ This template includes
* [Tailwind CSS](https://tailwindcss.com) + [Tailwind UI](https://tailwindui.com/).
* [Vite](https://vite.dev/) for dev tooling.

### Writing smart contracts
## Writing smart contracts

Currently, only Remix environment is supported.
Currently, two ways of developing smart contracts are supported. Both are deploying to Westend Asset Hub.

### Remix

1. Run `pnpm remixd` to start remixd environment.
2. Go to https://remix.polkadot.io and activate REMIXD plugin.
3. Start hacking! Changes performed in Remix will be synced to local file system.
4. After deploying and pinning a smart contract, it'll be saved on your file system, run `pnpm contracts:build` to
4. After deploying and pinning a smart contract, it'll be saved on your file system, run `pnpm contracts:export` to
export contract data.

### Interacting with smart contracts from frontend app
### Local development

1. Edit smart contracts in root directory
2. Run `pnpm contracts:build` to compile smart contracts
3. Run `pnpm contracts:deploy` to deploy them
Required environment variables:
* `ACCOUNT_SEED`: seed phrase for the account that will sign the deployment.
* `RPC_URL`: for Westend Asset Hub, probably `https://westend-asset-hub-eth-rpc.polkadot.io`, for kitchensink, probably `http://localhost:8545`
4. Run `pnpm contracts:export` to export contract data.

## Interacting with smart contracts from frontend app

1. Run `pnpm frontend:dev` to start `vite` environment
2. You can import all exported contracts using `import { contracts } from "contracts"`
Expand All @@ -34,7 +46,7 @@ const contract = new Contract(contractData.address, contractData.abi, signer);
const transactionResponse = await contract.retrieve(); // method on your smart contract
```

More info at:
* [contracts.polkadot.io](https://contracts.polkadot.io/): docs on smart contracts
More info at:
* [contracts.polkadot.io](https://contracts.polkadot.io/): docs on smart contracts
* [ethers docs](https://docs.ethers.org/v6/)

12 changes: 10 additions & 2 deletions templates/react-solidity/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"build": "npx tsc && node dist/exportPinnedContracts.js"
"export": "npx tsc && node dist/exportContracts.js",
"build": "npx tsc && node dist/build.js",
"deploy-contracts": "npx tsc && node dist/deploy.js"
},
"main": "dist/index.js",
"devDependencies": {
"ethers": "^6.13.4"
"ethers": "^6.13.4",
"@parity/revive": "^0.0.7",
"@polkadot-labs/hdkd-helpers": "^0.0.10",
"@polkadot-labs/hdkd": "^0.0.10",
"micro-sr25519": "^0.1.0",
"@scure/bip32": "^1.6.1",
"@scure/bip39": "^1.5.1"
}
}
40 changes: 40 additions & 0 deletions templates/react-solidity/contracts/src/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { compile } from "@parity/revive";
import { readFileSync, writeFileSync, readdirSync, rmSync, mkdirSync } from "fs";
import path from "path";

// based on https://github.com/paritytech/contracts-boilerplate/tree/e86ffe91f7117faf21378395686665856c605132/ethers/tools

const buildDir = ".build";
const contractsOutDir = path.join(buildDir, "contracts");
rmSync(contractsOutDir, { recursive: true, force: true });
mkdirSync(contractsOutDir, { recursive: true });

const contracts = readdirSync(process.cwd()).filter((f) => f.endsWith(".sol"));

console.log("Compiling contracts...");

(async () => {
for (const file of contracts) {
console.log(`Compiling ${file}`);
const name = path.basename(file, ".sol");

const input = {
[name]: { content: readFileSync(file, "utf8") }
};

const out = await compile(input);

for (const contracts of Object.values(out.contracts)) {
for (const [name, contract] of Object.entries(contracts)) {
console.log(`Writing contract ${name}...`);
writeFileSync(
path.join(contractsOutDir, `${name}.json`),
JSON.stringify({ abi: contract.abi, bytecode: `0x${contract.evm.bytecode.object}` }, null, 2)
);
}
}
}
})().catch(err => {
console.error(err);
process.exit(1);
});
60 changes: 60 additions & 0 deletions templates/react-solidity/contracts/src/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ethers, Interface, BytesLike } from "ethers";
import path from "path";
import { readdirSync, readFileSync, writeFileSync, mkdirSync } from "fs";

// based on https://github.com/paritytech/contracts-boilerplate/tree/e86ffe91f7117faf21378395686665856c605132/ethers/tools

if (!process.env.ACCOUNT_SEED) {
console.error("ACCOUNT_SEED environment variable is required for deploying smart contract");
process.exit(1);
}

if (!process.env.RPC_URL) {

Choose a reason for hiding this comment

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

would assert make similar effect?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, but with a stacktrace

console.error("RPC_URL environment variable is required for deploying smart contract");
process.exit(1);
}

const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = ethers.Wallet.fromPhrase(process.env.ACCOUNT_SEED, provider);

const buildDir = ".build";
const contractsOutDir = path.join(buildDir, "contracts");
const deploysDir = path.join(".deploys", "deployed-contracts");
mkdirSync(deploysDir, { recursive: true });

const contracts = readdirSync(contractsOutDir).filter((f) => f.endsWith(".json"));

type Contract = {
abi: Interface,
bytecode: BytesLike,
}

(async () => {
for (const file of contracts) {
const name = path.basename(file, ".json");
const contract = JSON.parse(readFileSync(path.join(contractsOutDir, file), "utf8")) as Contract;
const factory = new ethers.ContractFactory(
contract.abi,
contract.bytecode,
wallet
);

console.log(`Deploying contract ${name}...`);
const deployedContract = await factory.deploy();
await deployedContract.waitForDeployment();
const address = await deployedContract.getAddress();

console.log(`Deployed contract ${name}: ${address}`);

const fileContent = JSON.stringify({
name,
address,
abi: contract.abi,
deployedAt: Date.now()
});
writeFileSync(path.join(deploysDir, `${address}.json`), fileContent);
}
})().catch(err => {
console.error(err);
process.exit(1);
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ let res = "export const contracts = {";
let dirEntries: fs.Dirent[] = [];

try {
dirEntries = fs.readdirSync(
dirEntries.push(...fs.readdirSync(
path.join(".deploys", "pinned-contracts"),
{ recursive: true, withFileTypes: true }
);
));
dirEntries.push(...fs.readdirSync(
path.join(".deploys", "deployed-contracts"),
{ recursive: true, withFileTypes: true }
));
} catch (e: unknown) {
if (e instanceof Error && "code" in e && e.code === "ENOENT") {
console.warn("No pinned contracts found; remember to pin deployed contracts in order to use them from frontend");
console.warn("No contracts found; remember to pin deployed contracts in Remix in order to use them from frontend");
process.exit();
}
}
Expand All @@ -23,7 +27,7 @@ for (const entry of dirEntries) {
if (entry.isFile() && entry.name.startsWith("0x") && entry.name.endsWith(".json")) {
const strippedAddress = entry.name.slice(2, entry.name.length - 5);

console.log(`Processing pinned contract ${strippedAddress}`);
console.log(`Processing contract ${strippedAddress}`);

const value = fs.readFileSync(path.join(entry.parentPath, entry.name), "utf-8");
res += `\n "${strippedAddress}": ${value},\n`;
Expand All @@ -34,4 +38,5 @@ res += "};";
const outPath = path.join("dist", "contracts.js");
fs.writeFileSync(outPath, res);

console.log(`Exported pinned contracts to ${outPath}`);
console.log(`Exported contracts to ${outPath}`);

2 changes: 1 addition & 1 deletion templates/react-solidity/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import polkadotLogo from "./assets/polkadot-logo.svg";
import { useNetworkData } from "./hooks/useNetworkData";

function App() {
const contractData = contracts["12d7b1015d6888edbeb19610cfe518310405c685"];
const contractData = contracts["859Ac8969AdEa0C41393b3eAB299C5b32a0EA391"];
const { storedValue, balance, chainId } = useNetworkData(contractData);

return (
Expand Down
2 changes: 2 additions & 0 deletions templates/react-solidity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"type": "module",
"scripts": {
"remixd": "npx remixd -s ./contracts -u https://remix.polkadot.io",
"contracts:export": "pnpm --filter=contracts export",
"contracts:build": "pnpm --filter=contracts build",
"contracts:deploy": "pnpm --filter=contracts deploy-contracts",
"frontend:dev": "pnpm --filter=frontend dev",
"frontend:build": "pnpm --filter=frontend build",
"frontend:lint": "pnpm --filter=frontend lint",
Expand Down
Loading
Loading