Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7d00674
chore: update hardhat configuration and dependencies for bridge contr…
blueogin Jan 26, 2026
6a11b79
feat: enhance GoodDollarOFTAdapter with new bridge limits and account…
blueogin Jan 27, 2026
b54c09b
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Jan 28, 2026
56be154
feat: add .env.sample for configuration, update Hardhat config for XD…
blueogin Jan 29, 2026
3b1ea49
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Jan 29, 2026
b45c2ae
fix: clarify documentation in GoodDollarOFTAdapter by removing redund…
blueogin Jan 29, 2026
89fae4a
chore: remove outdated verification script for GoodDollar OFT contracts
blueogin Jan 30, 2026
76e9709
refactor: update ISuperGoodDollar interface implementation and remove…
blueogin Feb 2, 2026
d77238d
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Feb 2, 2026
fb5d269
refactor: remove upgrade authorization logic and change deployment ki…
blueogin Feb 2, 2026
1d08bce
feat: introduce deployment-oft.json for OFT contracts and update scri…
blueogin Feb 2, 2026
124a1a1
feat: add oft.config.json for network-specific limit configurations a…
blueogin Feb 2, 2026
d1dc1d4
docs: add OFT configuration guide for cross-chain setup between XDC a…
blueogin Feb 2, 2026
9d11522
refactor: remove unused initialization code from OFT deployment scrip…
blueogin Feb 2, 2026
c5b45df
chore: enable contract size checking on compile in Hardhat configuration
blueogin Feb 2, 2026
e907906
refactor: streamline GoodDollarOFTAdapter by removing unused structs …
blueogin Feb 2, 2026
5c4c510
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Feb 2, 2026
30178c0
refactor: remove unused imports from GoodDollarOFTAdapter to enhance …
blueogin Feb 4, 2026
71da58c
refactor: enhance GoodDollarOFTAdapter with UUPS upgradeability, upda…
blueogin Feb 6, 2026
e67caf1
refactor: update OFT deployment script to use UUPS upgradeability for…
blueogin Feb 6, 2026
52284e0
refactor: reorganize GoodDollarOFTAdapter contract structure for impr…
blueogin Feb 10, 2026
0f8d105
refactor: update Hardhat configuration to include Solidity version 0.…
blueogin Feb 10, 2026
a4b2c68
refactor: add feeRecipient parameter to GoodDollarOFTAdapter initiali…
blueogin Feb 10, 2026
b678f7b
feat: introduce deployOFT script for GoodDollar OFT contracts and rem…
blueogin Feb 10, 2026
dcfc3fb
fix: update contract path in grant-minter-role script to reflect new …
blueogin Feb 10, 2026
866e1d1
feat: add deployment configuration for OFT contracts and update deplo…
blueogin Feb 10, 2026
6ccca46
refactor: replace request approval mechanism with failed receive requ…
blueogin Feb 11, 2026
73f995b
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Feb 11, 2026
5f6ed35
feat: add feeRecipient parameter to GoodDollarOFTAdapter initializati…
blueogin Feb 11, 2026
2ac107d
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Feb 11, 2026
c859ba3
feat: enhance deployment scripts for GoodDollarMinterBurner and GoodD…
blueogin Feb 11, 2026
a1dc6aa
feat: add ReceiveRequestFailed event to GoodDollarOFTAdapter for impr…
blueogin Feb 11, 2026
ffbc78c
feat: update deployment addresses for GoodDollarMinterBurner and Good…
blueogin Feb 11, 2026
836764b
feat: enhance GoodDollarOFTAdapter with optimistic window for failed …
blueogin Mar 2, 2026
257db1a
feat: implement OFT configuration scripts and documentation for bridg…
blueogin Mar 2, 2026
11d66fb
chore: remove outdated deployment JSON files for GoodDollarMinterBurn…
blueogin Mar 2, 2026
2b109b2
feat: refactor deployment script for GoodDollarMinterBurner to use de…
blueogin Mar 2, 2026
365283e
chore: remove OFT configuration script for XDC and CELO networks
blueogin Mar 2, 2026
a98d554
chore: remove unused contract factory and type definitions for IFeesF…
blueogin Mar 2, 2026
04e4c2a
fix: correct logic in GoodDollarOFTAdapter to ensure daily limits are…
blueogin Mar 2, 2026
bd2e5d1
chore: update deployment addresses for GoodDollarMinterBurner and Goo…
blueogin Mar 3, 2026
9406240
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Mar 3, 2026
4a1e1a3
feat: enhance deployment script to differentiate between development …
blueogin Mar 3, 2026
b748e7f
chore: update deployment addresses for GoodDollarMinterBurner and Goo…
blueogin Mar 3, 2026
aa25144
fix: update OPTIMISTIC_WINDOW to 5 minutes and modify approveFailedRe…
blueogin Mar 3, 2026
8ecceb6
chore: clean up .env.sample by removing deprecated variables related …
blueogin Mar 5, 2026
59df931
chore: remove outdated configuration options and troubleshooting sect…
blueogin Mar 5, 2026
86d4b55
chore: remove unnecessary configuration options from oft.config.json …
blueogin Mar 5, 2026
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
4 changes: 4 additions & 0 deletions packages/bridge-contracts/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PRIVATE_KEY=
PUBLIC_KEY=

