Skip to main content
In this guide you will create a Hardhat project, write a WKRC payment contract, deploy it to StableNet Testnet, and verify it on the Explorer. Time: ~15 minutes Tools: Node.js, Hardhat Network: StableNet Testnet (Chain ID 8283)

Prerequisites

Before you start, make sure you have:
  • Node.js ≥ 18 — run node --version to check. Download from nodejs.org if needed.
  • A funded testnet wallet. Get free WKRC from the faucet:

    Faucet

    Request testnet WKRC — no sign-up required.

    Explorer

    Verify your balance before deploying.
StableNet enforces a minimum priority fee of 27,600 Gwei. All deploy and send transactions must include maxPriorityFeePerGas at or above this value. Transactions below this threshold are rejected at the transaction pool level.

Network Details

ParameterValue
Chain ID8283
RPC URLhttps://api.test.stablenet.network
Explorerexplorer.stablenet.network
Faucetfaucet.stablenet.network
WKRC Contract0x0000000000000000000000000000000000001000
Minimum Priority Fee27,600 Gwei (27600000000000 wei)
EVM Versionshanghai (blob opcodes not supported — do not use cancun)

Steps

1

Create a Hardhat project

Create a new directory, initialize a Node.js project, and install Hardhat:
mkdir stablenet-hardhat && cd stablenet-hardhat
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
Then initialize a Hardhat project and select “Create a JavaScript project” when prompted:
npx hardhat init
Your project structure:
stablenet-hardhat/
├── contracts/        # Solidity source files
├── scripts/          # Deployment scripts
├── test/             # Test files
└── hardhat.config.js # Hardhat configuration
Install dotenv to keep your private key out of source control:
npm install dotenv
Create a .env file and add it to .gitignore:
echo "PRIVATE_KEY=0xYourPrivateKey" > .env
echo ".env" >> .gitignore
2

Configure hardhat.config.js

Replace the contents of hardhat.config.js with the following:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

