Add mainnet cW market registry and align ops scripts with Stack A.
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m18s
CI/CD Pipeline / Security Scanning (push) Successful in 2m27s
CI/CD Pipeline / Lint and Format (push) Failing after 46s
CI/CD Pipeline / Terraform Validation (push) Failing after 28s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 29s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 1m0s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 29s
Validation / validate-genesis (push) Successful in 30s
Validation / validate-terraform (push) Failing after 32s
Validation / validate-kubernetes (push) Failing after 10s
Validation / validate-smart-contracts (push) Failing after 13s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 22s
Verify Deployment / Verify Deployment (push) Failing after 11m53s

- CWTokenMarketRegistry + USD feeds for mainnet cW* market anchors
- HYBX cross-chain treasury line registry expansion
- PMM seed default → live DODOPMMIntegration (0x86ADA6Ef…)
- mint-xau-chain138: troy-ounce mint helper with multisig path
- relay: prefer CCIP_MAINNET_LINK138 relayer key on default profile

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-06-22 19:50:52 -07:00
parent 5b827b7548
commit 6ba33a4d3f
14 changed files with 772 additions and 31 deletions

View File

@@ -1,6 +1,6 @@
{
"version": "1.0.0",
"updated": "2026-06-17",
"updated": "2026-06-20",
"description": "OMNL office settlement lines \u2014 treasury EVM wallets + canonical M1 token anchors per chain.",
"lines": [
{
@@ -20,7 +20,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x63246fbf065dc14fcd18890be3b3274a90faebb4defa602cfeaaf56254d5c3eb",
@@ -39,7 +41,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xf3b667aa86df7021f12238da02013ea06691654a19e14820a8e1f0810ea8584b",
@@ -58,7 +62,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x329d143e0dd4209e5dadc159dc39a1b2121f9f0e1daa7f6d4ef7fdd685017e86",
@@ -77,7 +83,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x00709b1e16c8ea744c115bbcf28c588bceefe2ee79b8ccf3ec723bafee375b0a",
@@ -96,7 +104,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x98179d4cf92f0703702f0f98eda788932cb5d3bf823df275b6ca826d40df941a",
@@ -115,7 +125,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x7e796548c9367f06628f65d7cbb8dbb104c08b0559e3079d3e4ba2868aab1a02",
@@ -134,7 +146,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x78287281703fd64c3bd014a5eb61130324075ea91e286def362ba3cbb06237ac",
@@ -153,7 +167,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xafe717d3727d08ef51307b96d4c090f69cd3f8b32622c6d6a9068d16b13da4f6",
@@ -172,7 +188,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x7eee9b83385d0220cac4fdae36222ed0364d445baf60e8a1fbd24d8100819e7e",
@@ -191,7 +209,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x751d9a27b003b5b8ec98f086003238f1ac16f1db5083817f1d8e8729bd3dbda8",
@@ -210,7 +230,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x2065d9d64806c8946d4d59e78cd709cadb0275f2962bc986e9bdd76d5b2bdde3",
@@ -229,7 +251,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x981d13dd8853a67cb3d865e9b77400c2e848a753be12c1cf693621288b2ba253",
@@ -248,7 +272,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x4b492f8afa40d6191fe1319190345b32fdc1918b727de2b54f86f9e890834f05",
@@ -267,7 +293,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xa86f3acebcecb46454e091fcf24a35f9efaa939421983f69563b53f55bb7ae94",
@@ -286,7 +314,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xc236020f755fe76acaf5eb5fd8a53114d1f49e8e58d985beb4f4410d52abd2a6",
@@ -305,7 +335,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xc820d6d2cf4b054b08435a1ea3318a88ffa849b51fc61ab12ef15b248423be96",
@@ -324,7 +356,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xf3c45215b31095db55af3e8a65668f86de60f689c4fdeb8d54e89f3504752c43",
@@ -343,7 +377,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xc96c3bb01849a9f3d8fd62f1114b5606a9235d53f771b424b299207952175f7d",
@@ -362,7 +398,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x5d44d97f66dcb6591d36b70375b84c73b73cfc8f3788228b5977632beb995b1d",
@@ -381,7 +419,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xa1fd57654dc5fef77117da02995c584a19a4b6949a03cd91ccb16551d7d3d01d",
@@ -400,7 +440,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xd5666338b8da738cf82c681e4a39ae5f817861061accfd98464a30470bdfcc58",
@@ -419,7 +461,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0x3d83b0f803d19de0cd096a4c29fc27c8ef02248d8e5978a3a2e16bbe714754dc",
@@ -438,7 +482,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xaede6a63b3f49d0d18b13267855274e250516a97c84dcf0d31e17f359ceca4ff",
@@ -457,7 +503,9 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
},
{
"lineId": "0xcdd67c94cd265ef8dd383a06b7ac4733e83a8c7cdf5030aed9ec36ab2999004a",
@@ -476,7 +524,10 @@
"tokenM0": "0x0000000000000000000000000000000000000000",
"tokenM1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
},
"inboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8",
"inboundSettlementType": "gnosis-safe"
}
]
],
"globalInboundSettlementMainnet": "0xB08502ceE2545eEF782e933CFb80D0663E564da8"
}