ETHERSCAN_KEY=
10 changes: 5 additions & 5 deletions packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable {
emit BridgePaused(_isPaused);
}

function approveFailedRequest(bytes32 _guid) external onlyOwner {
function approveFailedRequest(bytes32 _guid) external {
FailedReceiveRequest memory request = failedReceiveRequests[_guid];
require(request.timestamp + OPTIMISTIC_WINDOW < block.timestamp, 'optimistic period not ended');
require(request.failed, 'request not failed');
Expand Down Expand Up @@ -364,11 +364,11 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable {
_origin.srcEid
);
emit ReceiveRequestFailed(_guid, toAddress, amountLD, _origin.srcEid);
revert BRIDGE_LIMITS(reason);
} else{
bridgeDailyLimit.bridged24Hours += amountLD;
accountsDailyLimit[toAddress].bridged24Hours += amountLD;
super._lzReceive(_origin, _guid, _message, _executor, _extraData);
}
bridgeDailyLimit.bridged24Hours += amountLD;
accountsDailyLimit[toAddress].bridged24Hours += amountLD;
super._lzReceive(_origin, _guid, _message, _executor, _extraData);
}
/**
* @notice Mints tokens to the specified address upon receiving them
Expand Down
249 changes: 249 additions & 0 deletions packages/bridge-contracts/deploy/deployOFT.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/***
* Hardhat-deploy script for GoodDollar OFT (Omnichain Fungible Token) contracts
*
* Deploys (same pattern as MessageBridge: deterministic proxy + implementation + execute initialize):
* 1. GoodDollarMinterBurner - DAO-upgradeable contract that handles minting and burning of GoodDollar tokens for OFT
* 2. GoodDollarOFTAdapter - Upgradeable LayerZero OFT adapter that wraps GoodDollar token for cross-chain transfers
*
* Steps:
* 1. Deploy ERC1967Proxy (deterministic) for GoodDollarMinterBurner, deploy implementation, execute initialize(nameService)
* 2. Deploy ERC1967Proxy (deterministic) for GoodDollarOFTAdapter, deploy implementation (constructor: token, lzEndpoint), execute initialize(token, minterBurner, owner, feeRecipient)
*
* Note: Setting OFT adapter as operator on GoodDollarMinterBurner must be done separately via DAO governance
*/