module.exports = {
  solidity: {
    version: "0.8.20",
    settings: {
      // Blob opcodes are not supported on StableNet — do not use cancun
      evmVersion: "shanghai",
    },
  },
  networks: {
    stablenet_testnet: {
      url: "https://api.test.stablenet.network",
      chainId: 8283,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
};
Do not set evmVersion to cancun. StableNet does not support blob opcodes (BLOBHASH, BLOBBASEFEE). Use paris or shanghai.
3

Write WKRCPayment.sol

Delete the placeholder contract and create contracts/WKRCPayment.sol:
rm contracts/Lock.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
}

/// @title WKRCPayment
/// @notice Accepts WKRC payments from an approved sender.
///         The sender must approve this contract before calling pay().
contract WKRCPayment {
    /// @dev NativeCoinAdapter (WKRC) — fixed system contract address on StableNet.
    IERC20 public constant WKRC =
        IERC20(0x0000000000000000000000000000000000001000);

    event Payment(address indexed from, address indexed to, uint256 amount);

    /// @notice Pull `amount` of WKRC from msg.sender and forward it to `to`.
    ///         Requires prior WKRC.approve(address(this), amount).
    function pay(address to, uint256 amount) external {
        require(WKRC.transferFrom(msg.sender, to, amount), "WKRC transfer failed");
        emit Payment(msg.sender, to, amount);
    }

    /// @notice Check how much WKRC `owner` has approved for this contract.
    function allowanceOf(address owner) external view returns (uint256) {
        return WKRC.allowance(owner, address(this));
    }
}
4

Compile the contract

npx hardhat compile
Expected output:
Compiled 1 Solidity file successfully (evm target: shanghai).
Compiled artifacts are written to artifacts/ and cache/.
5

Write the deploy script

Delete the placeholder script and create scripts/deploy.js:
rm scripts/deploy.js 2>/dev/null; true
const { ethers } = require("hardhat");

async function main() {
  const [deployer] = await ethers.getSigners();
  console.log("Deploying with:", deployer.address);

  const WKRCPayment = await ethers.getContractFactory("WKRCPayment");

  // StableNet requires maxPriorityFeePerGas >= 27,600 Gwei on every transaction
  const contract = await WKRCPayment.deploy({
    maxPriorityFeePerGas: ethers.parseUnits("27600", "gwei"),
  });

  await contract.waitForDeployment();
  console.log("WKRCPayment deployed to:", await contract.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
You must pass maxPriorityFeePerGas on every deploy and send call. Hardhat does not pick up the network-enforced minimum automatically — omitting it will cause your transaction to be rejected.
6

Deploy to Testnet

Run the deploy script against StableNet Testnet:
npx hardhat run scripts/deploy.js --network stablenet_testnet
Successful output:
Deploying with: 0xYourWalletAddress
WKRCPayment deployed to: 0xABC...
Block inclusion may take up to 1–2 minutes. If the script appears to hang at waitForDeployment(), your transaction is already in the mempool and will be confirmed shortly. Do not cancel and retry — that may cause a nonce conflict.
Save the deployed contract address — you will need it to interact with the contract.
7

Verify on Explorer

Open explorer.stablenet.network and search for your contract address.You should see:
  • The deployment transaction with a Contract Creation label
  • The deployer address as the transaction sender
  • The contract bytecode stored on-chain
The deployment is confirmed when the transaction shows a block number and status Success.

What just happened?

  • hardhat.config.js connects Hardhat to StableNet Testnet using Chain ID 8283 and the public RPC endpoint.
  • evmVersion: "shanghai" ensures Solidity does not emit blob opcodes, which are not available on StableNet.
  • maxPriorityFeePerGas: ethers.parseUnits("27600", "gwei") satisfies the GovValidator-enforced minimum. Without it, the transaction pool rejects the transaction before it reaches a block.
  • waitForDeployment() polls eth_getTransactionReceipt until the deploy transaction is included in a block.
  • WKRC is the KRW-pegged native coin of StableNet exposed as a standard ERC-20 token via the NativeCoinAdapter system contract. The same balance used for gas is the balance returned by balanceOf().

Troubleshooting

  • Transaction rejected / “fee too low” — ensure maxPriorityFeePerGas is set to at least ethers.parseUnits("27600", "gwei") in every deploy and send call.
  • waitForDeployment() hangs — your transaction is in the mempool. Wait up to 2 minutes; do not cancel. Block time is ~1 second but mempool propagation can add latency.
  • “replacement transaction underpriced” — a previous transaction with the same nonce is pending. Increase both maxFeePerGas and maxPriorityFeePerGas to replace it, or wait for the original to confirm.
  • “transfer amount exceeds balance” during a WKRC send — reduce the amount or lower gas fees to ensure your wallet balance covers both the transfer and gas cost.
  • BLOBHASH / BLOBBASEFEE opcode errors — you are using evmVersion: "cancun". Change it to "shanghai" in hardhat.config.js.
maxFeePerGas defaults to 0 when omitted. Setting only maxPriorityFeePerGas leaves maxFeePerGas unset, which defaults to 0 and causes the transaction to be rejected. Specify both values explicitly in every call:
{
  maxPriorityFeePerGas: ethers.parseUnits("27600", "gwei"),
  maxFeePerGas: ethers.parseUnits("80000", "gwei"),
}
Hardhat v3 — npx hardhat init is interactive-only. Hardhat v3 changed npx hardhat init to an interactive-only flow that may not work in all environments. Install Hardhat v2 to use the standard project scaffold:
npm install --save-dev hardhat@2
Slow block inclusion at 27,600 Gwei. If your transaction is slow to confirm, increase both fee values:
{
  maxPriorityFeePerGas: ethers.parseUnits("35000", "gwei"),  // 35000000000000 wei
  maxFeePerGas: ethers.parseUnits("80000", "gwei"),          // 80000000000000 wei
}

Next Steps

Foundry Quickstart

Deploy the same WKRC payment contract using Foundry — includes approve and pay flow.

EVM Compatibility Reference

Supported opcodes, tool configuration snippets, fee delegation (tx type 0x16), and unsupported features.

Network Information

Full network parameters — Chain IDs, RPC endpoints, system contract addresses.

Explorer

Inspect your contract and transactions on-chain.