JPYC, Solidity, Polygon, Hardhat, SmartContract, Web3, Blockchain
Celebrating ¥100M Issued! A Practical Guide to Working with JPYC in Smart Contracts (Using Solidity, Hardhat, and Polygon) - Technical Perspective
10 Nov 2025



Why Dive into JPYC Right Now?
JPYC, the stablecoin tied to the Japanese Yen, hit the scene on October 27, 2025. It's blown up fast in Japan's Web3 world, crossing ¥100 million in issuance within just six days.
Up until now, if you were building dApps in Japan, you were stuck dealing with unpredictable cryptos like ETH or MATIC, or relying on overseas stablecoins like USDC. That made it tricky to create yen-based services without extra headaches.
But JPYC changes the game—it's officially an "Electronic Payment Instrument" under Japan's Payment Services Act. This means smoother, safer Web3 apps that feel natural for yen users. In this guide, I'll walk you through interacting with JPYC from a smart contract, with real code snippets. We'll use Polygon for its cheap fees and quick transactions, plus Hardhat and Solidity to build a basic contract for depositing and withdrawing JPYC.
Quick Heads-Up on Versions
I tested this on November 6, 2025. Web3 stuff moves quick, so updates to tools might break things later. Stick to these versions for best results:
Hardhat: 2.22.4
@nomicfoundation/hardhat-toolbox: 5.0.0
@openzeppelin/contracts: 5.0.2
dotenv: 16.4.5
Pop them into your package.json and run npm install to match.
What You'll Need to Get Started
Node.js (v18 or up works best)
A code editor like VS Code
MetaMask wallet extension with an account set up
Some MATIC on Polygon for gas fees (deploying and transacting)
A bit of JPYC for testing—grab a few hundred yen worth from the "JPYC EX" on the official site and send it to your MetaMask on Polygon
Step 1: Setting Up Hardhat
Fire up your terminal, make a folder, and kick off a Hardhat project:
mkdir jpyc-sc-tutorial cd jpyc-sc-tutorial npx hardhat
Answer the prompts like this:
What do you want to do? → Create a JavaScript project
Hardhat project root: → Hit Enter for default
Add a .gitignore? → yes
Install dependencies with npm? → yes
Then install these:
npm install @openzeppelin/contracts@5.0.2 dotenv@16.4.5
Create a .env file in the root and add:
POLYGON_RPC_URL="YOUR_ALCHEMY_OR_INFURA_POLYGON_RPC_URL" PRIVATE_KEY="YOUR_METAMASK_PRIVATE_KEY"
Swap in your Polygon RPC from Alchemy or Infura.
Use your MetaMask private key here—but keep it secret, seriously.
Update hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config(); module.exports = { solidity: "0.8.20", networks: { polygon: { url: process.env.POLYGON_RPC_URL || "", accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [], }, }, };
Step 2: Building the Smart Contract
In the contracts folder, ditch the sample Lock.sol and make JPYCHandler.sol:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract JPYCHandler is Ownable { IERC20 public immutable jpycToken; event Deposit(address indexed user, uint256 amount); event Withdraw(address indexed owner, uint256 amount); address constant JPYC_POLYGON_ADDRESS = 0x431D5dfF03120AFA4bDf332c61A6e1766eF37BDB; constructor() Ownable(msg.sender) { jpycToken = IERC20(JPYC_POLYGON_ADDRESS); } /** * @dev Lets a user deposit JPYC into the contract. * @param _amount Amount of JPYC (with 18 decimals in mind). * Note: User needs to approve this contract first to spend their JPYC. */ function depositJPYC(uint256 _amount) external { require(_amount > 0, "Amount must be greater than zero"); uint256 allowance = jpycToken.allowance(msg.sender, address(this)); require(allowance >= _amount, "Check the token allowance"); bool success = jpycToken.transferFrom(msg.sender, address(this), _amount); require(success, "Transfer failed"); emit Deposit(msg.sender, _amount); } /** * @dev Owner can pull out all JPYC from the contract. */ function withdrawAllJPYC() external onlyOwner { uint256 balance = getContractBalance(); require(balance > 0, "No JPYC to withdraw"); jpycToken.transfer(owner(), balance); emit Withdraw(owner(), balance); } /** * @dev Check the contract's JPYC balance. * @return Balance in wei-like units. */ function getContractBalance() public view returns (uint256) { return jpycToken.balanceOf(address(this)); } }
Step 3: Deploying and Interacting
Now let's get it on-chain and test it.
Deployment Script
Tweak scripts/deploy.js:
const hre = require("hardhat"); async function main() { const jpycHandler = await hre.ethers.deployContract("JPYCHandler"); await jpycHandler.waitForDeployment(); console.log(`JPYCHandler deployed to: ${jpycHandler.target}`); } main().catch((error) => { console.error(error); process.exitCode = 1; });
Run it:
npx hardhat run scripts/deploy.js --network polygon
Grab the deployed address from the console.
Interaction Script
Add scripts/interact.js:
const { ethers } = require("hardhat"); // CONFIG const JPYC_HANDLER_ADDRESS = "YOUR_DEPLOYED_CONTRACT_ADDRESS"; const JPYC_TOKEN_ADDRESS = "0x431D5dfF03120AFA4bDf332c61A6e1766eF37BDB"; const DEPOSIT_AMOUNT = ethers.parseUnits("10", 18); // JPYC has 18 decimals async function main() { const [signer] = await ethers.getSigners(); console.log("Using wallet:", signer.address); const jpycHandler = await ethers.getContractAt("JPYCHandler", JPYC_HANDLER_ADDRESS, signer); const jpycToken = await ethers.getContractAt("IERC20", JPYC_TOKEN_ADDRESS, signer); console.log("\\nApproving spend..."); const approveTx = await jpycToken.approve(JPYC_HANDLER_ADDRESS, DEPOSIT_AMOUNT); await approveTx.wait(); console.log("Approved. Tx:", approveTx.hash); console.log("\\nDepositing..."); const depositTx = await jpycHandler.depositJPYC(DEPOSIT_AMOUNT); await depositTx.wait(); console.log("Deposited. Tx:", depositTx.hash); const contractBalance = await jpycHandler.getContractBalance(); console.log("\\nCheck balance:", ethers.formatUnits(contractBalance, 18) + " JPYC"); } main().catch((error) => { console.error(error); process.exitCode = 1; });
Swap in your contract address, then run:
npx hardhat run scripts/interact.js --network polygon
You should see the approval, deposit, and a balance of 10 JPYC.
Wrapping Up and Ideas for More
That's the basics for handling JPYC in a contract. The approve-then-transferFrom pattern is key for any ERC20 work—get comfy with it.
This simple setup can grow into cool stuff like:
An e-commerce payment system
Tipping for creators on blogs
Stable in-game money for Web3 games
With JPYC here, yen-focused Web3 projects have tons of potential. Grab this code, tweak it, and build something awesome.
I've put this guide together to give you a solid starting point for building with JPYC. Web3 development can sometimes throw a curveball, especially with different versions and environments.
If you follow the steps but still run into any trouble, or just have a question about the code, please feel free to reach out. I'd be happy to help clarify things. Happy building
Why Dive into JPYC Right Now?
JPYC, the stablecoin tied to the Japanese Yen, hit the scene on October 27, 2025. It's blown up fast in Japan's Web3 world, crossing ¥100 million in issuance within just six days.
Up until now, if you were building dApps in Japan, you were stuck dealing with unpredictable cryptos like ETH or MATIC, or relying on overseas stablecoins like USDC. That made it tricky to create yen-based services without extra headaches.
But JPYC changes the game—it's officially an "Electronic Payment Instrument" under Japan's Payment Services Act. This means smoother, safer Web3 apps that feel natural for yen users. In this guide, I'll walk you through interacting with JPYC from a smart contract, with real code snippets. We'll use Polygon for its cheap fees and quick transactions, plus Hardhat and Solidity to build a basic contract for depositing and withdrawing JPYC.
Quick Heads-Up on Versions
I tested this on November 6, 2025. Web3 stuff moves quick, so updates to tools might break things later. Stick to these versions for best results:
Hardhat: 2.22.4
@nomicfoundation/hardhat-toolbox: 5.0.0
@openzeppelin/contracts: 5.0.2
dotenv: 16.4.5
Pop them into your package.json and run npm install to match.
What You'll Need to Get Started
Node.js (v18 or up works best)
A code editor like VS Code
MetaMask wallet extension with an account set up
Some MATIC on Polygon for gas fees (deploying and transacting)
A bit of JPYC for testing—grab a few hundred yen worth from the "JPYC EX" on the official site and send it to your MetaMask on Polygon
Step 1: Setting Up Hardhat
Fire up your terminal, make a folder, and kick off a Hardhat project:
mkdir jpyc-sc-tutorial cd jpyc-sc-tutorial npx hardhat
Answer the prompts like this:
What do you want to do? → Create a JavaScript project
Hardhat project root: → Hit Enter for default
Add a .gitignore? → yes
Install dependencies with npm? → yes
Then install these:
npm install @openzeppelin/contracts@5.0.2 dotenv@16.4.5
Create a .env file in the root and add:
POLYGON_RPC_URL="YOUR_ALCHEMY_OR_INFURA_POLYGON_RPC_URL" PRIVATE_KEY="YOUR_METAMASK_PRIVATE_KEY"
Swap in your Polygon RPC from Alchemy or Infura.
Use your MetaMask private key here—but keep it secret, seriously.
Update hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config(); module.exports = { solidity: "0.8.20", networks: { polygon: { url: process.env.POLYGON_RPC_URL || "", accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [], }, }, };
Step 2: Building the Smart Contract
In the contracts folder, ditch the sample Lock.sol and make JPYCHandler.sol:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract JPYCHandler is Ownable { IERC20 public immutable jpycToken; event Deposit(address indexed user, uint256 amount); event Withdraw(address indexed owner, uint256 amount); address constant JPYC_POLYGON_ADDRESS = 0x431D5dfF03120AFA4bDf332c61A6e1766eF37BDB; constructor() Ownable(msg.sender) { jpycToken = IERC20(JPYC_POLYGON_ADDRESS); } /** * @dev Lets a user deposit JPYC into the contract. * @param _amount Amount of JPYC (with 18 decimals in mind). * Note: User needs to approve this contract first to spend their JPYC. */ function depositJPYC(uint256 _amount) external { require(_amount > 0, "Amount must be greater than zero"); uint256 allowance = jpycToken.allowance(msg.sender, address(this)); require(allowance >= _amount, "Check the token allowance"); bool success = jpycToken.transferFrom(msg.sender, address(this), _amount); require(success, "Transfer failed"); emit Deposit(msg.sender, _amount); } /** * @dev Owner can pull out all JPYC from the contract. */ function withdrawAllJPYC() external onlyOwner { uint256 balance = getContractBalance(); require(balance > 0, "No JPYC to withdraw"); jpycToken.transfer(owner(), balance); emit Withdraw(owner(), balance); } /** * @dev Check the contract's JPYC balance. * @return Balance in wei-like units. */ function getContractBalance() public view returns (uint256) { return jpycToken.balanceOf(address(this)); } }
Step 3: Deploying and Interacting
Now let's get it on-chain and test it.
Deployment Script
Tweak scripts/deploy.js:
const hre = require("hardhat"); async function main() { const jpycHandler = await hre.ethers.deployContract("JPYCHandler"); await jpycHandler.waitForDeployment(); console.log(`JPYCHandler deployed to: ${jpycHandler.target}`); } main().catch((error) => { console.error(error); process.exitCode = 1; });
Run it:
npx hardhat run scripts/deploy.js --network polygon
Grab the deployed address from the console.
Interaction Script
Add scripts/interact.js:
const { ethers } = require("hardhat"); // CONFIG const JPYC_HANDLER_ADDRESS = "YOUR_DEPLOYED_CONTRACT_ADDRESS"; const JPYC_TOKEN_ADDRESS = "0x431D5dfF03120AFA4bDf332c61A6e1766eF37BDB"; const DEPOSIT_AMOUNT = ethers.parseUnits("10", 18); // JPYC has 18 decimals async function main() { const [signer] = await ethers.getSigners(); console.log("Using wallet:", signer.address); const jpycHandler = await ethers.getContractAt("JPYCHandler", JPYC_HANDLER_ADDRESS, signer); const jpycToken = await ethers.getContractAt("IERC20", JPYC_TOKEN_ADDRESS, signer); console.log("\\nApproving spend..."); const approveTx = await jpycToken.approve(JPYC_HANDLER_ADDRESS, DEPOSIT_AMOUNT); await approveTx.wait(); console.log("Approved. Tx:", approveTx.hash); console.log("\\nDepositing..."); const depositTx = await jpycHandler.depositJPYC(DEPOSIT_AMOUNT); await depositTx.wait(); console.log("Deposited. Tx:", depositTx.hash); const contractBalance = await jpycHandler.getContractBalance(); console.log("\\nCheck balance:", ethers.formatUnits(contractBalance, 18) + " JPYC"); } main().catch((error) => { console.error(error); process.exitCode = 1; });
Swap in your contract address, then run:
npx hardhat run scripts/interact.js --network polygon
You should see the approval, deposit, and a balance of 10 JPYC.
Wrapping Up and Ideas for More
That's the basics for handling JPYC in a contract. The approve-then-transferFrom pattern is key for any ERC20 work—get comfy with it.
This simple setup can grow into cool stuff like:
An e-commerce payment system
Tipping for creators on blogs
Stable in-game money for Web3 games
With JPYC here, yen-focused Web3 projects have tons of potential. Grab this code, tweak it, and build something awesome.
I've put this guide together to give you a solid starting point for building with JPYC. Web3 development can sometimes throw a curveball, especially with different versions and environments.
If you follow the steps but still run into any trouble, or just have a question about the code, please feel free to reach out. I'd be happy to help clarify things. Happy building
More articles

Redefining Financial Freedom
Unlocking the Potential of Web3: A Comprehensive Guide for Entrepreneurs and Investors
August 27, 2024

Automating Trust and Efficiency
Understanding Web3: A Technical Deep Dive into Blockchain, Smart Contracts, and Decentralized Applications
September 1, 2024

A New Standard for Asset Protection
From Idea to Innovation: How Entrepreneurs and Investors Can Leverage Web3 Technologies for Growth