import { DeployFunction } from 'hardhat-deploy/types';
import { ethers } from 'hardhat';
import Contracts from '@gooddollar/goodprotocol/releases/deployment.json';
import fse from 'fs-extra';
import release from '../release/deployment-oft.json';
import { getImplementationAddress } from '@openzeppelin/upgrades-core';
import { verifyContract } from './utils/verifyContract';

// Network-specific LayerZero endpoints
const lzEndpoints: { [key: string]: string } = {
'development-celo': '0x1a44076050125825900e736c501f859c50fE728c',
'production-celo': '0x1a44076050125825900e736c501f859c50fE728c',
'development-xdc': '0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa',
'production-xdc': '0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa',
};

const func: DeployFunction = async function (hre) {
const { deployments, network } = hre;
const [root] = await ethers.getSigners();

const networkName = network.name;

console.log('Deployment signer:', {
networkName,
root: root.address,
balance: await ethers.provider.getBalance(root.address).then((_) => _.toString()),
});

// Get contract addresses from GoodProtocol deployment
const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any;
if (!goodProtocolContracts) {
throw new Error(
`No GoodProtocol contracts found for network ${networkName}. Please check @gooddollar/goodprotocol/releases/deployment.json`,
);
}

// Get token address from GoodProtocol
const tokenAddress = goodProtocolContracts.GoodDollar;
if (!tokenAddress) {
throw new Error(
`Token address not found in GoodProtocol deployment for network ${networkName}. Please deploy SuperGoodDollar or GoodDollar first.`,
);
}

// Get NameService for DAO integration from GoodProtocol
const nameServiceAddress = goodProtocolContracts.NameService;
if (!nameServiceAddress) {
throw new Error(
`NameService address not found in GoodProtocol deployment for network ${networkName}. Please deploy NameService first.`,
);
}

// Get Controller address directly from GoodProtocol contracts (or via NameService if needed)
let controllerAddress = goodProtocolContracts.Controller;
if (!controllerAddress) {
// Fallback: try to get Controller via NameService interface
const INameService = await ethers.getContractAt(
'@gooddollar/goodprotocol/contracts/Interfaces.sol:INameService',
nameServiceAddress,
);
controllerAddress = await INameService.getAddress('CONTROLLER');
if (!controllerAddress || controllerAddress === ethers.constants.AddressZero) {
throw new Error(`Controller address not found in GoodProtocol deployment for network ${networkName}`);
}
}

// Get LayerZero endpoint
const lzEndpoint = lzEndpoints[networkName] || process.env.LAYERZERO_ENDPOINT;
if (!lzEndpoint) {
throw new Error(
`LayerZero endpoint not found. Please set LAYERZERO_ENDPOINT environment variable or add default for network ${networkName}`,
);
}

console.log('Deployment parameters:', {
tokenAddress,
nameServiceAddress,
controllerAddress,
lzEndpoint,
networkName,
});

// Get Controller and Avatar addresses (used for OFT adapter owner)
const Controller = await ethers.getContractAt('Controller', controllerAddress);
const avatarAddress = await Controller.avatar();

if (!avatarAddress || avatarAddress === ethers.constants.AddressZero) {
throw new Error(`Avatar address is invalid: ${avatarAddress}`);
}
console.log('✅ Verified Avatar address:', avatarAddress);

let isDevelopment = false;
if (network.name.includes('development')) {
isDevelopment = true;
}
// --- GoodDollarMinterBurner (hardhat-deploy: deterministic proxy + implementation + execute initialize) ---
const minterBurnerProxySalt = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(isDevelopment ? 'Development-GoodDollarMinterBurnerV1' : 'Production-GoodDollarMinterBurnerV1'),
);
const minterBurnerProxyDeploy = await deployments.deterministic('GoodDollarMinterBurner', {
contract: 'ERC1967Proxy',
from: root.address,
salt: minterBurnerProxySalt,
log: true,
});
const minterBurnerProxy = await minterBurnerProxyDeploy.deploy();
const minterBurnerAddress = minterBurnerProxy.address;
console.log('GoodDollarMinterBurner proxy', minterBurnerAddress);

