
Company News
AWS Security Hub Adds Socket for Supply Chain Security
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.
@deuro/eurocoin
Advanced tools
It shall support a wide range of collateralized minting methods that are governed by a democratic process.
This repository is a friendly fork of Frankencoin-ZCHF.
This is the source code repository for the smart contracts of the oracle-free, collateralized stablecoin dEURO.
There also is a public frontend and a documentation page.
The source code can be found in the contracts folder. The following are the most important contracts.
| Contract | Description |
|---|---|
| DecentralizedEURO.sol | The DecentralizedEURO (dEURO) ERC20 token |
| Equity.sol | The Native Decentralized Euro Protocol Share (nDEPS) ERC20 token |
| MintingHub.sol | Plugin for oracle-free collateralized minting |
| Position.sol | A borrowed minting position holding collateral |
| PositionRoller.sol | A module to roll positions into new ones |
| StablecoinBridge.sol | Plugin for 1:1 swaps with other EUR stablecoins |
| BridgedToken.sol | Generic bridged token contract for L2 deployments, e.g. dEURO on Optimism & Base, DEPS on Base |
| Savings.sol | A module to pay out interest to ZCHF holders |
| Leadrate.sol | A module that can provide a leading interest rate for the system |
| PositionFactory.sol | Create a completely new position in a newly deployed contract |
| DEPSWrapper.sol | Enables nDEPS to be wrapped in DEPS |
| FrontendGateway.sol | A module that rewards frontend providers for referrals into the dEURO Ecosystem |
| MintingHubGateway.sol | Plugin for oracle-free collateralized minting with rewards for frontend providers |
| SavingsGateway.sol | A module to pay out interest to ZCHF holders and reward frontend providers |
| SavingsVaultDEURO.sol | ERC-4626 vault wrapper for the Savings module |
The last status adopted by Frankencoin was Commit a2ce625c554bbd3465a31e7d8b7360a054339dd2 on December 2, 2024. The following things were built on it as a fork.
The lock-up of 3 days has been removed without replacement.
Frankencoin had a single bridge to XCHF from Bitcoin Suisse
dEURO has 4 bridges to
In contrast to Frankencoin, dEURO does not use the minting module v1 at all
Interest is no longer paid when a position is opened but is credited as a debt on an ongoing basis and only has to be paid when a position is closed or modified.
Native ETH/WETH support across MintingHub, Position, and PositionRoller. Leadrate integrated directly into MintingHub. Interest is now charged only on the usable mint (excluding reserve contribution). A reference position mechanism allows cooldown-free price increases.
It is possible to use the SmartContracts through a gateway and thus obtain a refferal commission. This module is completely new.
2023-02-10 Blockbite
2023-06-09 code4rena
2023-10-30 chainsecurity Report 1
2024-09-25 Decurity
2024-11-28 ChainSecurity Report 2
// yarn run <command> args...
"wallet": "npx ts-node helper/wallet.info.ts",
"compile": "npx hardhat compile",
"test": "npx hardhat test",
"coverage": "npx hardhat coverage",
"deploy": "npx hardhat run scripts/deployment/deploy/<script>.ts --network <network>",
"verify": "npx hardhat verify",
"build": "tsup",
"publish": "npm publish --access public"
yarn install
See .env.example
file: .env
ALCHEMY_RPC_KEY=...
DEPLOYER_SEED="test test test test test test test test test test test junk"
DEPLOYER_SEED_INDEX=1 // optional, select deployer
DEPLOYER_PRIVATE_KEY=... // optional, replaces deployer seed
ETHERSCAN_API_KEY=...
USE_FORK=false
CONFIRM_DEPLOYMENT=false
Create new session or re-navigate to the current directory, to make sure environment is loaded from
.env
Develop your contracts in the
/contractsdirectory and compile with:
yarn run compile # Compiles all contracts
All test files are located in /test directory. Run tests using:
yarn run test # Run all tests
yarn run test test/TESTSCRIPT.ts # Run specific test file
yarn run coverage # Generate test coverage report
With tsc-watch (auto refresh commands)
npx tsc-watch --onCompilationComplete "npx hardhat test ./test/RollerTests.ts"
Then run a deployment script with tags and network params (e.g., sepolia that specifies the network)
hh deploy --network sepolia --tags MockTokens
hh deploy --network sepolia --tags DecentralizedEURO
hh deploy --network sepolia --tags PositionFactory
hh deploy --network sepolia --tags MintingHub
hh deploy --network sepolia --tags MockEURToken
hh deploy --network sepolia --tags XEURBridge
hh deploy --network sepolia --tags positions
Recommanded commands for
sepolianetwork. Test deployments on a local Mainnet fork usingnpx hardhat nodewithUSE_FORK=truein.env. The networks are configured inhardhat.config.ts, including the Mainnet fork. SetCONFIRM_DEPLOYMENT=trueto enable confirmation prompts before each deployment.
Deploy bridges for EUR stablecoins using the dedicated deployment script:
# Deploy bridge for specific stablecoin, e.g. EUROP
BRIDGE_KEY=EUROP npx hardhat run scripts/deployment/deploy/deployBridge.ts --network mainnet
# Test on forked mainnet
USE_FORK=true BRIDGE_KEY=EUROP npx hardhat run scripts/deployment/deploy/deployBridge.ts --network hardhat
Bridge keys and configurations are defined in scripts/deployment/config/stablecoinBridgeConfig.ts
npx hardhat verify --network polygon --constructor-args $CONSTRUCTOR_ARGS_FILE $ADDRESS
Export contract ABIs for npm package usage by copying the JSON into dedicated TypeScript files:
file: exports/abis/...
export const StorageABI = [
...
JSON
...
] as const;
Provides a mapping of contract addresses for the Membership and Storage contracts deployed on different blockchain networks.
The ADDRESS object contains the contract addresses for the mainnet and polygon networks, with the network ID as the key.
The zeroAddress is used as a placeholder for the mainnet network, as the contracts have not been deployed there yet.
file: exports/address.config.ts
import { mainnet, polygon } from 'viem/chains';
import { Address, zeroAddress } from 'viem';
export interface ChainAddress {
membership: Address;
storage: Address;
}
export const ADDRESS: Record<number, ChainAddress> = {
[mainnet.id]: {
membership: zeroAddress, // if not available
storage: zeroAddress,
},
[polygon.id]: {
membership: '0x72950A0A9689fCA941Ddc9E1a58dcD3fb792E3D2',
storage: '0x8A7e8091e71cCB7D1EbDd773C26AD82AAd323328',
},
};
Config: /tsup.config.ts
TSUP bundles TypeScript code into optimized JavaScript packages. This package uses TSUP to create production-ready builds.
yarn run build
Increase Version: Update version number in package.json using semantic versioning (e.g. 0.0.1 -> 0.0.2) before publishing new changes.
file: /package.json
"name": "@frankencoin/zchf",
"version": "0.2.16", <-- HERE
Login to your NPM account
npm login
This will publish your package to NPM with public access, making it available for anyone to install and use.
yarn run publish
To publish new version. publish: "npm publish --access public"
Note: During npm package publishing, the command may execute twice. The second execution will fail with a version conflict since the package is already published. This is expected behavior and the first publish will have succeeded.
(not needed, since its already a true JS bundled module)
E.g. for NextJs using the next.config.js in root of project.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
transpilePackages: ["@.../core", "@.../api"],
};
module.exports = nextConfig;
allowance: Added address(reserve)) to the spender addresses with unlimited dEURO allowance.burnWithReserve: Removed unused function.burnFromWithReserve: Use _spendAllowance to control spending power of minters based on allowance.burnFromWithReserveNet: Renamed from burnWithReserve.distributeProfits: New function to distinguish between reserve withdrawals due to losses vs interest payouts (e.g. to savings) -> Loss vs ProfitDistributed event._withdrawFromReserve: New helper function used by coverLoss and distributeProfits.supportsInterface: Added IDecentralizedEURO support.BelowMinimumHoldingPeriod: New custom error for failed !canRedeem(owner) check._finishChallenge: The Position.notifyChallengeSucceeded call now returns both the required principal repayment amount and interest payment amount. The bidder pays only the offer (unit price × collateral): DEURO.transferFrom(msg.sender, address(this), offer). After deducting the challenger reward, the remaining funds cover both repayment and interest from the same pool. If fundsAvailable > repayment + interest, the surplus is split between reserve profits and the position owner. If insufficient, coverLoss draws from the reserve. Interest is collected at the end via DEURO.collectProfits(address(this), interest).buyExpiredCollateral: The buyer pays forceSalePrice * amount. The proceeds are passed to Position.forceSale(buyer, amount, proceeds) which handles interest repayment and principal repayment from the same pool internally.fixedAnnualRatePPM: The interest rate for a position is synced with the lead rate (Leadrate.currentRatePPM) at creation time (in the constructor or, in the case of cloning, in the initialize function) using the _fixRateToLeadrate function. From this point onwards, the interest rate for a particular position instance is fixed unless new tokens are minted (the loan is increased), at which point it is re-synced with the lead rate. It is expected that in the case of lowered interest rates, position owners will roll their current positions into new ones (for free) to benefit from it.availableForClones: This function now only considers the principal amount in its calculations. This is because the (accrued) interest does not belong to the minted dEURO tokens of a position and therefore do not belong in this calculation.adjust: The newDebt parameter was changed to newPrincipal. Consequently, owners are able to control their principal amount without having the outstanding interest amount tied to it. Naturally, if they wish to reduce their principal, they must first pay any outstanding interest. This is handled automatically by the adjust function.MintingUpdate: The last paramter of this event now only reports the new principal amount and not the entire debt amount which would include the outstanding interest. This is more in line with the overall purpose of this event._adjustPrice: The accrued interest is removed from the bounds paramter passed to _setPrice. This is because the interest does not belong in the collateral "sanity check" logic._accrueInterest: Refactored_calculateInterest: Renamed and refactored from getDebtAtTime.getDebt: RefactoredgetInterest: New public function to get the currently outstanding (unpaid) interest on the position._mint: Updated to manage interest accrual and the syncing of the interest rate to the lead rate._notifyRepaid: Refactored, including sanity check._notifyInterestPaid: Refactored, including sanity check.forceSale: The function signature is forceSale(address buyer, uint256 colAmount, uint256 proceeds). Interest repayment and principal repayment are handled from the proceeds pool. Interest is repaid first, then the principal is repaid using _repayPrincipalNet. If proceeds exceed what's needed, the surplus goes to the position owner. If no collateral remains after the sale, any outstanding principal is covered by the system via coverLoss._payDownDebt: Refactored_repayInterest: New helper function to pay off outstanding interest by some amount. Returns the remainder in the case that amount exceeds the outstanding interest._repayPrincipal: New helper function to repay principal by some exact amount using burnFromWithReserve. Returns the remaining funds._repayPrincipalNet: New function to repay principal by some amount, where amount specifies the amount to be burned from the payer. This is done using the DecentralizedEURO.burnFromWithReserveNet function. As _repayPrincipalNet is used by the forceSale function, repayPrincipalNet(buyer, proceeds);, where proceeds may exceed getUsableMint(principal) amount (the maximum amount claimable by a particular position) we cap repayWithReserve at said maximal claimable amount. If funds remain thereafter, they are burned directly in order to pay of any remaining principal. The final remainder is returned.notifyChallengeSucceeded: Now computes and returns the proportional amount of interest that must be paid in order to successfully challenge a position.rollFullyWithExpiration: Fix logic to compute the amount to mint in the target Position.roll: Refactor and send any remaining flash loan from the debt repayment (reserve portion returned by source.repay(totRepayment) > Position._repayPrincipal > DecentralizedEURO.burnFromWithReserve) to msg.sender for the flash loan repayment._cloneTargetPosition: New helper function used to clone the target position. Used only by PositionRoller.roll.refresh: Replace the use of DecentralizedEURO.coverLoss with DecentralizedEURO.distributeProfits. This replaces the Loss event with the ProfitDistributed event.mintTo: Replace standard transfer functions with OppenZeppelin's SafeERC20 variants for the source stablecoin.The gateway contracts (FrontendGateway.sol, SavingsGateway.sol, MintingHubGateway.sol) provide a way to generously reward frontend providers or referrer, paid for by DEPS Holder. These Contracts are not present in the Frankencoin Ecosystem.
The fuzzing tests are written in Solidity and made of two main contracts located in the foundry-test/invariant folder: Invariants.t.sol which contains the invariants and Handler.t.sol which contains the actions of the fuzzing test. During each run the functions in Handler.t.sol are called by the fuzzing engine in a random order and with random inputs starting with the initial state of the system as defined by Invariants.setUp(). After each run the invariants defined in Invariants.t.sol are checked to ensure that the system is still in a valid state.
After installing foundry on your machine and running forge install to install the required dependencies, you can use the following command to run the fuzzing tests:
# remove build artifacts & cache
forge clean
# run the fuzzing tests
forge test
# more verbose output (with grep to omit some logs)
forge test -vvv | grep -v "Bound result"
# show progress
forge test --show-progress
# re-run a failed test
# Tip: Set .profile.logging.snapshot=true in foundry.toml to log snapshots
forge test --rerun
The configuration for the fuzzing tests can be found in the foundry.toml file. Furthermore, the remappings.txt file contains the remappings for the fuzzing test contracts. In order to debug handler reverts, you can set .invariant.fail_on_revert=true in the foundry.toml file.
FAQs
It shall support a wide range of collateralized minting methods that are governed by a democratic process.
The npm package @deuro/eurocoin receives a total of 1,590 weekly downloads. As such, @deuro/eurocoin popularity was classified as popular.
We found that @deuro/eurocoin demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.

Research
/Security News
Popular npm packages keyv and cacheable compromised.

Security News
A misconfiguration gave three Anthropic models internet access, and one, believing it was in a simulation, shipped a credential-stealing package to PyPI.