View File

@@ -0,0 +1,216 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20Supply} from "./interfaces/IERC20Supply.sol";
import {ICWTokenUsdFeedView, IUniV3PoolTokens} from "./interfaces/IMarketAnchor.sol";
/**
* @title CWTokenMarketRegistry
* @notice On-chain market anchor for mainnet cW* — face USD, published TWAP, x402 settlement mirror,
* and Chainlink-compatible per-token feeds for indexers that ingest custom oracles.
* @dev Does not unilaterally control Etherscan UI; pairs with UniV3 liquidity + operator sync.
*/
contract CWTokenMarketRegistry {
uint8 public constant PRICE_DECIMALS = 8;
/// @dev Max deviation from faceValueUsdE8 on publishPrice (1500 = 15%) for USD-face cW* stables.
uint16 public constant MAX_PEG_DEVIATION_BPS = 1500;
address public admin;
bool public paused;
struct TokenConfig {
bool registered;
uint8 tokenDecimals;
uint64 faceValueUsdE8;
address uniV3Pool;
bool cwIsToken0;
uint64 publishedPriceUsdE8;
uint256 publishedAt;
uint80 roundId;
uint256 settlementCount;
}
mapping(address => TokenConfig) public tokens;
mapping(address => address) public tokenFeed;
address[] public tokenList;
event AdminChanged(address indexed newAdmin);
event Paused();
event Unpaused();
event TokenRegistered(
address indexed token,
uint64 faceValueUsdE8,
address indexed uniV3Pool,
address indexed feed
);
event MarketPricePublished(
address indexed token,
uint80 roundId,
uint64 priceUsdE8,
uint256 timestamp,
string source
);
event X402SettlementRecorded(
address indexed token,
address indexed payer,
address indexed payTo,
uint256 amountRaw,
uint64 faceUsdE8,
uint256 settlementChainId,
bytes32 settlementTxHash,
bytes32 resourceHash
);
modifier onlyAdmin() {
require(msg.sender == admin, "only admin");
_;
}
modifier whenNotPaused() {
require(!paused, "paused");
_;
}
constructor(address _admin) {
require(_admin != address(0), "zero admin");
admin = _admin;
}
function setAdmin(address newAdmin) external onlyAdmin {
require(newAdmin != address(0), "zero admin");
admin = newAdmin;
emit AdminChanged(newAdmin);
}
function pause() external onlyAdmin {
paused = true;
emit Paused();
}
function unpause() external onlyAdmin {
paused = false;
emit Unpaused();
}
function registerToken(
address token,
uint8 tokenDecimals,
uint64 faceValueUsdE8,
address uniV3Pool,
bool cwIsToken0,
address feed
) external onlyAdmin whenNotPaused {
require(token != address(0), "zero token");
require(faceValueUsdE8 > 0, "zero face");
require(feed != address(0), "zero feed");
require(
ICWTokenUsdFeedView(feed).registry() == address(this)
&& ICWTokenUsdFeedView(feed).token() == token,
"feed mismatch"
);
if (uniV3Pool != address(0)) {
address t0 = IUniV3PoolTokens(uniV3Pool).token0();
address t1 = IUniV3PoolTokens(uniV3Pool).token1();
require(t0 == token || t1 == token, "pool token");
}
if (tokens[token].registered) {
require(tokens[token].tokenDecimals == tokenDecimals, "decimals locked");
} else {
tokenList.push(token);
}
tokens[token] = TokenConfig({
registered: true,
tokenDecimals: tokenDecimals,
faceValueUsdE8: faceValueUsdE8,
uniV3Pool: uniV3Pool,
cwIsToken0: cwIsToken0,
publishedPriceUsdE8: faceValueUsdE8,
publishedAt: block.timestamp,
roundId: 1,
settlementCount: tokens[token].settlementCount
});
tokenFeed[token] = feed;
emit TokenRegistered(token, faceValueUsdE8, uniV3Pool, feed);
emit MarketPricePublished(token, 1, faceValueUsdE8, block.timestamp, "face_bootstrap");
}
function publishPrice(address token, uint64 priceUsdE8, string calldata source)
external
onlyAdmin
whenNotPaused
{
TokenConfig storage t = _requireToken(token);
require(priceUsdE8 > 0, "zero price");
_enforcePegBand(t.faceValueUsdE8, priceUsdE8);
t.roundId += 1;
t.publishedPriceUsdE8 = priceUsdE8;
t.publishedAt = block.timestamp;
emit MarketPricePublished(token, t.roundId, priceUsdE8, block.timestamp, source);
}
function recordX402Settlement(
address token,
address payer,
address payTo,
uint256 amountRaw,
uint64 faceUsdE8,
uint256 settlementChainId,
bytes32 settlementTxHash,
bytes32 resourceHash
) external onlyAdmin whenNotPaused {
TokenConfig storage t = _requireToken(token);
require(payer != address(0) && payTo != address(0), "zero addr");
require(amountRaw > 0, "zero amount");
t.settlementCount += 1;
emit X402SettlementRecorded(
token,
payer,
payTo,
amountRaw,
faceUsdE8,
settlementChainId,
settlementTxHash,
resourceHash
);
}
function latestPriceUsdE8(address token) external view returns (uint64) {
TokenConfig storage t = _requireToken(token);
return t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
}
/// @notice USD market cap with 8 decimals: priceUsdE8 * totalSupply / 10^tokenDecimals
function onchainMarketCapUsdE8(address token) external view returns (uint256) {
TokenConfig storage t = _requireToken(token);
uint64 price = t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
uint256 supply = IERC20Supply(token).totalSupply();
return (uint256(price) * supply) / (10 ** uint256(t.tokenDecimals));
}
function tokenCount() external view returns (uint256) {
return tokenList.length;
}
function latestRoundDataForToken(address token)
external
view
returns (uint80 roundId, int256 answer, uint256 updatedAt)
{
TokenConfig storage t = _requireToken(token);
uint64 price = t.publishedPriceUsdE8 != 0 ? t.publishedPriceUsdE8 : t.faceValueUsdE8;
return (t.roundId, int256(uint256(price)), t.publishedAt);
}
function _requireToken(address token) internal view returns (TokenConfig storage t) {
t = tokens[token];
require(t.registered, "unregistered");
}
function _enforcePegBand(uint64 faceUsdE8, uint64 priceUsdE8) internal pure {
uint256 maxDev = (uint256(faceUsdE8) * MAX_PEG_DEVIATION_BPS) / 10_000;
require(priceUsdE8 >= faceUsdE8 - maxDev && priceUsdE8 <= faceUsdE8 + maxDev, "peg band");
}
}