const minterBurnerImpl = await deployments.deploy('GoodDollarMinterBurner_Implementation', {
contract: 'GoodDollarMinterBurner',
from: root.address,
deterministicDeployment: true,
log: true,
});
console.log('GoodDollarMinterBurner implementation', minterBurnerImpl.address);

const minterBurnerContract = await ethers.getContractAt('GoodDollarMinterBurner', minterBurnerAddress);
const minterBurnerInitialized = await minterBurnerContract
.token()
.then((addr: string) => addr !== ethers.constants.AddressZero)
.catch(() => false);

if (!minterBurnerInitialized) {
console.log('Initializing GoodDollarMinterBurner...');
const minterBurnerInitData = minterBurnerContract.interface.encodeFunctionData('initialize', [
nameServiceAddress,
]);
await deployments.execute(
'GoodDollarMinterBurner',
{ from: root.address },
'initialize',
minterBurnerImpl.address,
minterBurnerInitData,
);
console.log('GoodDollarMinterBurner initialized');
} else {
console.log('GoodDollarMinterBurner already initialized');
}

// Update release file for MinterBurner
if (!release[networkName]) {
release[networkName] = {};
}
release[networkName].GoodDollarMinterBurner = minterBurnerAddress;
await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 });

// Verify GoodDollarMinterBurner implementation (no constructor args)
if (!['hardhat', 'localhost', 'develop'].includes(networkName)) {
await verifyContract(hre as any, minterBurnerImpl.address, [], 'GoodDollarMinterBurner');
}

// --- GoodDollarOFTAdapter (hardhat-deploy: deterministic proxy + implementation with constructor + execute initialize) ---
const oftAdapterProxySalt = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(isDevelopment ? 'Development-GoodDollarOFTAdapterV1' : 'Production-GoodDollarOFTAdapterV1'),
);
const oftAdapterProxyDeploy = await deployments.deterministic('GoodDollarOFTAdapter', {
contract: 'ERC1967Proxy',
from: root.address,
salt: oftAdapterProxySalt,
log: true,
});
const oftAdapterProxy = await oftAdapterProxyDeploy.deploy();
const oftAdapterAddress = oftAdapterProxy.address;
console.log('GoodDollarOFTAdapter proxy', oftAdapterAddress);

const oftAdapterImpl = await deployments.deploy('GoodDollarOFTAdapter_Implementation', {
contract: 'GoodDollarOFTAdapter',
from: root.address,
deterministicDeployment: true,
log: true,
args: [tokenAddress, lzEndpoint],
});
console.log('GoodDollarOFTAdapter implementation', oftAdapterImpl.address);

const oftAdapterContract = await ethers.getContractAt('GoodDollarOFTAdapter', oftAdapterAddress);
const oftAdapterInitialized = await oftAdapterContract
.minterBurner()
.then((addr: string) => addr !== ethers.constants.AddressZero)
.catch(() => false);

if (!oftAdapterInitialized) {
console.log('Initializing GoodDollarOFTAdapter...');
const oftAdapterInitData = oftAdapterContract.interface.encodeFunctionData('initialize', [
tokenAddress,
minterBurnerAddress,
root.address,
root.address,
]);
await deployments.execute(
'GoodDollarOFTAdapter',
{ from: root.address },
'initialize',
oftAdapterImpl.address,
oftAdapterInitData,
);
console.log('GoodDollarOFTAdapter initialized');
console.log('Fee recipient:', root.address);
} else {
console.log('GoodDollarOFTAdapter already initialized');
}

release[networkName].GoodDollarOFTAdapter = oftAdapterAddress;
await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 });

// Verify GoodDollarOFTAdapter implementation (constructor: tokenAddress, lzEndpoint)
if (!['hardhat', 'localhost', 'develop'].includes(networkName)) {
await verifyContract(hre as any, oftAdapterImpl.address, [tokenAddress, lzEndpoint], 'GoodDollarOFTAdapter');
}

