Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 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
b368b8c
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Mar 11, 2026
75d4e99
feat: add token approval step for MinterBurner in bridge-oft-token te…
blueogin Mar 12, 2026
ac95378
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Mar 16, 2026
67c8191
feat: update XDC RPC URL in hardhat config and add upgrade script for…
blueogin Mar 16, 2026
4274043
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Mar 18, 2026
9abc6ad
feat: add script to set bridge limits for GoodDollarOFTAdapter, enhan…
blueogin Mar 18, 2026
82f7167
docs: update OFT_CONFIGURING_GUIDE.md to clarify optional steps for s…
blueogin Mar 18, 2026
38ce793
fix: update deployment script for GoodDollarOFTAdapter and GoodDollar…
blueogin Mar 18, 2026
5cd6658
refactor: implement CREATE2 salt for deterministic deployments of Goo…
blueogin Mar 18, 2026
dba7ec6
refactor: update deployment scripts to skip verification for 'develop…
blueogin Mar 18, 2026
1c209f3
refactor: replace usage of deployment-oft.json with hardhat-deploy ar…
blueogin Mar 18, 2026
4c87230
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Mar 19, 2026
df199e2
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin Mar 19, 2026
57507a2
refactor: rename GoodDollarMinterBurner to GoodDollarOFTMinterBurner …
blueogin Mar 19, 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=
256 changes: 256 additions & 0 deletions packages/bridge-contracts/deploy/deployOFT.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/***
Copy link
Contributor

Choose a reason for hiding this comment

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

make sure this scripts does all the steps required for full deployment.
besides DAO related actions such as giving minter rights to the minterburner

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I confirmed that all 7 steps are required

Step 1 deploys and initializes the OFT contracts, which has to happen before anything else.
Step 2 sets the adapter as operator via DAO governance
Step 3 grants MINTER_ROLE via DAO governance
Step 4 wires LayerZero connections/enforced options, an external integration step that can fail and has to be run with the correct owner/delegate permissions.
Step 5 sets bridge limits as an optional step because you may not want to set them.
Step 6 tests bridging and is optional because it depends on having balances/gas/fees and is meant as a validation step, not a core deployment requirement.
Step 7 transfers ownership to the DAO Avatar and is optional.

* Hardhat-deploy script for GoodDollar OFT (Omnichain Fungible Token) contracts
*
* Deploys (same pattern as MessageBridge: deterministic proxy + implementation + execute initialize):
* 1. GoodDollarOFTMinterBurner - 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 GoodDollarOFTMinterBurner, deploy implementation
* then execute initialize(nameService, adapter) once the adapter proxy address is known
* 2. Deploy ERC1967Proxy (deterministic) for GoodDollarOFTAdapter, deploy implementation (constructor: token, lzEndpoint), execute initialize(token, minterBurner, owner, feeRecipient)
*
* Note: GoodDollarOFTMinterBurner.initialize wires the adapter as operator.
*/

import { DeployFunction } from 'hardhat-deploy/types';
import { ethers } from 'hardhat';
import Contracts from '@gooddollar/goodprotocol/releases/deployment.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;
}

// CREATE2 salt for implementations:
// hardhat-deploy uses `deterministicDeployment` as the CREATE2 salt.
// We derive it from the contract's compiled bytecode so version changes
// map to different deterministic implementation addresses.
const minterBurnerArtifact = await hre.artifacts.readArtifact('GoodDollarOFTMinterBurner');
const minterBurnerImplSalt = ethers.utils.keccak256(minterBurnerArtifact.bytecode);
const oftAdapterArtifact = await hre.artifacts.readArtifact('GoodDollarOFTAdapter');
const oftAdapterImplSalt = ethers.utils.keccak256(oftAdapterArtifact.bytecode);

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

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

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

if (!minterBurnerInitialized) {
console.log('GoodDollarOFTMinterBurner not initialized yet; will initialize after deploying adapter...');
} else {
console.log('GoodDollarOFTMinterBurner already initialized');
}

// Verify GoodDollarOFTMinterBurner implementation (no constructor args) on non-local networks (skip: 'hardhat', 'localhost')
if (!['hardhat', 'localhost'].includes(networkName)) {
await verifyContract(hre as any, minterBurnerImpl.address, [], 'GoodDollarOFTMinterBurner');
}

// --- 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);

// Initialize minter/burner after we know the adapter address
if (!minterBurnerInitialized) {
console.log('Initializing GoodDollarOFTMinterBurner with adapter address...');
const minterBurnerInitData = minterBurnerContract.interface.encodeFunctionData('initialize', [
nameServiceAddress,
oftAdapterAddress,
]);
await deployments.execute(
'GoodDollarOFTMinterBurner',
{ from: root.address },
'initialize',
minterBurnerImpl.address,
minterBurnerInitData,
);
console.log('GoodDollarOFTMinterBurner initialized');
}

const oftAdapterImpl = await deployments.deploy('GoodDollarOFTAdapter_Implementation', {
contract: 'GoodDollarOFTAdapter',
from: root.address,
deterministicDeployment: oftAdapterImplSalt,
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');
}

// Verify GoodDollarOFTAdapter implementation (constructor: tokenAddress, lzEndpoint) on non-local networks (skip: 'hardhat', 'localhost')
if (!['hardhat', 'localhost'].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('GoodDollarOFTMinterBurner:', 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'];
Loading