View File

@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IAggregatorV3} from "./interfaces/IAggregatorV3.sol";
import {CWTokenMarketRegistry} from "./CWTokenMarketRegistry.sol";
/**
* @title CWTokenUsdFeed
* @notice Per-token Chainlink AggregatorV3-compatible USD feed backed by CWTokenMarketRegistry.
*/
contract CWTokenUsdFeed is IAggregatorV3 {
CWTokenMarketRegistry public immutable registry;
address public immutable token;
string public description_;
constructor(CWTokenMarketRegistry _registry, address _token, string memory _description) {
require(address(_registry) != address(0) && _token != address(0), "zero arg");
registry = _registry;
token = _token;
description_ = _description;
}
function decimals() external pure returns (uint8) {
return 8;
}
function description() external view returns (string memory) {
return description_;
}
function version() external pure returns (uint256) {
return 1;
}
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
(roundId, answer, startedAt, updatedAt, answeredInRound) = latestRoundData();
if (_roundId != 0) {
require(roundId == _roundId, "stale round");
}
}
function latestRoundData()
public
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
)
{
(roundId, answer, updatedAt) = registry.latestRoundDataForToken(token);
startedAt = updatedAt;
answeredInRound = roundId;
}
}

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @notice Chainlink AggregatorV3-compatible surface for explorer / DeFi integrations.
interface IAggregatorV3 {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}

