Skip to content

Commit 2ac8c47

Browse files
committed
feat: implement hyperevm specific contract
1 parent caddd79 commit 2ac8c47

7 files changed

Lines changed: 90 additions & 27 deletions

File tree

contracts/bun.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contracts/foundry.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
[profile.default]
2+
libs = ["node_modules"]
23
remappings = ["@hyperlane/=node_modules/@hyperlane-xyz/core/contracts/", "forge-std/=node_modules/forge-std/src/"]
34

5+
[rpc_endpoints]
6+
mainnet = "https://ethereum-rpc.publicnode.com"
7+
48
[lint]
59
exclude_lints = ["pascal-case-struct", "mixed-case-function", "screaming-snake-case-const"]
610

contracts/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
"name": "@noble-assets/usdn",
33
"private": true,
44
"devDependencies": {
5-
"@hyperlane-xyz/core": "9.0.9",
5+
"@hyperlane-xyz/core": "9.0.15",
66
"@openzeppelin/contracts": "5.4.0",
77
"@openzeppelin/contracts-upgradeable": "5.4.0",
8-
"forge-std": "github:foundry-rs/forge-std#v1.10.0"
8+
"forge-std": "github:foundry-rs/forge-std#v1.11.0"
99
},
1010
"patchedDependencies": {
11-
"@hyperlane-xyz/core@9.0.9": "patches/@hyperlane-xyz%2Fcore@9.0.9.patch"
11+
"@hyperlane-xyz/core@9.0.15": "patches/@hyperlane-xyz%2Fcore@9.0.15.patch"
1212
}
1313
}
File renamed without changes.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2025 NASD Inc. All rights reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
pragma solidity 0.8.30;
19+
20+
import {NobleDollar as BaseNobleDollar} from "./NobleDollar.sol";
21+
22+
/**
23+
* @title NobleDollar
24+
* @author John Letey <john@noble.xyz>
25+
* @notice ERC20 Noble Dollar on HyperEVM.
26+
*/
27+
contract NobleDollar is BaseNobleDollar {
28+
/// @notice The address of the Hyperliquid bridge for this token
29+
address public bridge;
30+
31+
constructor(address mailbox_, uint32 tokenId_) BaseNobleDollar(mailbox_) {
32+
// According to the Hyperliquid documentation, this is how you derive the
33+
// bridge address for a linked HyperCore <> HyperEVM token.
34+
//
35+
// "Every token has a system address on the Core, which is the address
36+
// with first byte 0x20 and the remaining bytes all zeros, except for
37+
// the token index encoded in big-endian format."
38+
uint160 base = uint160(0x20) << 152;
39+
bridge = address(uint160(base | uint160(tokenId_)));
40+
}
41+
42+
/// @notice Claims all available yield for the bridge and transfers it to the owner.
43+
function claimForBridge() public {
44+
uint256 amount = claim(bridge);
45+
46+
_update(bridge, owner(), amount);
47+
}
48+
}

contracts/src/NobleDollar.sol

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ import {UIntMath} from "../utils/UIntMath.sol";
2424

2525
/*
2626
27-
███╗ ██╗ ██████╗ ██████╗ ██╗ ███████╗
28-
████╗ ██║██╔═══██╗██╔══██╗██║ ██╔════╝
29-
██╔██╗ ██║██║ ██║██████╔╝██║ █████╗
30-
██║╚██╗██║██║ ██║██╔══██╗██║ ██╔══╝
31-
██║ ╚████║╚██████╔╝██████╔╝███████╗███████╗
32-
╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝
33-
34-
██████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗
27+
███╗ ██╗ ██████╗ ██████╗ ██╗ ███████╗
28+
████╗ ██║██╔═══██╗██╔══██╗██║ ██╔════╝
29+
██╔██╗ ██║██║ ██║██████╔╝██║ █████╗
30+
██║╚██╗██║██║ ██║██╔══██╗██║ ██╔══╝
31+
██║ ╚████║╚██████╔╝██████╔╝███████╗███████╗
32+
╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝
33+
34+
██████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗
3535
██╔══██╗██╔═══██╗██║ ██║ ██╔══██╗██╔══██╗
3636
██║ ██║██║ ██║██║ ██║ ███████║██████╔╝
3737
██║ ██║██║ ██║██║ ██║ ██╔══██║██╔══██╗
@@ -94,17 +94,17 @@ contract NobleDollar is HypERC20 {
9494
_getUSDNStorage().index = IndexingMath.EXP_SCALED_ONE;
9595
}
9696

97-
/// @dev Returns the current index used for yield calculations.
97+
/// @notice Returns the current index used for yield calculations.
9898
function index() public view returns (uint128) {
9999
return _getUSDNStorage().index;
100100
}
101101

102-
/// @dev Returns the amount of principal in existence.
102+
/// @notice Returns the amount of principal in existence.
103103
function totalPrincipal() public view returns (uint112) {
104104
return _getUSDNStorage().totalPrincipal;
105105
}
106106

107-
/// @dev Returns the amount of principal owned for a given account.
107+
/// @notice Returns the amount of principal owned for a given account.
108108
function principalOf(address account) public view returns (uint112) {
109109
return _getUSDNStorage().principal[account];
110110
}
@@ -134,23 +134,32 @@ contract NobleDollar is HypERC20 {
134134
return expectedBalance > currentBalance ? expectedBalance - currentBalance : 0;
135135
}
136136

137+
/// @notice Claims all available yield for the caller.
138+
function claim() public {
139+
claim(msg.sender);
140+
}
141+
137142
/**
138-
* @notice Claims all available yield for the caller.
143+
* @notice Internal function to claim all available yield for a specified address.
139144
* @dev Calculates the claimable yield based on the difference between the expected balance
140145
* (principal * current index) and the actual token balance. Transfers the yield amount
141-
* from the contract to the caller and emits a YieldClaimed event.
142-
* @custom:throws NoClaimableYield if the caller has no yield available to claim.
146+
* from the contract to the specified address and emits a YieldClaimed event.
147+
* @param user The address to claim yield for.
148+
* @return The amount of yield claimed.
149+
* @custom:throws NoClaimableYield if the address has no yield available to claim.
143150
* @custom:emits YieldClaimed when yield is successfully claimed.
144151
*/
145-
function claim() public {
152+
function claim(address user) internal returns (uint256) {
146153
// Avoid DOS claiming by taking min of the contract's balance and user's yield.
147-
uint256 amount = UIntMath.min256(balanceOf(address(this)), yield(msg.sender));
154+
uint256 amount = UIntMath.min256(balanceOf(address(this)), yield(user));
148155

149156
if (amount == 0) revert NoClaimableYield();
150157

151-
_update(address(this), msg.sender, amount);
158+
_update(address(this), user, amount);
159+
160+
emit YieldClaimed(user, amount);
152161

153-
emit YieldClaimed(msg.sender, amount);
162+
return amount;
154163
}
155164

156165
/**

contracts/test/NobleDollar.t.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ contract NobleDollarTest is Test {
3232
address constant USER2 = 0xF2f1ACbe0BA726fEE8d75f3E32900526874740BB;
3333

3434
function setUp() public {
35+
vm.createSelectFork("mainnet");
36+
3537
NobleDollar implementation = new NobleDollar(MAILBOX);
3638
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
3739
address(implementation),

0 commit comments

Comments
 (0)