const minterBurnerImplAddress = await getImplementationAddress(ethers.provider, minterBurnerAddress).catch(
() => undefined,
);
const oftAdapterImplAddress = await getImplementationAddress(ethers.provider, oftAdapterAddress).catch(
() => undefined,
);

console.log('\n=== Deployment Summary ===');
console.log('Network:', networkName);
console.log('GoodDollarMinterBurner:', minterBurnerAddress, '(upgradeable)');
if (minterBurnerImplAddress) {
console.log(' Implementation:', minterBurnerImplAddress);
}
console.log('GoodDollarOFTAdapter:', oftAdapterAddress, '(upgradeable)');
if (oftAdapterImplAddress) {
console.log(' Implementation:', oftAdapterImplAddress);
}
console.log('Token:', tokenAddress);
console.log('OFT Adapter Owner (Avatar):', avatarAddress);
console.log('LayerZero Endpoint:', lzEndpoint);
console.log('========================\n');
};

export default func;
func.tags = ['OFT', 'GoodDollar'];
92 changes: 92 additions & 0 deletions packages/bridge-contracts/deploy/utils/verifyContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';

/**
* Verify a contract on block explorer (Etherscan, etc.)
*
* @param hre Hardhat runtime environment
* @param contractAddress The address of the contract to verify
* @param constructorArgs Optional constructor arguments array
* @param contractName Optional contract name for logging
* @returns Promise that resolves when verification is complete
*/
export async function verifyContract(
hre: HardhatRuntimeEnvironment,
contractAddress: string,
constructorArgs: any[] = [],
contractName?: string,
): Promise<void> {
const networkName = hre.network.name;
const displayName = contractName || contractAddress;

// Skip verification for local networks
if (['hardhat', 'localhost', 'develop'].includes(networkName)) {
console.log(`ℹ️ Skipping verification for ${displayName} on local network: ${networkName}`);
return;
}

console.log(`\n🔍 Verifying ${displayName}...`);
console.log(` Address: ${contractAddress}`);
if (constructorArgs.length > 0) {
console.log(` Constructor args: ${JSON.stringify(constructorArgs)}`);
}

try {
await hre.run('verify:verify', {
address: contractAddress,
constructorArguments: constructorArgs,
});
console.log(`✅ ${displayName} verified successfully`);
} catch (error: any) {
const errorMessage = error.message || error.toString() || '';

if (errorMessage.includes('Already Verified') || errorMessage.includes('already verified')) {
console.log(`ℹ️ ${displayName} already verified`);
} else {
console.log(`⚠️ ${displayName} verification error: ${errorMessage}`);
console.log(` You can verify manually using:`);
if (constructorArgs.length > 0) {
console.log(` npx hardhat verify --network ${networkName} ${contractAddress} ${constructorArgs.join(' ')}`);
} else {
console.log(` npx hardhat verify --network ${networkName} ${contractAddress}`);
}
}
}
}

/**
* Verify multiple contracts in sequence
*
* @param hre Hardhat runtime environment
* @param contracts Array of contract verification configs
*/
export async function verifyContracts(
hre: HardhatRuntimeEnvironment,
contracts: Array<{
address: string;
constructorArgs?: any[];
name?: string;
}>,
): Promise<void> {
console.log(`\n=== Starting Contract Verification (${contracts.length} contracts) ===`);

for (const contract of contracts) {
await verifyContract(
hre,
contract.address,
contract.constructorArgs || [],
contract.name,
);
}

// Also verify on Sourcify
console.log('\n🔍 Verifying on Sourcify...');
try {
await hre.run('sourcify');
console.log('✅ Sourcify verification completed');
} catch (error: any) {
console.log('⚠️ Sourcify verification error:', error.message || error.toString());
}

console.log('=== Contract Verification Complete ===\n');
}

Loading