View File

@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20Supply {
function totalSupply() external view returns (uint256);
function decimals() external view returns (uint8);
}

View File

@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface ICWTokenUsdFeedView {
function token() external view returns (address);
function registry() external view returns (address);
}
interface IUniV3PoolTokens {
function token0() external view returns (address);
function token1() external view returns (address);
}

View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console2} from "forge-std/Script.sol";
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
contract DeployCWTokenMarketRegistry is Script {
struct TokenSeed {
address token;
uint8 decimals;
uint64 faceUsdE8;
address uniV3Pool;
bool cwIsToken0;
string feedDescription;
}
function run() external {
vm.startBroadcast();
address admin = msg.sender;
CWTokenMarketRegistry registry = new CWTokenMarketRegistry(admin);
TokenSeed[] memory seeds = _seeds();
for (uint256 i = 0; i < seeds.length; i++) {
TokenSeed memory s = seeds[i];
CWTokenUsdFeed feed = new CWTokenUsdFeed(registry, s.token, s.feedDescription);
registry.registerToken(
s.token, s.decimals, s.faceUsdE8, s.uniV3Pool, s.cwIsToken0, address(feed)
);
console2.log("Registered", s.token);
console2.log(" feed", address(feed));
}
vm.stopBroadcast();
console2.log("CWTokenMarketRegistry", address(registry));
}
function _seeds() internal pure returns (TokenSeed[] memory) {
TokenSeed[] memory seeds = new TokenSeed[](2);
seeds[0] = TokenSeed({
token: 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a,
decimals: 6,
faceUsdE8: 100_000_000,
uniV3Pool: 0x1Cf2e685682C7F7beF508F0Af15Dfb5CDda01ee3,
cwIsToken0: true,
feedDescription: "cWUSDC / USD (GRU market anchor)"
});
seeds[1] = TokenSeed({
token: 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE,
decimals: 6,
faceUsdE8: 100_000_000,
uniV3Pool: 0x6eF5A392BcbE42F3D0a855595bEa60CFf9451784,
cwIsToken0: true,
feedDescription: "cWUSDT / USD (GRU market anchor)"
});
return seeds;
}
}

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console2} from "forge-std/Script.sol";
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
/// @notice Register remaining mainnet CompliantWrappedToken assets on deployed registry.
contract RegisterAdditionalCWMarketTokens is Script {
struct Seed {
address token;
uint8 decimals;
uint64 faceUsdE8;
address uniV3Pool;
bool cwIsToken0;
string symbol;
}
function run() external {
address registryAddr = vm.envAddress("CW_MARKET_REGISTRY_MAINNET");
CWTokenMarketRegistry registry = CWTokenMarketRegistry(registryAddr);
Seed[] memory seeds = _seeds();
vm.startBroadcast();
for (uint256 i = 0; i < seeds.length; i++) {
Seed memory s = seeds[i];
if (registry.tokenFeed(s.token) != address(0)) {
console2.log("skip already registered", s.symbol, s.token);
continue;
}
CWTokenUsdFeed feed = new CWTokenUsdFeed(
registry, s.token, string.concat(s.symbol, " / USD (GRU market anchor)")
);
registry.registerToken(
s.token, s.decimals, s.faceUsdE8, s.uniV3Pool, s.cwIsToken0, address(feed)
);
console2.log("Registered", s.symbol, s.token);
console2.log(" feed", address(feed));
}
vm.stopBroadcast();
console2.log("CWTokenMarketRegistry", registryAddr);
}
function _seeds() internal pure returns (Seed[] memory) {
// face $1.00 e8 for ISO fiat stables; pool=0 when no live UniV3 peg pool (DODO/UniV2 only)
Seed[] memory seeds = new Seed[](10);
seeds[0] = Seed(0xD4aEAa8cD3fB41Dc8437FaC7639B6d91B60A5e8d, 6, 100_000_000, address(0), true, "cWEURC");
seeds[1] = Seed(0x855d74FFB6CF75721a9bAbc8B2ed35c8119241dC, 6, 100_000_000, address(0), true, "cWEURT");
seeds[2] = Seed(0xc074007dc0Bfb384B1cf6426a56287Ed23FE4D52, 6, 100_000_000, address(0), true, "cWGBPC");
seeds[3] = Seed(0x1dDF9970F01c76A692Fdba2706203E6f16e0C46F, 6, 100_000_000, address(0), true, "cWGBPT");
seeds[4] = Seed(0x5020Db641B3Fc0dAbBc0c688C845bc4E3699f35F, 6, 100_000_000, address(0), true, "cWAUDC");
seeds[5] = Seed(0x07EEd0D7dD40984e47B9D3a3bdded1c536435582, 6, 100_000_000, address(0), true, "cWJPYC");
seeds[6] = Seed(0x0F91C5E6Ddd46403746aAC970D05d70FFe404780, 6, 100_000_000, address(0), true, "cWCHFC");
seeds[7] = Seed(0x209FE32fe7B541751D190ae4e50cd005DcF8EDb4, 6, 100_000_000, address(0), true, "cWCADC");
seeds[8] = Seed(0x572Be0fa8CA0534d642A567CEDb398B771D8a715, 6, 3_300_000_000_00, address(0), true, "cWXAUC");
seeds[9] = Seed(0xACE1DBF857549a11aF1322e1f91F2F64b029c906, 6, 3_300_000_000_00, address(0), true, "cWXAUT");
// cWBTC: register separately with live BTC/USD face (volatile)
return seeds;
}
}

View File

@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console2} from "forge-std/Script.sol";
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
import {IAggregatorV3} from "../../contracts/mainnet-market/interfaces/IAggregatorV3.sol";
/// @notice Register mainnet cWBTC with live Chainlink BTC/USD face (8 decimals).
contract RegisterCWBTMarketToken is Script {
address internal constant CWBTC = 0x2BBe3c809386FA5349EB2da31E0848ee9f54EE59;
address internal constant CHAINLINK_BTC_USD = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
function run() external {
address registryAddr = vm.envAddress("CW_MARKET_REGISTRY_MAINNET");
CWTokenMarketRegistry registry = CWTokenMarketRegistry(registryAddr);
if (registry.tokenFeed(CWBTC) != address(0)) {
console2.log("skip already registered cWBTC", CWBTC);
console2.log(" feed", registry.tokenFeed(CWBTC));
return;
}
(, int256 answer,, uint256 updatedAt,) = IAggregatorV3(CHAINLINK_BTC_USD).latestRoundData();
require(answer > 0, "chainlink btc/usd stale");
require(updatedAt > 0, "chainlink btc/usd no timestamp");
uint64 faceUsdE8 = uint64(uint256(answer));
require(faceUsdE8 > 0, "zero btc usd");
console2.log("Chainlink BTC/USD faceUsdE8", faceUsdE8);
vm.startBroadcast();
CWTokenUsdFeed feed = new CWTokenUsdFeed(registry, CWBTC, "cWBTC / USD (GRU market anchor)");
registry.registerToken(CWBTC, 8, faceUsdE8, address(0), true, address(feed));
vm.stopBroadcast();
console2.log("Registered cWBTC", CWBTC);
console2.log(" feed", address(feed));
console2.log("CWTokenMarketRegistry", registryAddr);
}
}

View File

@@ -24,7 +24,7 @@ fi
command -v cast >/dev/null 2>&1 || { echo "cast is required" >&2; exit 1; }
RPC_URL_138="${RPC_URL_138:-${RPC_URL:-http://192.168.11.221:8545}}"
DODO_PMM_INTEGRATION_ADDRESS="${CHAIN_138_DODO_PMM_INTEGRATION:-${DODO_PMM_INTEGRATION_ADDRESS:-0x5BDc62f1ae7D630c37A8B363a1d49845356Ee72d}}"
DODO_PMM_INTEGRATION_ADDRESS="${CHAIN_138_DODO_PMM_INTEGRATION:-${DODO_PMM_INTEGRATION_ADDRESS:-0x86ADA6Ef91A3B450F89f2b751e93B1b7A3218895}}"
PRIVATE_KEY="${PRIVATE_KEY:-${DEPLOYER_PRIVATE_KEY:-}}"
CHAIN_GAS_PRICE="${CHAIN_GAS_PRICE:-1000000000}"
APPROVE_GAS_LIMIT="${APPROVE_GAS_LIMIT:-200000}"

View File

@@ -65,9 +65,65 @@ echo " Troy ounces: $OZ -> base units: $BASE_UNITS"
echo " cXAUC: $DO_CXAUC cXAUT: $DO_CXAUT"
echo ""
MULTISIG="${ADDR_MULTISIG:-0xb9E29cFa1f89d369671E640d0BB3aD94Cab43965}"
GAS_LIMIT_MULTISIG="${GAS_LIMIT_MULTISIG:-600000}"
GAS_PRICE="${GAS_PRICE_138:-1000000000}"
extract_tx_hash() {
awk '/^transactionHash[[:space:]]/ {print $2; exit}'
}
tx_status_ok() {
local txhash="$1"
[[ -n "$txhash" && "$txhash" == 0x* ]] || return 1
local st
st=$(cast receipt "$txhash" status --rpc-url "$RPC" 2>/dev/null | awk '{print $1}' || echo "")
[[ "$st" == "1" || "$st" == "true" ]]
}
mint_via_multisig() {
local addr="$1" label="$2"
local data txid exec_out txhash count
data=$(cast calldata "mint(address,uint256)" "$TO" "$BASE_UNITS")
echo -n " multisig mint $label... "
if [ -n "${DRY_RUN:-}" ]; then
echo "[dry-run]"
return 0
fi
cast send "$MULTISIG" \
"submitTransaction(address,uint256,bytes)" "$addr" 0 "$data" \
--rpc-url "$RPC" --private-key "$PRIVATE_KEY" --legacy \
--gas-limit "$GAS_LIMIT_MULTISIG" --gas-price "$GAS_PRICE" >/dev/null || {
echo "FAIL (submit)"; return 1
}
count=$(cast call "$MULTISIG" "getTransactionCount()(uint256)" --rpc-url "$RPC" | awk '{print $1}')
txid=$((count - 1))
exec_out=$(cast send "$MULTISIG" "executeTransaction(uint256)" "$txid" \
--rpc-url "$RPC" --private-key "$PRIVATE_KEY" --legacy \
--gas-limit "$GAS_LIMIT_MULTISIG" --gas-price "$GAS_PRICE" 2>&1) || {
echo "FAIL (execute)"; return 1
}
txhash=$(echo "$exec_out" | extract_tx_hash)
if tx_status_ok "$txhash"; then
echo "OK (tx $txhash)"
return 0
fi
echo "FAIL (receipt: $txhash)"
return 1
}
run_mint() {
local addr="$1"
local label="$2"
local owner lc_to lc_own lc_ms
owner=$(cast call "$addr" "owner()(address)" --rpc-url "$RPC" 2>/dev/null || echo "")
lc_to=$(echo "$TO" | tr '[:upper:]' '[:lower:]')
lc_own=$(echo "$owner" | tr '[:upper:]' '[:lower:]')
lc_ms=$(echo "$MULTISIG" | tr '[:upper:]' '[:lower:]')
if [[ "$lc_own" == "$lc_ms" ]]; then
mint_via_multisig "$addr" "$label" || return 1
return 0
fi
if [ -n "${DRY_RUN:-}" ]; then
echo "[dry-run] cast send $label $addr mint $TO $BASE_UNITS"
return 0

View File

@@ -90,13 +90,15 @@ fi
# Ensure PRIVATE_KEY is exported
if [ -n "$PRIVATE_KEY" ]; then
export PRIVATE_KEY
if [ "$PROFILE" != "mainnet-link-138" ] || [ -z "${CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY:-}" ]; then
if { [ "$PROFILE" = "mainnet-link-138" ] || [ -z "$PROFILE" ]; } && [ -n "${CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY:-}" ]; then
: # dedicated relayer set below
else
export RELAYER_PRIVATE_KEY="$PRIVATE_KEY"
fi
fi
# Dedicated relayer for mainnet-link-138 (wins over deployer PRIVATE_KEY).
if [ "$PROFILE" = "mainnet-link-138" ] && [ -n "${CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY:-}" ]; then
# Dedicated relayer for mainnet-link-138 and default WETH lane (wins over deployer PRIVATE_KEY).
if { [ "$PROFILE" = "mainnet-link-138" ] || [ -z "$PROFILE" ]; } && [ -n "${CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY:-}" ]; then
export RELAYER_PRIVATE_KEY="$CCIP_MAINNET_LINK138_RELAYER_PRIVATE_KEY"
if [ -n "${CCIP_MAINNET_LINK138_RELAYER_ADDRESS:-}" ]; then
export RELAYER_ADDRESS="$CCIP_MAINNET_LINK138_RELAYER_ADDRESS"

View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
contract CWTokenMarketRegistryTest is Test {
CWTokenMarketRegistry registry;
MockErc20 token;
CWTokenUsdFeed feed;
function setUp() public {
registry = new CWTokenMarketRegistry(address(this));
token = new MockErc20(6, 15_452_106_931_334_998_000_000);
feed = new CWTokenUsdFeed(registry, address(token), "test cWUSD / USD");
registry.registerToken(address(token), 6, 100_000_000, address(0), true, address(feed));
}
function test_latestRoundData_faceBootstrap() public view {
(, int256 answer,,,) = feed.latestRoundData();
assertEq(answer, 100_000_000);
}
function test_onchainMarketCap() public view {
uint256 cap = registry.onchainMarketCapUsdE8(address(token));
assertEq(cap, 15_452_106_931_334_998_000_000 * 100_000_000 / 1e6);
}
function test_publishPrice_updatesFeed() public {
registry.publishPrice(address(token), 99_500_000, "univ3_twap");
(, int256 answer,,,) = feed.latestRoundData();
assertEq(answer, 99_500_000);
}
function test_publishPrice_revertsOutsidePegBand() public {
vm.expectRevert(bytes("peg band"));
registry.publishPrice(address(token), 50_000_000, "bad");
}
function test_registerToken_revertsFeedMismatch() public {
CWTokenUsdFeed badFeed = new CWTokenUsdFeed(registry, address(0xBEEF), "bad");
vm.expectRevert(bytes("feed mismatch"));
registry.registerToken(address(0xCAFE), 6, 100_000_000, address(0), true, address(badFeed));
}
function test_recordX402Settlement() public {
registry.recordX402Settlement(
address(token),
address(0x4A66),
address(0x6435),
10_000,
10_000,
8453,
bytes32(uint256(1)),
keccak256("resource")
);
(,,,,,,,, uint256 settlementCount) = registry.tokens(address(token));
assertEq(settlementCount, 1);
}
function test_pauseBlocksPublish() public {
registry.pause();
vm.expectRevert(bytes("paused"));
registry.publishPrice(address(token), 100_000_000, "x");
}
}
contract MockErc20 {
uint8 public decimals;
uint256 public totalSupply;
constructor(uint8 d, uint256 supply) {
decimals = d;
totalSupply = supply;
}
}

View File

@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {CWTokenMarketRegistry} from "../../contracts/mainnet-market/CWTokenMarketRegistry.sol";
import {CWTokenUsdFeed} from "../../contracts/mainnet-market/CWTokenUsdFeed.sol";
contract CWTokenMarketRegistryForkTest is Test {
address constant CWUSDC = 0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a;
address constant CWUSDT = 0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE;
address constant POOL_CWUSDC = 0x1Cf2e685682C7F7beF508F0Af15Dfb5CDda01ee3;
address constant POOL_CWUSDT = 0x6eF5A392BcbE42F3D0a855595bEa60CFf9451784;
address constant CWBTC = 0x2BBe3c809386FA5349EB2da31E0848ee9f54EE59;
address constant CHAINLINK_BTC_USD = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
function test_fork_registerMainnetTokensAndMarketCap() public {
string memory rpc = vm.envOr(string("ETHEREUM_MAINNET_RPC"), string("https://ethereum.publicnode.com"));
vm.createSelectFork(rpc);
CWTokenMarketRegistry registry = new CWTokenMarketRegistry(address(this));
CWTokenUsdFeed feedUsdc = new CWTokenUsdFeed(registry, CWUSDC, "cWUSDC / USD");
registry.registerToken(CWUSDC, 6, 100_000_000, POOL_CWUSDC, true, address(feedUsdc));
CWTokenUsdFeed feedUsdt = new CWTokenUsdFeed(registry, CWUSDT, "cWUSDT / USD");
registry.registerToken(CWUSDT, 6, 100_000_000, POOL_CWUSDT, true, address(feedUsdt));
(, int256 usdcPx,,,) = feedUsdc.latestRoundData();
assertEq(usdcPx, 100_000_000);
uint256 mcap = registry.onchainMarketCapUsdE8(CWUSDC);
assertGt(mcap, 0);
assertEq(registry.tokenFeed(CWUSDC), address(feedUsdc));
assertEq(registry.tokenCount(), 2);
}
function test_fork_registerCWBTWithChainlinkBtcUsd() public {
string memory rpc = vm.envOr(string("ETHEREUM_MAINNET_RPC"), string("https://ethereum.publicnode.com"));
vm.createSelectFork(rpc);
(, int256 answer,,,) = AggregatorV3(CHAINLINK_BTC_USD).latestRoundData();
assertGt(answer, 0);
CWTokenMarketRegistry registry = new CWTokenMarketRegistry(address(this));
uint64 faceUsdE8 = uint64(uint256(answer));
CWTokenUsdFeed feed = new CWTokenUsdFeed(registry, CWBTC, "cWBTC / USD");
registry.registerToken(CWBTC, 8, faceUsdE8, address(0), true, address(feed));
(, int256 px,,,) = feed.latestRoundData();
assertEq(px, answer);
assertGt(registry.onchainMarketCapUsdE8(CWBTC), 0);
}
}
interface AggregatorV3 {
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}