├── .changelog ├── unreleased │ └── .gitkeep ├── config.toml ├── v10.1.0 │ ├── summary.md │ └── dependencies │ │ └── 570-bump-dollar.md ├── v11.1.0 │ ├── summary.md │ └── dependencies │ │ └── 609-611-bump-orbiter.md ├── v4.0.1 │ ├── summary.md │ └── bug-fixes │ │ └── 274-distribution-hooks.md ├── v4.0.3 │ ├── summary.md │ ├── improvements │ │ └── 000-migrate-ftf.md │ └── dependencies │ │ ├── 001-pfm.md │ │ └── 000-ftf.md ├── v4.1.2 │ ├── summary.md │ ├── improvements │ │ └── 357-migrate-forwarding.md │ └── dependencies │ │ ├── 357-bump-forwarding.md │ │ ├── 359-bump-cctp.md │ │ └── 359-bump-ftf.md ├── v10.1.2 │ ├── summary.md │ └── dependencies │ │ ├── 600-bump-forwarding.md │ │ └── 600-bump-comet.md ├── v4.0.2 │ ├── summary.md │ └── improvements │ │ └── 277-tariff-query.md ├── v4.1.1 │ ├── summary.md │ └── bug-fixes │ │ └── 353-abci.md ├── v4.1.3 │ ├── summary.md │ └── dependencies │ │ └── 363-bump-cctp.md ├── v8.0.3 │ ├── summary.md │ └── dependencies │ │ ├── 431-bump-halo.md │ │ └── 433-bump-math.md ├── v8.0.4 │ ├── summary.md │ └── dependencies │ │ ├── 440-bump-authority.md │ │ └── 441-bump-sdk.md ├── v8.0.5 │ ├── summary.md │ └── dependencies │ │ └── 466-bump-comet.md ├── v9.0.1 │ ├── summary.md │ └── dependencies │ │ ├── 511-bump-dollar.md │ │ └── 509-bump-authority.md ├── v9.0.2 │ ├── summary.md │ └── dependencies │ │ └── 513-bump-ibc.md ├── v9.0.3 │ ├── summary.md │ └── dependencies │ │ └── 522-bump-swap.md ├── v9.0.4 │ ├── summary.md │ └── dependencies │ │ ├── 549-bump-swap.md │ │ └── 549-bump-dollar.md ├── v4.0.0 │ ├── bug-fixes │ │ ├── 252-simulation-tests.md │ │ └── 253-ledger-macos-sonoma.md │ ├── dependencies │ │ ├── 250-bump-ibc.md │ │ └── 250-bump-pfm.md │ ├── improvements │ │ └── 241-ftf-interface-changes.md │ ├── features │ │ └── 215-rosetta-support.md │ └── summary.md ├── v10.0.0 │ ├── dependencies │ │ └── 530-bump-go.md │ ├── improvements │ │ └── 516-module-path.md │ ├── bug-fixes │ │ └── 515-empty-injections.md │ ├── features │ │ ├── 528-permission-hyperlane.md │ │ ├── 519-integrate-hyperlane.md │ │ ├── 527-integrate-warp.md │ │ ├── 541-integrate-ratelimit.md │ │ └── 526-bump-dollar.md │ └── summary.md ├── v5.0.0 │ ├── improvements │ │ └── 271-module-path.md │ ├── features │ │ └── 380-fast-blocks.md │ ├── dependencies │ │ └── 385-cosmos-sdk.md │ └── summary.md ├── v6.0.0 │ ├── improvements │ │ └── 389-module-path.md │ └── summary.md ├── v7.0.0 │ ├── improvements │ │ └── 399-module-path.md │ ├── bug-fixes │ │ └── 405-bump-halo.md │ └── summary.md ├── v9.0.0 │ ├── improvements │ │ └── 443-module-path.md │ ├── features │ │ ├── 470-enable-swagger.md │ │ ├── 487-in-place-fork.md │ │ ├── 449-integrate-swap.md │ │ ├── 444-integrate-wormhole.md │ │ ├── 448-integrate-dollar.md │ │ └── 463-integrate-jester.md │ ├── dependencies │ │ ├── 455-bump-ftf.md │ │ ├── 464-autocli-maps.md │ │ ├── 495-bump-sdk.md │ │ ├── 480-bump-authority.md │ │ ├── 481-bump-forwarding.md │ │ ├── 506-bump-ibc.md │ │ └── 488-bump-pfm.md │ ├── bug-fixes │ │ └── 432-fix-ica.md │ └── summary.md ├── v11.0.0 │ ├── improvements │ │ ├── 564-module-path.md │ │ ├── 557-remove-heighliner.md │ │ ├── 597-shido-client.md │ │ ├── 603-router-client.md │ │ └── 571-distribution-funds.md │ ├── features │ │ ├── 576-integrate-orbiter.md │ │ └── 587-hyperlane-collateral-token.md │ ├── dependencies │ │ └── 573-bump-dollar.md │ └── summary.md ├── v3.1.0 │ ├── features │ │ └── 235-ibc-authority.md │ ├── improvements │ │ └── 234-module-path.md │ └── summary.md ├── v11.0.1 │ ├── summary.md │ └── improvements │ │ └── 605-static-release-builds.md ├── v10.1.1 │ ├── summary.md │ └── dependencies │ │ └── 582-bump-globalfee.md ├── epilogue.md ├── v10.0.1 │ ├── summary.md │ └── dependencies │ │ └── 567-bump-dollar.md ├── v4.1.0 │ ├── dependencies │ │ ├── 000-sdk.md │ │ ├── 000-ftf.md │ │ ├── 000-ibc.md │ │ └── 000-pfm.md │ ├── improvements │ │ ├── 346-migrate-blockibc.md │ │ └── 249-283-module-path.md │ └── summary.md ├── v8.0.1 │ ├── summary.md │ └── bug-fixes │ │ └── 428-surplus-supply.md ├── v8.0.2 │ ├── summary.md │ └── bug-fixes │ │ └── 429-forwarding-initialization.md └── v8.0.0 │ └── summary.md ├── go.work.example ├── .github ├── images │ ├── dark_banner.png │ └── light_banner.png ├── workflows │ ├── check-license.yaml │ ├── docker-publish.yaml │ ├── draft-release.yaml │ └── e2e-tests.yaml ├── CODEOWNERS ├── license.yml └── README.md ├── .gitignore ├── buf.yaml ├── buf.gen.yaml ├── Dockerfile ├── api ├── gen │ └── index.html ├── generate.sh └── embed.go ├── tools.go ├── orbiter.go ├── jester ├── client.go └── config.go ├── upgrade ├── store.go ├── constants.go └── upgrade.go ├── cmd ├── nobled │ └── main.go ├── root.go ├── init.go └── commands.go ├── e2e ├── readme.md ├── upgrade_test.go ├── conformance_test.go ├── module_check_test.go ├── ratelimit_test.go ├── cctp_roles_test.go ├── ibc_utils.go ├── cctp_receive_message_test.go ├── cctp_receive_message_with_caller_test.go ├── cctp_deposit_for_burn_test.go ├── cctp_deposit_for_burn_with_caller_test.go └── cctp_replace_deposit_for_burn_test.go ├── local_net ├── single-val.sh ├── README.md ├── in-place-fork.sh ├── utils.sh └── multi-val.sh ├── export.go ├── ante_permissioned_liquidity.go ├── ante_permissioned_hyperlane.go ├── Makefile ├── ante.go ├── app.yaml ├── proposal.go ├── legacy.go └── LICENSE /.changelog/unreleased/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.work.example: -------------------------------------------------------------------------------- 1 | go 1.24 2 | 3 | use ( 4 | . 5 | e2e 6 | ) 7 | -------------------------------------------------------------------------------- /.changelog/config.toml: -------------------------------------------------------------------------------- 1 | project_url = "https://github.com/noble-assets/noble" 2 | -------------------------------------------------------------------------------- /.github/images/dark_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noble-assets/noble/HEAD/.github/images/dark_banner.png -------------------------------------------------------------------------------- /.github/images/light_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noble-assets/noble/HEAD/.github/images/light_banner.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .duke 2 | .idea 3 | .vscode 4 | api/proto 5 | api/tmp-swagger-gen 6 | build 7 | go.work 8 | go.work.sum 9 | -------------------------------------------------------------------------------- /.changelog/v10.1.0/summary.md: -------------------------------------------------------------------------------- 1 | *Jul 30, 2025* 2 | 3 | This is a minor release to the v10 Stratum line, codenamed Ember. 4 | -------------------------------------------------------------------------------- /.changelog/v11.1.0/summary.md: -------------------------------------------------------------------------------- 1 | *Nov 19, 2025* 2 | 3 | This is a minor release to the v11 Flux line, codenamed Pulse. 4 | -------------------------------------------------------------------------------- /.changelog/v4.0.1/summary.md: -------------------------------------------------------------------------------- 1 | *Nov 16, 2023* 2 | 3 | This is a consensus breaking patch release to the v4 Argon line. 4 | -------------------------------------------------------------------------------- /.changelog/v4.0.3/summary.md: -------------------------------------------------------------------------------- 1 | *Mar 11, 2024* 2 | 3 | This is a consensus breaking patch release to the v4 Argon line. 4 | -------------------------------------------------------------------------------- /.changelog/v4.1.2/summary.md: -------------------------------------------------------------------------------- 1 | *May 2, 2024* 2 | 3 | This is a consensus breaking patch release to the v4.1 Fusion line. 4 | -------------------------------------------------------------------------------- /.changelog/v10.1.2/summary.md: -------------------------------------------------------------------------------- 1 | *Oct 14, 2025* 2 | 3 | This is a consensus breaking patch to the v10.1 Ember release line. 4 | -------------------------------------------------------------------------------- /.changelog/v4.0.2/summary.md: -------------------------------------------------------------------------------- 1 | *Nov 21, 2023* 2 | 3 | This is a non-consensus breaking patch release to the v4 Argon line. 4 | -------------------------------------------------------------------------------- /.changelog/v4.1.1/summary.md: -------------------------------------------------------------------------------- 1 | *Apr 16, 2024* 2 | 3 | This is a consensus breaking patch release to the v4.1 Fusion line. 4 | -------------------------------------------------------------------------------- /.changelog/v4.1.3/summary.md: -------------------------------------------------------------------------------- 1 | *May 10, 2024* 2 | 3 | This is a consensus breaking patch release to the v4.1 Fusion line. 4 | -------------------------------------------------------------------------------- /.changelog/v8.0.3/summary.md: -------------------------------------------------------------------------------- 1 | *Nov 20, 2024* 2 | 3 | This is a non-consensus breaking patch to the v8 Helium release line. 4 | -------------------------------------------------------------------------------- /.changelog/v8.0.4/summary.md: -------------------------------------------------------------------------------- 1 | *Dec 16, 2024* 2 | 3 | This is a non-consensus breaking patch to the v8 Helium release line. 4 | -------------------------------------------------------------------------------- /.changelog/v8.0.5/summary.md: -------------------------------------------------------------------------------- 1 | *Feb 3, 2025* 2 | 3 | This is a non-consensus breaking patch to the v8 Helium release line. 4 | -------------------------------------------------------------------------------- /.changelog/v9.0.1/summary.md: -------------------------------------------------------------------------------- 1 | *Mar 4, 2025* 2 | 3 | This is a non-consensus breaking patch to the v9 Argentum release line. 4 | -------------------------------------------------------------------------------- /.changelog/v9.0.2/summary.md: -------------------------------------------------------------------------------- 1 | *Mar 12, 2025* 2 | 3 | This is a non-consensus breaking patch to the v9 Argentum release line. 4 | -------------------------------------------------------------------------------- /.changelog/v9.0.3/summary.md: -------------------------------------------------------------------------------- 1 | *Mar 25, 2025* 2 | 3 | This is a consensus breaking patch to the v9 Argentum release line. 4 | -------------------------------------------------------------------------------- /.changelog/v9.0.4/summary.md: -------------------------------------------------------------------------------- 1 | *May 8, 2025* 2 | 3 | This is a non-consensus breaking patch to the v9 Argentum release line. 4 | -------------------------------------------------------------------------------- /.changelog/v4.0.0/bug-fixes/252-simulation-tests.md: -------------------------------------------------------------------------------- 1 | - Fix simulation tests. ([#252](https://github.com/noble-assets/noble/pull/252)) 2 | -------------------------------------------------------------------------------- /buf.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/buf.json 2 | version: v2 3 | modules: 4 | - path: api/proto 5 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/dependencies/530-bump-go.md: -------------------------------------------------------------------------------- 1 | - Bump supported Golang version to `v1.24` ([#530](https://github.com/noble-assets/noble/pull/530)) 2 | -------------------------------------------------------------------------------- /.changelog/v5.0.0/improvements/271-module-path.md: -------------------------------------------------------------------------------- 1 | - Update module path for v5 release line. ([#271](https://github.com/noble-assets/noble/pull/271)) 2 | -------------------------------------------------------------------------------- /.changelog/v6.0.0/improvements/389-module-path.md: -------------------------------------------------------------------------------- 1 | - Update module path for v6 release line. ([#389](https://github.com/noble-assets/noble/pull/389)) 2 | -------------------------------------------------------------------------------- /.changelog/v7.0.0/improvements/399-module-path.md: -------------------------------------------------------------------------------- 1 | - Update module path for v7 release line. ([#399](https://github.com/noble-assets/noble/pull/399)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/improvements/443-module-path.md: -------------------------------------------------------------------------------- 1 | - Update module path for v9 release line. ([#443](https://github.com/noble-assets/noble/pull/443)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/improvements/516-module-path.md: -------------------------------------------------------------------------------- 1 | - Update module path for v10 release line. ([#516](https://github.com/noble-assets/noble/pull/516)) 2 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/improvements/564-module-path.md: -------------------------------------------------------------------------------- 1 | - Update module path for v11 release line. ([#564](https://github.com/noble-assets/noble/pull/564)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.0/bug-fixes/253-ledger-macos-sonoma.md: -------------------------------------------------------------------------------- 1 | - Fix Ledger support for macOS Sonoma. ([#253](https://github.com/noble-assets/noble/pull/253)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/features/470-enable-swagger.md: -------------------------------------------------------------------------------- 1 | - Enable Swagger documentation in API endpoint. ([#470](https://github.com/noble-assets/noble/pull/470)) 2 | -------------------------------------------------------------------------------- /.changelog/v3.1.0/features/235-ibc-authority.md: -------------------------------------------------------------------------------- 1 | - Include support for IBC inside the ParamAuthority. ([#235](https://github.com/noble-assets/noble/pull/235)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.2/improvements/277-tariff-query.md: -------------------------------------------------------------------------------- 1 | - Implement a parameter query for the `x/tariff` module. ([#277](https://github.com/noble-assets/noble/pull/277)) 2 | -------------------------------------------------------------------------------- /.changelog/v8.0.3/dependencies/431-bump-halo.md: -------------------------------------------------------------------------------- 1 | - Update `x/halo` to latest non-consensus breaking patch. ([#431](https://github.com/noble-assets/noble/pull/431)) 2 | -------------------------------------------------------------------------------- /.changelog/v8.0.4/dependencies/440-bump-authority.md: -------------------------------------------------------------------------------- 1 | - Update `x/authority` to include helper CLI commands. ([#440](https://github.com/noble-assets/noble/pull/440)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/bug-fixes/515-empty-injections.md: -------------------------------------------------------------------------------- 1 | - Ensure transaction injections from Jester are non-empty. ([#515](https://github.com/noble-assets/noble/pull/515)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.3/improvements/000-migrate-ftf.md: -------------------------------------------------------------------------------- 1 | - Switch to [migrated](https://github.com/circlefin/noble-fiattokenfactory) version of `x/fiattokenfactory` module. 2 | -------------------------------------------------------------------------------- /.changelog/v11.0.1/summary.md: -------------------------------------------------------------------------------- 1 | *Oct 21, 2025* 2 | 3 | This is a non-consensus breaking patch to the v11 Flux release line. 4 | 5 | No core code changes are made in this patch. -------------------------------------------------------------------------------- /.changelog/v4.1.1/bug-fixes/353-abci.md: -------------------------------------------------------------------------------- 1 | - Remove custom ABCI logic inside `DeliverTx` that causes consensus failures. ([#353](https://github.com/noble-assets/noble/pull/353)) 2 | -------------------------------------------------------------------------------- /.changelog/v5.0.0/features/380-fast-blocks.md: -------------------------------------------------------------------------------- 1 | - Update the default `commit_timeout` to `500ms` to improve block time. ([#380](https://github.com/noble-assets/noble/pull/380)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/features/528-permission-hyperlane.md: -------------------------------------------------------------------------------- 1 | - Introduce a new ante handler that permissions Hyperlane actions. ([#528](https://github.com/noble-assets/noble/pull/528)) 2 | -------------------------------------------------------------------------------- /.changelog/v11.0.1/improvements/605-static-release-builds.md: -------------------------------------------------------------------------------- 1 | - Ensure binaries included in releases are statically linked. ([#605](https://github.com/noble-assets/noble/pull/605)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.1/bug-fixes/274-distribution-hooks.md: -------------------------------------------------------------------------------- 1 | - Unregister `x/distribution` hooks to address consensus failure. ([#274](https://github.com/noble-assets/noble/pull/274)) 2 | -------------------------------------------------------------------------------- /.changelog/v7.0.0/bug-fixes/405-bump-halo.md: -------------------------------------------------------------------------------- 1 | - Update `x/halo` to correctly check recipient role when trading to fiat. ([#405](https://github.com/noble-assets/noble/pull/405)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/dependencies/455-bump-ftf.md: -------------------------------------------------------------------------------- 1 | - Bump FiatTokenFactory to remove the limit check when decoding addresses. ([#455](https://github.com/noble-assets/noble/pull/455)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.1.1/summary.md: -------------------------------------------------------------------------------- 1 | *Aug 25, 2025* 2 | 3 | This is a non-consensus breaking patch to the v10.1 Ember release line. 4 | 5 | This upgrade is only relevant for validators. 6 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/improvements/557-remove-heighliner.md: -------------------------------------------------------------------------------- 1 | - Remove Heighliner with a Dockerfile in E2E and build environments. ([#557](https://github.com/noble-assets/noble/pull/557)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.0/dependencies/250-bump-ibc.md: -------------------------------------------------------------------------------- 1 | - Bump IBC to [`v4.5.1`](https://github.com/cosmos/ibc-go/releases/tag/v4.5.1) ([#250](https://github.com/noble-assets/noble/pull/250)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.0/improvements/241-ftf-interface-changes.md: -------------------------------------------------------------------------------- 1 | - Add `x/fiattokenfactory` interface changes required for CCTP. ([#241](https://github.com/noble-assets/noble/pull/241)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.3/dependencies/001-pfm.md: -------------------------------------------------------------------------------- 1 | - Bump PFM to [`455757b`](https://github.com/cosmos/ibc-apps/commit/455757bb5771c29cf2f83b59e37f6513e07c92be) to resolve Mandrake disclosure. 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/features/487-in-place-fork.md: -------------------------------------------------------------------------------- 1 | - Enable functionality for in-place forking a synced testnet or mainnet node. ([#487](https://github.com/noble-assets/noble/pull/487)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/dependencies/464-autocli-maps.md: -------------------------------------------------------------------------------- 1 | - Bump `cosmossdk.io/client/v2` to support returning maps inside AutoCLI queries. ([#464](https://github.com/noble-assets/noble/pull/464)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.3/dependencies/522-bump-swap.md: -------------------------------------------------------------------------------- 1 | - Bump Swap to [`v1.0.1`](https://github.com/noble-assets/swap/releases/tag/v1.0.1) ([#522](https://github.com/noble-assets/noble/pull/522)) 2 | -------------------------------------------------------------------------------- /.changelog/epilogue.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | ## Previous Changes 4 | 5 | This changelog has yet to be fully initialized. For previous versions please refer to the release notes for a summary of changes. 6 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/improvements/597-shido-client.md: -------------------------------------------------------------------------------- 1 | - Recover expired Shido IBC light client, `07-tendermint-106` → `07-tendermint-186` ([#597](https://github.com/noble-assets/noble/pull/597)) 2 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/improvements/603-router-client.md: -------------------------------------------------------------------------------- 1 | - Recover expired Router IBC light client, `07-tendermint-136` → `07-tendermint-192` ([#603](https://github.com/noble-assets/noble/pull/603)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/features/449-integrate-swap.md: -------------------------------------------------------------------------------- 1 | - Integrate our custom Swap module, that enables the exchange of tokens issued on Noble. ([#449](https://github.com/noble-assets/noble/pull/449)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.1/summary.md: -------------------------------------------------------------------------------- 1 | *Jul 7, 2025* 2 | 3 | This is a patch to the v10 Stratum release line. 4 | 5 | If the Dollar module has no yield recipients enabled, this is non-consensus breaking. 6 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/dependencies/495-bump-sdk.md: -------------------------------------------------------------------------------- 1 | - Bump Cosmos SDK to [`v0.50.12`](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.50.12) ([#495](https://github.com/noble-assets/noble/pull/495)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/features/444-integrate-wormhole.md: -------------------------------------------------------------------------------- 1 | - Integrate our custom Wormhole module, that enables Wormhole messaging on Noble via IBC. ([#444](https://github.com/noble-assets/noble/pull/444)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/features/519-integrate-hyperlane.md: -------------------------------------------------------------------------------- 1 | - Integrate the Hyperlane Core module, that enables messaging via the Hyperlane protocol. ([#519](https://github.com/noble-assets/noble/pull/519)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/features/527-integrate-warp.md: -------------------------------------------------------------------------------- 1 | - Integrate the Hyperlane Warp module, that enables token transfers via the Hyperlane protocol. ([#527](https://github.com/noble-assets/noble/pull/527)) 2 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/features/576-integrate-orbiter.md: -------------------------------------------------------------------------------- 1 | - Integrate Orbiter module, that introduces a new system for cross-chain interoperability. ([#576](https://github.com/noble-assets/noble/pull/576)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/features/448-integrate-dollar.md: -------------------------------------------------------------------------------- 1 | - Integrate our custom Dollar module, that enables the issuance of Noble's stablecoin $USDN. ([#448](https://github.com/noble-assets/noble/pull/448)) 2 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/features/587-hyperlane-collateral-token.md: -------------------------------------------------------------------------------- 1 | - Enable permissionless Hyperlane collateral token creation, for all assets except $USDN. ([#587](https://github.com/noble-assets/noble/pull/587)) 2 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/improvements/571-distribution-funds.md: -------------------------------------------------------------------------------- 1 | - Claim `x/distribution` module funds via upgrade handler that were stuck after its removal. ([#571](https://github.com/noble-assets/noble/pull/571)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.0/features/215-rosetta-support.md: -------------------------------------------------------------------------------- 1 | - Include support for Coinbase's [Rosetta API](https://docs.cloud.coinbase.com/rosetta/docs/welcome). ([#215](https://github.com/noble-assets/noble/pull/215)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.0/dependencies/000-sdk.md: -------------------------------------------------------------------------------- 1 | - Switch to Noble's Cosmos SDK fork ([`v0.45.16-noble`](https://github.com/noble-assets/cosmos-sdk/releases/tag/v0.45.16-noble)), allowing `DeliverTx` to be extended. 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/features/541-integrate-ratelimit.md: -------------------------------------------------------------------------------- 1 | - Integrate the IBC Rate Limit module, that enables more granular control over IBC token transfers. ([#541](https://github.com/noble-assets/noble/pull/541)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.3/dependencies/000-ftf.md: -------------------------------------------------------------------------------- 1 | - Bump FiatTokenFactory to [`14edf83`](https://github.com/circlefin/noble-fiattokenfactory/commit/14edf83ee1c96055e2c17ea56ca9dd303d3c14f6) to enable `x/authz` support. 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.2/improvements/357-migrate-forwarding.md: -------------------------------------------------------------------------------- 1 | - Switch to [migrated](https://github.com/noble-assets/forwarding) version of `x/forwarding` module. ([#357](https://github.com/noble-assets/noble/pull/357)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/features/463-integrate-jester.md: -------------------------------------------------------------------------------- 1 | - Integrate our custom Jester sidecar, that enables the automatic relaying of $USDN transfers to Noble. ([#463](https://github.com/noble-assets/noble/pull/463)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/features/526-bump-dollar.md: -------------------------------------------------------------------------------- 1 | - Upgrade the Dollar module to enable $USDN yield distribution across specific IBC channels and Hyperlane routes. ([#526](https://github.com/noble-assets/noble/pull/526)) 2 | -------------------------------------------------------------------------------- /.changelog/v3.1.0/improvements/234-module-path.md: -------------------------------------------------------------------------------- 1 | - Align module path with Go's [naming convention](https://go.dev/doc/modules/version-numbers#major-version). ([#234](https://github.com/noble-assets/noble/pull/234)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.2/dependencies/357-bump-forwarding.md: -------------------------------------------------------------------------------- 1 | - Bump `x/forwarding` module to [`v1.1.0`](https://github.com/noble-assets/forwarding/releases/tag/v1.1.0) ([#357](https://github.com/noble-assets/noble/pull/357)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.2/dependencies/359-bump-cctp.md: -------------------------------------------------------------------------------- 1 | - Bump CCTP to [`69ee090`](https://github.com/circlefin/noble-cctp/commit/69ee090808c05987c504b383939e71ad491594e7) ([#359](https://github.com/noble-assets/noble/pull/359)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.3/dependencies/363-bump-cctp.md: -------------------------------------------------------------------------------- 1 | - Bump CCTP to [`253cf7e`](https://github.com/circlefin/noble-cctp/commit/253cf7eb943669e283b4dcb25f83c7096080e67a) ([#363](https://github.com/noble-assets/noble/pull/363)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/bug-fixes/432-fix-ica.md: -------------------------------------------------------------------------------- 1 | - Update the capabilities of previously created ICA channels from the ICA Controller module back to the ICA Host module. ([#432](https://github.com/noble-assets/noble/pull/432)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.4/dependencies/549-bump-swap.md: -------------------------------------------------------------------------------- 1 | - Bump Swap to [`v1.0.2`](https://github.com/noble-assets/swap/releases/tag/v1.0.2) to improve `SimulateSwap` query. ([#549](https://github.com/noble-assets/noble/pull/549)) 2 | -------------------------------------------------------------------------------- /.changelog/v8.0.1/summary.md: -------------------------------------------------------------------------------- 1 | *Nov 13, 2024* 2 | 3 | This is a consensus breaking patch to the v8 Helium release line. 4 | 5 | It addresses the following consensus failure when upgrading Noble's mainnet to the `v8.0.0` release. 6 | -------------------------------------------------------------------------------- /.changelog/v8.0.2/summary.md: -------------------------------------------------------------------------------- 1 | *Nov 13, 2024* 2 | 3 | This is a consensus breaking patch to the v8 Helium release line. 4 | 5 | It addresses the following consensus failure when upgrading Noble's mainnet to the `v8.0.1` release. 6 | -------------------------------------------------------------------------------- /.changelog/v9.0.4/dependencies/549-bump-dollar.md: -------------------------------------------------------------------------------- 1 | - Bump Dollar to [`v1.0.2`](https://github.com/noble-assets/dollar/releases/tag/v1.0.2) to improve `PendingRewards` query. ([#549](https://github.com/noble-assets/noble/pull/549)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.1/dependencies/567-bump-dollar.md: -------------------------------------------------------------------------------- 1 | - Bump Dollar to [`v2.0.1`](https://github.com/noble-assets/dollar/releases/tag/v2.0.1) to gracefully handle transfer errors. ([#567](https://github.com/noble-assets/noble/pull/567)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.0/dependencies/000-ftf.md: -------------------------------------------------------------------------------- 1 | - Bump FiatTokenFactory to [`0a7385d`](https://github.com/circlefin/noble-fiattokenfactory/commit/0a7385d9a37744ced1e4d61eae10de2b117f482b) for various blocklist and paused check improvements. 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.1/dependencies/511-bump-dollar.md: -------------------------------------------------------------------------------- 1 | - Bump Dollar to [`v1.0.1`](https://github.com/noble-assets/dollar/releases/tag/v1.0.1) to correct recipient address in event. ([#511](https://github.com/noble-assets/noble/pull/511)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.0/dependencies/000-ibc.md: -------------------------------------------------------------------------------- 1 | - Bump IBC to [`v4.6.0`](https://github.com/cosmos/ibc-go/releases/tag/v4.6.0) to resolve [ASA-2024-007](https://github.com/cosmos/ibc-go/security/advisories/GHSA-j496-crgh-34mx) security advisory. 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.0/improvements/346-migrate-blockibc.md: -------------------------------------------------------------------------------- 1 | - Switch to migrated `x/blockibc` under [`circlefin/noble-fiattokenfactory`](https://github.com/circlefin/noble-fiattokenfactory). ([#346](https://github.com/noble-assets/noble/pull/346)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.2/dependencies/359-bump-ftf.md: -------------------------------------------------------------------------------- 1 | - Bump FiatTokenFactory to [`738932c`](https://github.com/circlefin/noble-fiattokenfactory/commit/738932cb316d06f587c49dfb11a50515cce657d9) ([#359](https://github.com/noble-assets/noble/pull/359)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/dependencies/480-bump-authority.md: -------------------------------------------------------------------------------- 1 | - Bump Authority to [`v1.0.2`](https://github.com/noble-assets/authority/releases/tag/v1.0.2) to include a new helper CLI command. ([#480](https://github.com/noble-assets/noble/pull/480)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.1/dependencies/509-bump-authority.md: -------------------------------------------------------------------------------- 1 | - Bump Authority to [`v1.0.3`](https://github.com/noble-assets/authority/releases/tag/v1.0.3) to correctly implement codec interface. ([#509](https://github.com/noble-assets/noble/pull/509)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.1.1/dependencies/582-bump-globalfee.md: -------------------------------------------------------------------------------- 1 | - Bump GlobalFee to [`v1.0.1`](https://github.com/noble-assets/globalfee/releases/tag/v1.0.1) to harden mempool checks of bypass messages. ([#582](https://github.com/noble-assets/noble/pull/582)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.1.2/dependencies/600-bump-forwarding.md: -------------------------------------------------------------------------------- 1 | - Bump Forwarding to [`v2.0.3`](https://github.com/noble-assets/forwarding/releases/tag/v2.0.3) to remove `x/bank` `GetAllBalances` usage. ([#600](https://github.com/noble-assets/noble/pull/600)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.1.0/dependencies/570-bump-dollar.md: -------------------------------------------------------------------------------- 1 | - Bump Dollar to [`v2.1.0`](https://github.com/noble-assets/dollar/releases/tag/v2.1.0) to introduce logic that handles the end of Vaults Season One. ([#570](https://github.com/noble-assets/noble/pull/570)) 2 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/dependencies/573-bump-dollar.md: -------------------------------------------------------------------------------- 1 | - Bump Dollar to [`v2.2.0`](https://github.com/noble-assets/dollar/releases/tag/v2.2.0) to migrate Points Season Two configuration values to state. ([#573](https://github.com/noble-assets/noble/pull/573)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.0/improvements/249-283-module-path.md: -------------------------------------------------------------------------------- 1 | - Align module path with Go's [naming convention](https://go.dev/doc/modules/version-numbers#major-version). ([#249](https://github.com/noble-assets/noble/pull/249), [#283](https://github.com/noble-assets/noble/pull/283)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/dependencies/481-bump-forwarding.md: -------------------------------------------------------------------------------- 1 | - Bump Forwarding to [`v2.0.1`](https://github.com/noble-assets/forwarding/releases/tag/v2.0.1) to check recipient length and harden validation when registering accounts. ([#481](https://github.com/noble-assets/noble/pull/481)) 2 | -------------------------------------------------------------------------------- /buf.gen.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json.schemastore.org/buf.gen.json 2 | version: v1 3 | plugins: 4 | - name: openapiv2 5 | out: ./api/tmp-swagger-gen 6 | opt: logtostderr=true,fqn_for_openapi_name=true,simple_operation_ids=true,json_names_for_fields=false 7 | -------------------------------------------------------------------------------- /.changelog/v11.0.0/summary.md: -------------------------------------------------------------------------------- 1 | *Oct 21, 2025* 2 | 3 | This is the Flux major release of Noble. It introduces the [Orbiter](https://github.com/noble-assets/orbiter) module, that is a new system for cross-chain interoperability. 4 | 5 | This and other notable changes are documented below. 6 | -------------------------------------------------------------------------------- /.changelog/v5.0.0/dependencies/385-cosmos-sdk.md: -------------------------------------------------------------------------------- 1 | - Switch to Noble's Cosmos SDK fork ([`v0.45.16-send-restrictions`](https://github.com/noble-assets/cosmos-sdk/releases/tag/v0.45.16-send-restrictions)) that supports send restrictions. ([#385](https://github.com/noble-assets/noble/pull/385)) 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/dependencies/506-bump-ibc.md: -------------------------------------------------------------------------------- 1 | - Bump IBC to [`v8.6.1`](https://github.com/cosmos/ibc-go/releases/v8.6.1) to resolve [ASA-2025-004](https://github.com/cosmos/ibc-go/security/advisories/GHSA-jg6f-48ff-5xrw) security advisory. ([#506](https://github.com/noble-assets/noble/pull/506)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.0.0/dependencies/250-bump-pfm.md: -------------------------------------------------------------------------------- 1 | - Bump Packet Forward Middleware to [`v4.1.1`](https://github.com/cosmos/ibc-apps/releases/tag/middleware%2Fpacket-forward-middleware%2Fv4.1.1) ([#250](https://github.com/noble-assets/noble/pull/250), [#258](https://github.com/noble-assets/noble/pull/258)) 2 | -------------------------------------------------------------------------------- /.changelog/v4.1.0/dependencies/000-pfm.md: -------------------------------------------------------------------------------- 1 | - Bump PFM from Mandrake patch commit ([`455757b`](https://github.com/cosmos/ibc-apps/commit/455757bb5771c29cf2f83b59e37f6513e07c92be)) to release tag ([`v4.1.2`](https://github.com/cosmos/ibc-apps/releases/tag/middleware%2Fpacket-forward-middleware%2Fv4.1.2)). 2 | -------------------------------------------------------------------------------- /.changelog/v9.0.2/dependencies/513-bump-ibc.md: -------------------------------------------------------------------------------- 1 | - Bump IBC to [`v8.7.0`](https://github.com/cosmos/ibc-go/releases/tag/v8.7.0) to resolve [ISA-2025-001](https://github.com/cosmos/ibc-go/security/advisories/GHSA-4wf3-5qj9-368v) security advisory. ([#513](https://github.com/noble-assets/noble/pull/513)) 2 | -------------------------------------------------------------------------------- /.changelog/v11.1.0/dependencies/609-611-bump-orbiter.md: -------------------------------------------------------------------------------- 1 | - Bump Orbiter to [`v2.0.0`](https://github.com/noble-assets/orbiter/releases/tag/v2.0.0) to support a fixed amount fee and improve stats. ([#609](https://github.com/noble-assets/noble/pull/609), [#611](https://github.com/noble-assets/noble/pull/611)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.1.2/dependencies/600-bump-comet.md: -------------------------------------------------------------------------------- 1 | - Bump CometBFT to [`v0.38.19`](https://github.com/cometbft/cometbft/releases/tag/v0.38.19) to resolve [ASA-2025-003](https://github.com/cometbft/cometbft/security/advisories/GHSA-hrhf-2vcr-ghch) security advisory. ([#600](https://github.com/noble-assets/noble/pull/600)) 2 | -------------------------------------------------------------------------------- /.changelog/v8.0.4/dependencies/441-bump-sdk.md: -------------------------------------------------------------------------------- 1 | - Bump Cosmos SDK to [`v0.50.11`](https://github.com/cosmos/cosmos-sdk/releases/v0.50.11) to resolve [ABS-0043/ABS-0044](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-8wcc-m6j2-qxvm) security advisory. ([#441](https://github.com/noble-assets/noble/pull/441)) 2 | -------------------------------------------------------------------------------- /.changelog/v8.0.3/dependencies/433-bump-math.md: -------------------------------------------------------------------------------- 1 | - Bump `cosmossdk.io/math` to [`v1.4.0`](https://github.com/cosmos/cosmos-sdk/releases/tag/math%2Fv1.4.0) to resolve [ASA-2024-010](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-7225-m954-23v7) security advisory. ([#433](https://github.com/noble-assets/noble/pull/433)) 2 | -------------------------------------------------------------------------------- /.changelog/v3.1.0/summary.md: -------------------------------------------------------------------------------- 1 | *Sep 15, 2023* 2 | 3 | This is a minor release to the v3 Radon line. 4 | 5 | In response to multiple IBC channels expiring on Noble's mainnet network, it was decided to expand the functionality of Noble's Maintenance Multisig to include IBC upgrade functionality (allowing expired clients to be changed). 6 | -------------------------------------------------------------------------------- /.changelog/v8.0.2/bug-fixes/429-forwarding-initialization.md: -------------------------------------------------------------------------------- 1 | - Due to IBC-Go v8 not supporting App Wiring, the Noble Core Team has to manually initialize all IBC modules and keepers. The Forwarding module receives multiple IBC keepers, which have to be manually set once wiring is complete. ([#429](https://github.com/noble-assets/noble/pull/429)) 2 | -------------------------------------------------------------------------------- /.changelog/v10.0.0/summary.md: -------------------------------------------------------------------------------- 1 | *Jun 6, 2025* 2 | 3 | This is the Stratum major release of Noble. It introduces [composable yield](https://www.noble.xyz/blog/composable-yield-a-new-paradigm-for-stablecoins) for the Noble Dollar (USDN), along with integration of the [Hyperlane](https://hyperlane.xyz/) protocol. 4 | 5 | This and other notable changes are documented below. 6 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/summary.md: -------------------------------------------------------------------------------- 1 | *Feb 28, 2025* 2 | 3 | This is the Argentum major release of Noble. It introduces various new modules 4 | that enable the issuance and use-cases of the Noble Dollar (USDN), Noble's 5 | yield bearing stablecoin. USDN is fully collateralized by U.S. Treasury bills 6 | via the M^0 protocol. 7 | 8 | This and other notable changes are documented below. 9 | -------------------------------------------------------------------------------- /.changelog/v8.0.5/dependencies/466-bump-comet.md: -------------------------------------------------------------------------------- 1 | - Bump CometBFT to [`v0.38.17`](https://github.com/cometbft/cometbft/releases/v0.38.17) to resolve [ASA-2025-001](https://github.com/cometbft/cometbft/security/advisories/GHSA-22qq-3xwm-r5x4) and [ASA-2025-002](https://github.com/cometbft/cometbft/security/advisories/GHSA-r3r4-g7hq-pq4f) security advisories. ([#466](https://github.com/noble-assets/noble/pull/466)) 2 | -------------------------------------------------------------------------------- /.changelog/v5.0.0/summary.md: -------------------------------------------------------------------------------- 1 | *Jul 5, 2024* 2 | 3 | This is the Krypton major release of Noble. It introduces a new `x/aura` module 4 | that enables the native issuance of [Ondo's US Dollar Yield (**USDY**)][usdy] 5 | asset. USDY is a tokenized note secured by short-term US Treasuries and bank 6 | demand deposits. 7 | 8 | Other notable changes are documented below. 9 | 10 | [usdy]: https://ondo.finance/usdy 11 | -------------------------------------------------------------------------------- /.changelog/v9.0.0/dependencies/488-bump-pfm.md: -------------------------------------------------------------------------------- 1 | - Bump Packet Forward Middleware to [`v8.2.0`](https://github.com/cosmos/ibc-apps/releases/tag/middleware%2Fpacket-forward-middleware%2Fv8.2.0) to resolve [GHSA-6fgm-x6ff-w78f](https://github.com/cosmos/ibc-apps/security/advisories/GHSA-6fgm-x6ff-w78f) security advisory. ([#488](https://github.com/noble-assets/noble/pull/488), [#506](https://github.com/noble-assets/noble/pull/506)) 2 | -------------------------------------------------------------------------------- /.changelog/v8.0.1/bug-fixes/428-surplus-supply.md: -------------------------------------------------------------------------------- 1 | - In the v8 Helium upgrade handler, the Noble Core Team wanted to align a missconfiguration in the initial genesis file that resulted in 18 surplus $STAKE existing, bringing the total supply to 1,000,000,018. The migration plan involved burning the surplus 18 tokens via the Uupgrade module, however, the module account was never initialized and permissioned. ([#428](https://github.com/noble-assets/noble/pull/428)) 2 | -------------------------------------------------------------------------------- /.changelog/v6.0.0/summary.md: -------------------------------------------------------------------------------- 1 | *Aug 27, 2024* 2 | 3 | This is the Xenon major release of Noble. It introduces a new `x/halo` module 4 | that enables the native issuance of [Hashnote's US Yield Coin (**USYC**)][usyc] 5 | asset. USYC is an on-chain representation of Hashnote's Short Duration Yield 6 | Fund, primarily investing in U.S. Treasury Bills and engaging in reverse repo 7 | activities. 8 | 9 | Other notable changes are documented below. 10 | 11 | [usyc]: https://usyc.hashnote.com 12 | -------------------------------------------------------------------------------- /.changelog/v4.0.0/summary.md: -------------------------------------------------------------------------------- 1 | *Nov 6, 2023* 2 | 3 | This is the long awaited Argon major release of Noble. It introduces a new [`x/cctp`](https://github.com/circlefin/noble-cctp) module that implements Circle's [Cross Chain Transfer Protocol (CCTP)](https://www.circle.com/en/cross-chain-transfer-protocol), allowing native $USDC transfers between supported EVM networks and Noble (with many more networks to come). 4 | 5 | Along with the integration of the CCTP module, the following changes were made. 6 | -------------------------------------------------------------------------------- /.changelog/v7.0.0/summary.md: -------------------------------------------------------------------------------- 1 | *Sep 13, 2024* 2 | 3 | This is the Numus major release of Noble. It introduces a new `x/florin` module 4 | that enables the native issuance of [Monerium's EUR emoney (**EURe**)][eure] 5 | asset. EURe is issued by Monerium EMI, a regulated entity, licensed in the EEA. 6 | E-money is recognized as a digital alternative to cash, 1:1 backed in 7 | high-quality liquid assets and is unconditionally redeemable on demand. 8 | 9 | Other notable changes are documented below. 10 | 11 | [eure]: https://monerium.com 12 | -------------------------------------------------------------------------------- /.github/workflows/check-license.yaml: -------------------------------------------------------------------------------- 1 | name: Check License 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | check-license: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout Repository 10 | uses: actions/checkout@v4 11 | - name: Install Go 12 | uses: actions/setup-go@v5 13 | with: 14 | go-version-file: 'go.mod' 15 | - name: Install go-license 16 | run: go install github.com/palantir/go-license@latest 17 | - name: Check License 18 | run: make check-license 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS nobled-builder 2 | 3 | RUN apk add --no-cache \ 4 | build-base \ 5 | git \ 6 | linux-headers 7 | 8 | WORKDIR /src 9 | 10 | COPY go.mod go.sum ./ 11 | 12 | RUN go mod download 13 | 14 | COPY . . 15 | 16 | # https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide 17 | ARG TARGETOS TARGETARCH 18 | 19 | RUN GOOS=$TARGETOS GOARCH=$TARGETARCH LDFLAGS='-extldflags "-static"' make build 20 | 21 | FROM alpine:3 22 | 23 | WORKDIR /root 24 | 25 | COPY --from=nobled-builder /src/build/nobled /usr/bin/nobled 26 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # CODEOWNERS: https://help.github.com/articles/about-codeowners/ 2 | 3 | # Everything goes through the following "global owners" by default. Unless a 4 | # later match takes precedence, they will be requested for review when someone 5 | # opens a PR. Note that the last matching pattern takes precedence, so global 6 | # owners are only requested if there isn't a more specific codeowner specified 7 | # below. For this reason, the global codeowners are often repeated in 8 | # package-level definitions. 9 | 10 | * @noble-assets/engineering 11 | 12 | # CODEOWNERS for the CODEOWNER file 13 | 14 | /.github/CODEOWNERS @johnletey 15 | -------------------------------------------------------------------------------- /.changelog/v8.0.0/summary.md: -------------------------------------------------------------------------------- 1 | *Nov 11, 2024* 2 | 3 | This is the Helium major release of Noble. It upgrades Noble itself and all of it's core modules to the latest stable release of the Cosmos SDK, `v0.50.x` a.k.a. Eden. Additional module changes have been documented below: 4 | 5 | #### FiatTokenFactory 6 | 7 | The BlockIBC logic was improved to support both Bech32 and Bech32m for IBC recipient addresses. 8 | 9 | #### Florin 10 | 11 | The module was updated to accept a user's public key when verifying signatures, instead of relying on on-chain data. 12 | 13 | #### Forwarding 14 | 15 | The module was updated to include a fallback address and a list of allowed denominations to forward. 16 | -------------------------------------------------------------------------------- /.github/license.yml: -------------------------------------------------------------------------------- 1 | header: | 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // Copyright 2025 NASD Inc. All Rights Reserved. 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 | -------------------------------------------------------------------------------- /.changelog/v4.1.0/summary.md: -------------------------------------------------------------------------------- 1 | *Apr 15, 2024* 2 | 3 | This is a minor release to the v4 Argon line, codenamed Fusion. 4 | 5 | The main part of this release is the introduction of the `x/forwarding` module. 6 | It allows users to create a new account type, where the receipt of funds into 7 | that account triggers an automatic IBC transfer over a specified channel to a 8 | recipient address. This allows for one-click transfers to any IBC-enabled chain, 9 | and can be used in tandem with, for example, the receipt of funds from a 10 | [Circle Mint][mint] account or via [CCTP][cctp-docs]. 11 | 12 | Other notable changes include are documented below. 13 | 14 | [cctp-docs]: https://www.circle.com/en/cross-chain-transfer-protocol 15 | [mint]: https://www.circle.com/en/circle-mint 16 | -------------------------------------------------------------------------------- /api/gen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Noble API Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | //go:build tools 18 | 19 | // This file uses the recommended method for tracking developer tools in a Go module. 20 | // 21 | // REF: https://go.dev/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 22 | package tools 23 | 24 | import ( 25 | _ "github.com/golangci/golangci-lint/cmd/golangci-lint" 26 | _ "mvdan.cc/gofumpt" 27 | ) 28 | -------------------------------------------------------------------------------- /orbiter.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package noble 18 | 19 | import orbiter "github.com/noble-assets/orbiter/v2" 20 | 21 | func (app *App) RegisterOrbiterControllers() { 22 | in := orbiter.ComponentsInputs{ 23 | BankKeeper: app.BankKeeper, 24 | CCTPKeeper: app.CCTPKeeper, 25 | Orbiters: app.OrbiterKeeper, 26 | WarpKeeper: app.WarpKeeper, 27 | } 28 | 29 | orbiter.InjectComponents(in) 30 | } 31 | -------------------------------------------------------------------------------- /jester/client.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package jester 18 | 19 | import ( 20 | "net/http" 21 | "strings" 22 | 23 | jester "jester.noble.xyz/api" 24 | ) 25 | 26 | func NewClient(address string) jester.QueryServiceClient { 27 | if !strings.Contains(address, "://") { 28 | address = "http://" + address 29 | } 30 | 31 | return jester.NewQueryServiceClient( 32 | http.DefaultClient, 33 | address, 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /upgrade/store.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package upgrade 18 | 19 | import ( 20 | storetypes "cosmossdk.io/store/types" 21 | upgradetypes "cosmossdk.io/x/upgrade/types" 22 | "github.com/cosmos/cosmos-sdk/baseapp" 23 | ) 24 | 25 | func CreateStoreLoader(upgradeHeight int64) baseapp.StoreLoader { 26 | storeUpgrades := storetypes.StoreUpgrades{} 27 | 28 | return upgradetypes.UpgradeStoreLoader(upgradeHeight, &storeUpgrades) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/nobled/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" 24 | "github.com/noble-assets/noble/v11" 25 | "github.com/noble-assets/noble/v11/cmd" 26 | ) 27 | 28 | func main() { 29 | rootCmd := cmd.NewRootCmd() 30 | if err := svrcmd.Execute(rootCmd, "", noble.DefaultNodeHome); err != nil { 31 | fmt.Fprintln(rootCmd.OutOrStderr(), err) 32 | os.Exit(1) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /e2e/readme.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | 4 | We utilize the [interchaintest](https://github.com/strangelove-ventures/interchaintest) testing suite. 5 | 6 | All tests are located in the [interchaintest folder](../interchaintest/). 7 | 8 | ## How to Run tests: 9 | 10 | ### Requirements: 11 | 12 | - Docker (running) 13 | - Golang 14 | 15 | 1. Create local docker image that contains the `noble` binary. 16 | If you make any code changes, you'll want to re-make the image before running tests. 17 | 18 | `make local-image` 19 | 20 | 2. Now we can run the tests. There are two ways to run the test. 21 | 22 | a. If you are using VS Code you can simply click the `run test` button above each test. 23 | For this to work, you may need to install the [Go extension](https://marketplace.visualstudio.com/items?itemName=golang.Go) to VS Code. 24 | 25 | ![vsCode_runTest](../docs/images/vscode_runtests.png) 26 | 27 | b. Or you can run it from the command line: 28 | 29 | ``` 30 | cd interchaintest 31 | go test -v -run 32 | 33 | # Example 34 | go test -timeout 10m -v -run TestCCTP_DepForBurnWithCallerOnEth 35 | ``` 36 | -------------------------------------------------------------------------------- /local_net/single-val.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./utils.sh 4 | 5 | BIN=../build/nobled 6 | 7 | HOME1=.duke 8 | 9 | for arg in "$@"; do 10 | case $arg in 11 | -r | --reset) 12 | rm -rf $HOME1 13 | shift 14 | ;; 15 | esac 16 | done 17 | 18 | if ! [ -f $HOME1/data/priv_validator_state.json ]; then 19 | $BIN init validator --chain-id "duke-1" --home $HOME1 &>/dev/null 20 | 21 | $BIN keys add validator --home $HOME1 --keyring-backend test &>/dev/null 22 | $BIN genesis add-genesis-account validator 1000000ustake --home $HOME1 --keyring-backend test 23 | AUTHORITY=$($BIN keys add authority --home $HOME1 --keyring-backend test --output json | jq .address) 24 | $BIN genesis add-genesis-account authority 4000000ustake --home $HOME1 --keyring-backend test 25 | $BIN genesis add-genesis-account noble1cyyzpxplxdzkeea7kwsydadg87357qnah9s9cv 1000000uusdc --home $HOME1 --keyring-backend test 26 | 27 | update_genesis $HOME1 $AUTHORITY 28 | 29 | $BIN genesis gentx validator 1000000ustake --chain-id "duke-1" --home $HOME1 --keyring-backend test &>/dev/null 30 | $BIN genesis collect-gentxs --home $HOME1 &>/dev/null 31 | fi 32 | 33 | $BIN start --home $HOME1 34 | -------------------------------------------------------------------------------- /e2e/upgrade_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/noble-assets/noble/e2e" 23 | ) 24 | 25 | func TestChainUpgrade(t *testing.T) { 26 | if testing.Short() { 27 | t.Skip("skipping test in short mode.") 28 | } 29 | 30 | genesisVersion := "v11.0.1" 31 | 32 | upgrades := []e2e.ChainUpgrade{ 33 | { 34 | Image: e2e.LocalImages[0], 35 | UpgradeName: "pulse", 36 | }, 37 | } 38 | 39 | e2e.TestChainUpgrade(t, genesisVersion, upgrades, false) 40 | } 41 | -------------------------------------------------------------------------------- /e2e/conformance_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/noble-assets/noble/e2e" 24 | "github.com/strangelove-ventures/interchaintest/v8/conformance" 25 | ) 26 | 27 | func TestConformance(t *testing.T) { 28 | t.Parallel() 29 | 30 | ctx := context.Background() 31 | 32 | var nw e2e.NobleWrapper 33 | nw, ibcSimd, rf, r, ibcPathName, rep, _, client, network := e2e.NobleSpinUpIBC(t, ctx, e2e.LocalImages, false) 34 | 35 | conformance.TestChainPair(t, ctx, client, network, nw.Chain, ibcSimd, rf, rep, r, ibcPathName) 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Create and Push Docker Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | 11 | jobs: 12 | build-and-push-image: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Login to GitHub Container Registry 23 | uses: docker/login-action@v3 24 | with: 25 | registry: ${{ env.REGISTRY }} 26 | username: ${{ github.actor }} 27 | password: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | - name: Set up QEMU 30 | uses: docker/setup-qemu-action@v3 31 | 32 | - name: Set up Buildx 33 | uses: docker/setup-buildx-action@v3 34 | 35 | - name: Normalize Tag 36 | id: normalize 37 | run: echo "tag=$(echo ${{ github.ref_name }} | sed 's/[\/\.]/-/g')" >> $GITHUB_OUTPUT 38 | 39 | - name: Build and Push Docker Image 40 | uses: docker/build-push-action@v6 41 | with: 42 | context: . 43 | push: true 44 | tags: ${{ env.REGISTRY }}/${{ github.repository }}:${{ steps.normalize.outputs.tag }} 45 | platforms: linux/amd64,linux/arm64 46 | -------------------------------------------------------------------------------- /e2e/module_check_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "testing" 23 | 24 | "github.com/noble-assets/noble/e2e" 25 | "github.com/stretchr/testify/require" 26 | ) 27 | 28 | func TestRestrictedModules(t *testing.T) { 29 | t.Parallel() 30 | 31 | ctx := context.Background() 32 | 33 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, false) 34 | noble := nw.Chain.GetNode() 35 | 36 | restrictedModules := []string{"circuit", "gov", "group"} 37 | 38 | for _, module := range restrictedModules { 39 | require.False(t, noble.HasCommand(ctx, "query", module), fmt.Sprintf("%s is a restricted module", module)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /upgrade/constants.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package upgrade 18 | 19 | // UpgradeName is the name of this specific software upgrade used on-chain. 20 | const UpgradeName = "pulse" 21 | 22 | // UpgradeASCII is the ASCII art shown to node operators upon successful upgrade. 23 | const UpgradeASCII = ` 24 | 25 | ██████╗ ██╗ ██╗██╗ ███████╗███████╗ 26 | ██╔══██╗██║ ██║██║ ██╔════╝██╔════╝ 27 | ██████╔╝██║ ██║██║ ███████╗█████╗ 28 | ██╔═══╝ ██║ ██║██║ ╚════██║██╔══╝ 29 | ██║ ╚██████╔╝███████╗███████║███████╗ 30 | ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ 31 | 32 | ` 33 | 34 | // TestnetChainID is the Chain ID of the Noble testnet. 35 | const TestnetChainID = "grand-1" 36 | 37 | // MainnetChainID is the Chain ID of the Noble mainnet. 38 | const MainnetChainID = "noble-1" 39 | -------------------------------------------------------------------------------- /api/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf ./api/proto 3 | rm -rf ./api/tmp-swagger-gen 4 | 5 | # IMPORTANT: These versions should match the go.mod! 6 | buf export buf.build/bcp-innovations/hyperlane-cosmos:v1.0.1 --output api/proto 7 | buf export buf.build/cosmos/cosmos-sdk:v0.50.0 --output api/proto 8 | buf export buf.build/cosmos/ibc:e69c8f372b127401762cb251bb7b60371b4cef29 --output api/proto 9 | buf export buf.build/noble-assets/aura:v2.0.0 --output api/proto 10 | buf export buf.build/noble-assets/authority:v1.0.3 --output api/proto 11 | buf export buf.build/noble-assets/cctp:4285c94ec19438ad1e05ba3e5106a5e7980cfffd --output api/proto 12 | buf export buf.build/noble-assets/dollar:v2.2.0 --output api/proto 13 | buf export buf.build/noble-assets/fiattokenfactory:5f9bd9dd2c5b5336b94bae4a47195bdf035f04af --output api/proto 14 | buf export buf.build/noble-assets/florin:v2.0.0 --output api/proto 15 | buf export buf.build/noble-assets/forwarding:v2.0.3 --output api/proto 16 | buf export buf.build/noble-assets/globalfee:v1.0.1 --output api/proto 17 | buf export buf.build/noble-assets/halo:v2.0.1 --output api/proto 18 | buf export buf.build/noble-assets/orbiter:v2.0.0 --output api/proto 19 | buf export buf.build/noble-assets/rate-limiting:v8.0.0 --output api/proto 20 | buf export buf.build/noble-assets/swap:v1.0.2 --output api/proto 21 | buf export buf.build/noble-assets/wormhole:v1.0.0 --output api/proto 22 | 23 | buf generate 24 | swagger-combine ./api/config.json -o ./api/gen/swagger.yaml -f yaml --includeDefinitions true 25 | -------------------------------------------------------------------------------- /api/embed.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package api 18 | 19 | import ( 20 | "embed" 21 | "net/http" 22 | 23 | "github.com/cosmos/cosmos-sdk/client" 24 | "github.com/gorilla/mux" 25 | ) 26 | 27 | //go:embed gen 28 | var SwaggerUI embed.FS 29 | 30 | // RegisterSwaggerAPI provides a common function which registers swagger route with API Server 31 | func RegisterSwaggerAPI(_ client.Context, rtr *mux.Router, swaggerEnabled bool) error { 32 | if !swaggerEnabled { 33 | return nil 34 | } 35 | 36 | index, err := SwaggerUI.ReadFile("gen/index.html") 37 | if err != nil { 38 | return err 39 | } 40 | rtr.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 41 | _, _ = w.Write(index) 42 | }) 43 | 44 | swagger, err := SwaggerUI.ReadFile("gen/swagger.yaml") 45 | if err != nil { 46 | return err 47 | } 48 | rtr.HandleFunc("/swagger.yaml", func(w http.ResponseWriter, r *http.Request) { 49 | _, _ = w.Write(swagger) 50 | }) 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /local_net/README.md: -------------------------------------------------------------------------------- 1 | # Local Net Quick Start 2 | 3 | Before running any of the scripts below, ensure you have built Noble using `make build`. 4 | 5 | - [Single Validator Network](#single-validator-network) 6 | - [Multi Validator Network](#multi-validator-network) 7 | - [In-Place Network Fork](#in-place-network-fork) 8 | 9 | ## Single Validator Network 10 | 11 | Start a single validator local Noble network. 12 | 13 | ```sh 14 | sh single-val.sh -r 15 | ``` 16 | 17 | ## Multi Validator Network 18 | 19 | Start a three validator local Noble network. 20 | 21 | Note: this requires [tmux](https://github.com/tmux/tmux/wiki). 22 | 23 | ```sh 24 | sh multi-val.sh -r 25 | 26 | # How to kill: 27 | # `ctr-c` kill 1 out of the three nodes 28 | killall nobled # kill remaining noble nodes 29 | # (`ctrl-b` then `d`) exit out of tmux session 30 | tmux kill-session -t 3v-network # kill tmux session 31 | ``` 32 | 33 | ## In-Place Network Fork 34 | 35 | Synchronize a mainnet (or testnet) node using state sync, then create an `in-place-testnet`. 36 | 37 | Note: your noble binary in the `build` folder must be compatible with the relevant network. 38 | 39 | ```sh 40 | # ARGS: 41 | # -r|--reset - delete chain home folder resetting network 42 | # -t|--testnet - sync testnet instead of mainnet 43 | # -u|--trigger-testnet-upgrade - trigger an upgrade handler to run on the first block of the forked network 44 | 45 | # mainnet example: 46 | sh in-place-fork.sh -r 47 | 48 | # testnet example: 49 | sh in-place-fork.sh -r -t 50 | 51 | # trigger upgrade example: 52 | sh in-place-fork.sh -r -t -u "v9.0.0-rc.0" 53 | ``` 54 | -------------------------------------------------------------------------------- /.github/workflows/draft-release.yaml: -------------------------------------------------------------------------------- 1 | name: Build Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | 11 | jobs: 12 | build-and-push-image: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Login to GitHub Container Registry 23 | uses: docker/login-action@v3 24 | with: 25 | registry: ${{ env.REGISTRY }} 26 | username: ${{ github.actor }} 27 | password: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | - name: Set up QEMU 30 | uses: docker/setup-qemu-action@v3 31 | 32 | - name: Set up Buildx 33 | uses: docker/setup-buildx-action@v3 34 | 35 | - name: Build and Push Docker Image 36 | uses: docker/build-push-action@v6 37 | with: 38 | context: . 39 | push: true 40 | tags: ${{ env.REGISTRY }}/${{ github.repository }}:${{ github.ref_name }} 41 | platforms: linux/amd64,linux/arm64 42 | 43 | draft-release: 44 | needs: build-and-push-image 45 | runs-on: ubuntu-latest 46 | steps: 47 | - name: Copy Binary 48 | run: | 49 | docker create -it --entrypoint sh --name amd --platform linux/amd64 ${{ env.REGISTRY }}/${{ github.repository }}:${{ github.ref_name }} 50 | docker create -it --entrypoint sh --name arm --platform linux/arm64 ${{ env.REGISTRY }}/${{ github.repository }}:${{ github.ref_name }} 51 | docker cp amd:/usr/bin/nobled ./nobled_linux-amd64 52 | docker cp arm:/usr/bin/nobled ./nobled_linux-arm64 53 | sha256sum ./nobled_linux-amd64 > ./checksum.txt 54 | sha256sum ./nobled_linux-arm64 >> ./checksum.txt 55 | 56 | - name: Draft Release 57 | uses: softprops/action-gh-release@v2 58 | with: 59 | draft: true 60 | files: | 61 | nobled_linux-amd64 62 | nobled_linux-arm64 63 | checksum.txt 64 | -------------------------------------------------------------------------------- /export.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package noble 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | 23 | cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" 24 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 25 | "github.com/cosmos/cosmos-sdk/x/staking" 26 | ) 27 | 28 | // ExportAppStateAndValidators exports the state of the application for a genesis file. 29 | func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs, modulesToExport []string) (servertypes.ExportedApp, error) { 30 | // as if they could withdraw from the start of the next block 31 | ctx := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()}) 32 | 33 | // We export at last height + 1, because that's the height at which 34 | // CometBFT will start InitChain. 35 | height := app.LastBlockHeight() + 1 36 | if forZeroHeight { 37 | panic("zero height genesis is unsupported") 38 | } 39 | 40 | genState, err := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) 41 | if err != nil { 42 | return servertypes.ExportedApp{}, fmt.Errorf("failed to export genesis state: %w", err) 43 | } 44 | 45 | appState, err := json.MarshalIndent(genState, "", " ") 46 | if err != nil { 47 | return servertypes.ExportedApp{}, err 48 | } 49 | 50 | validators, err := staking.WriteValidators(ctx, app.StakingKeeper) 51 | return servertypes.ExportedApp{ 52 | AppState: appState, 53 | Validators: validators, 54 | Height: height, 55 | ConsensusParams: app.BaseApp.GetConsensusParams(ctx), 56 | }, err 57 | } 58 | -------------------------------------------------------------------------------- /jester/config.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package jester 18 | 19 | import ( 20 | serverconfig "github.com/cosmos/cosmos-sdk/server/config" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | const ( 25 | defaultJesterAddress = "localhost:9091" 26 | ) 27 | 28 | // AppendJesterConfig appends the Jester configuration to app.toml 29 | func AppendJesterConfig(srvCfg *serverconfig.Config) (customAppTemplate string, NobleAppConfig interface{}) { 30 | type JesterConfig struct { 31 | GRPCAddress string `mapstructure:"grpc-address"` 32 | } 33 | 34 | type CustomAppConfig struct { 35 | serverconfig.Config 36 | 37 | JesterConfig JesterConfig `mapstructure:"jester"` 38 | } 39 | 40 | defaultJesterConfig := JesterConfig{ 41 | GRPCAddress: defaultJesterAddress, 42 | } 43 | 44 | NobleAppConfig = CustomAppConfig{Config: *srvCfg, JesterConfig: defaultJesterConfig} 45 | 46 | customAppTemplate = serverconfig.DefaultConfigTemplate + ` 47 | ############################################################################### 48 | ### Jester (sidecar) ### 49 | ############################################################################### 50 | 51 | [jester] 52 | 53 | # Jester's gRPC server address. 54 | # This should not conflict with the CometBFT gRPC server. 55 | grpc-address = "{{ .JesterConfig.GRPCAddress }}" 56 | ` 57 | return customAppTemplate, NobleAppConfig 58 | } 59 | 60 | // Flags 61 | 62 | const ( 63 | FlagGRPCAddress = "jester.grpc-address" 64 | ) 65 | 66 | func AddFlags(cmd *cobra.Command) { 67 | cmd.Flags().String(FlagGRPCAddress, defaultJesterAddress, "Jester's gRPC server address") 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/e2e-tests.yaml: -------------------------------------------------------------------------------- 1 | name: End to End Tests 2 | 3 | on: 4 | pull_request: 5 | 6 | env: 7 | TAR_PATH: nobled.tar 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up Buildx 21 | uses: docker/setup-buildx-action@v3 22 | 23 | - name: Build Docker Image 24 | uses: docker/build-push-action@v6 25 | with: 26 | context: . 27 | load: true 28 | tags: noble:local 29 | outputs: type=docker, dest=${{ env.TAR_PATH }} 30 | 31 | - name: Publish Tarball as Artifact 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: noble-docker-image 35 | path: ${{ env.TAR_PATH }} 36 | 37 | prepare: 38 | runs-on: ubuntu-latest 39 | outputs: 40 | matrix: ${{ steps.set-matrix.outputs.matrix }} 41 | steps: 42 | - name: Checkout Repository 43 | uses: actions/checkout@v4 44 | 45 | - name: Install Go 46 | uses: actions/setup-go@v5 47 | with: 48 | go-version-file: 'go.mod' 49 | 50 | - name: Generate Matrix 51 | id: set-matrix 52 | run: | 53 | # Run the command and convert its output to a JSON array 54 | TESTS=$(cd e2e && go test -list . | grep -v "^ok " | jq -R -s -c 'split("\n")[:-1]') 55 | echo "matrix=${TESTS}" >> $GITHUB_OUTPUT 56 | 57 | test: 58 | needs: 59 | - build 60 | - prepare 61 | runs-on: ubuntu-latest 62 | strategy: 63 | matrix: 64 | # names of `make` commands to run tests 65 | test: ${{fromJson(needs.prepare.outputs.matrix)}} 66 | fail-fast: false 67 | 68 | steps: 69 | - name: Checkout Repository 70 | uses: actions/checkout@v4 71 | 72 | - name: Install Go 73 | uses: actions/setup-go@v5 74 | with: 75 | go-version-file: 'go.mod' 76 | 77 | - name: Download Tarball Artifact 78 | uses: actions/download-artifact@v4 79 | with: 80 | name: noble-docker-image 81 | 82 | - name: Load Docker Image 83 | run: docker image load -i ${{ env.TAR_PATH }} 84 | 85 | - name: Run Tests 86 | run: cd e2e && go test -race -v -timeout 30m -run ^${{ matrix.test }}$ . 87 | -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Noble Banner 5 | 6 |

7 | Latest Release 8 | Block Height 9 |

10 | ✨ noble.xyz ✨ 11 |
12 | ✨ dollar.noble.xyz ✨ 13 |
14 | ✨ express.noble.xyz ✨ 15 |
16 |
17 | 18 | Noble is an [application-specific blockchain](https://docs.cosmos.network/main/learn/intro/why-app-specific) built on the Cosmos SDK, purpose-built for asset issuance with a focus on stablecoins and real-world assets (RWAs). As an IBC-enabled chain, Noble ensures seamless interoperability across the Cosmos ecosystem, enabling fast and secure transactions. 19 | 20 | In addition to supporting various RWAs, Noble offers its own native real-world asset, Noble Dollar (USDN) — a yield-bearing stablecoin that gives developers and end users control over the underlying yield. Additionally, Noble has implemented Circle's [Cross-Chain Transfer Protocol (CCTP)](https://www.circle.com/cross-chain-transfer-protocol) to facilitate native transfers of USDC across multiple blockchain networks. 21 | 22 | You can learn more about all the assets we offer [here](https://www.noble.xyz/#assets)! 23 | 24 | ## Documentation 25 | 26 | For all documentation outside of installation, please visit our [official documentation](https://docs.noble.xyz). 27 | 28 | ## Installation 29 | 30 | When installing from source, [Golang](https://go.dev) is required. 31 | 32 | ```sh 33 | git clone https://github.com/noble-assets/noble.git 34 | cd noble 35 | git checkout 36 | make install 37 | ``` 38 | 39 | Noble is also available via: 40 | 41 | - [Releases](https://github.com/noble-assets/noble/releases) 42 | - [Docker](https://github.com/noble-assets/noble/pkgs/container/noble) 43 | 44 | ## Local Net Quickstart 45 | 46 | Looking to spin up a standalone local Noble chain? Leverage our quickstart guide and scripts [here](../local_net)! 47 | 48 | ## Contributing 49 | 50 | We welcome contributions! If you find a bug or have feedback, open an issue. Pull requests for bug fixes are appreciated. 51 | For major changes, please open an issue first to discuss your proposal. 52 | 53 | Note: We do not accept contributions for grammar or spelling corrections. 54 | -------------------------------------------------------------------------------- /ante_permissioned_liquidity.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package noble 18 | 19 | import ( 20 | "fmt" 21 | 22 | sdk "github.com/cosmos/cosmos-sdk/types" 23 | "github.com/cosmos/cosmos-sdk/x/authz" 24 | swaptypes "swap.noble.xyz/types" 25 | stableswaptypes "swap.noble.xyz/types/stableswap" 26 | 27 | "github.com/noble-assets/noble/v11/upgrade" 28 | ) 29 | 30 | // PermissionedAccount is the account allowed to perform liquidity actions on Noble. 31 | const PermissionedAccount = "noble18vx4czzv4rgrfhm0pzhwu5janjdh4ssdkpu8vr" 32 | 33 | var _ sdk.AnteDecorator = &PermissionedLiquidityDecorator{} 34 | 35 | // PermissionedLiquidityDecorator is a custom ante handler that permissions all liquidity actions on Noble. 36 | type PermissionedLiquidityDecorator struct{} 37 | 38 | func NewPermissionedLiquidityDecorator() PermissionedLiquidityDecorator { 39 | return PermissionedLiquidityDecorator{} 40 | } 41 | 42 | func (d PermissionedLiquidityDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { 43 | if ctx.ChainID() == upgrade.MainnetChainID { 44 | for _, msg := range tx.GetMsgs() { 45 | err := d.CheckMessage(msg) 46 | if err != nil { 47 | return ctx, err 48 | } 49 | } 50 | } 51 | 52 | return next(ctx, tx, simulate) 53 | } 54 | 55 | func (d PermissionedLiquidityDecorator) CheckMessage(msg sdk.Msg) error { 56 | switch m := msg.(type) { 57 | case *stableswaptypes.MsgAddLiquidity: 58 | if m.Signer != PermissionedAccount { 59 | return fmt.Errorf("%s is currently a permissioned action", sdk.MsgTypeURL(msg)) 60 | } 61 | case *stableswaptypes.MsgRemoveLiquidity: 62 | if m.Signer != PermissionedAccount { 63 | return fmt.Errorf("%s is currently a permissioned action", sdk.MsgTypeURL(msg)) 64 | } 65 | case *swaptypes.MsgWithdrawRewards: 66 | if m.Signer != PermissionedAccount { 67 | return fmt.Errorf("%s is currently a permissioned action", sdk.MsgTypeURL(msg)) 68 | } 69 | case *authz.MsgExec: 70 | execMsgs, err := m.GetMessages() 71 | if err != nil { 72 | return err 73 | } 74 | 75 | for _, execMsg := range execMsgs { 76 | err = d.CheckMessage(execMsg) 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | } 82 | 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /local_net/in-place-fork.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BIN=../build/nobled 4 | 5 | cleanup() { 6 | echo "Stopping processes..." 7 | kill "$NOBLED_PID" "$TAIL_PID" "$NOBLED_PID2" 2>/dev/null 8 | exit 0 9 | } 10 | trap cleanup SIGINT SIGTERM 11 | 12 | HOME1=.duke 13 | 14 | CHAINID="noble-1" 15 | PEERS="4f9df51e0800e79e0d45fd376c11236b99be4e12@99.79.58.157:26656,3402e50ad4d838b26f8341a956c7b4b8a3c61ee5@65.109.93.44:21556" 16 | SNAP_RPC="https://noble-rpc.polkachu.com:443" 17 | 18 | UPGRADE_ARG="" 19 | 20 | for arg in "$@"; do 21 | case $arg in 22 | -r | --reset) 23 | rm -rf "$HOME1" 24 | ;; 25 | -t | --testnet) 26 | CHAINID="grand-1" 27 | PEERS="f2067cc7a23a4b2525f5f98430797b1e5c92e3aa@35.183.110.236:26656,8b22414f37d381a99ba99cd1edc5b884d43b7e53@65.109.23.114:21556" 28 | SNAP_RPC="https://noble-testnet-rpc.polkachu.com:443" 29 | ;; 30 | -u | --trigger-testnet-upgrade) 31 | UPGRADE_ARG="$2" 32 | shift 33 | ;; 34 | esac 35 | shift 36 | done 37 | 38 | $BIN init in-place --chain-id $CHAINID --home $HOME1 39 | 40 | LATEST_HEIGHT=$(curl -s $SNAP_RPC/block | jq -r .result.block.header.height) 41 | BLOCK_HEIGHT=$((LATEST_HEIGHT - 2000)) 42 | TRUST_HASH=$(curl -s "$SNAP_RPC/block?height=$BLOCK_HEIGHT" | jq -r .result.block_id.hash) 43 | 44 | sed -i.bak -E " 45 | s|^(enable[[:space:]]+=[[:space:]]+).*$|\1true| ; 46 | s|^(rpc_servers[[:space:]]+=[[:space:]]+).*$|\1\"$SNAP_RPC,$SNAP_RPC\"| ; 47 | s|^(trust_height[[:space:]]+=[[:space:]]+).*$|\1$BLOCK_HEIGHT| ; 48 | s|^(trust_hash[[:space:]]+=[[:space:]]+).*$|\1\"$TRUST_HASH\"| ; 49 | s|^persistent_peers *=.*|persistent_peers = \"$PEERS\"|" $HOME1/config/config.toml 50 | 51 | $BIN start --halt-height $LATEST_HEIGHT --home "$HOME1" >"$HOME1/logs.log" 2>&1 & 52 | NOBLED_PID=$! 53 | 54 | tail -f "$HOME1/logs.log" & 55 | TAIL_PID=$! 56 | 57 | # Wait for node to halt because of `--halt-height` flag 58 | wait $NOBLED_PID 59 | echo "Node is synced! Preparing for in-place testnet..." 60 | sleep 2 61 | 62 | # Create operator address that will control the chain 63 | OPERATOR=$($BIN keys add operator --home $HOME1 --keyring-backend test --output json | jq -r .address) 64 | 65 | if [[ -n "$UPGRADE_ARG" ]]; then 66 | printf 'y\n' | $BIN in-place-testnet inPlace "$OPERATOR" --trigger-testnet-upgrade "$UPGRADE_ARG" --home "$HOME1" >>"$HOME1/logs.log" 2>&1 & 67 | else 68 | printf 'y\n' | $BIN in-place-testnet inPlace "$OPERATOR" --home "$HOME1" >>"$HOME1/logs.log" 2>&1 & 69 | fi 70 | 71 | NOBLED_PID2=$! 72 | 73 | cat <<'EOF' 74 | ###################################################################### 75 | # # 76 | # STARTING IN-PLACE TESTNET # 77 | # # 78 | ###################################################################### 79 | EOF 80 | echo 👑 Operator: $OPERATOR 81 | 82 | # Keep tailing logs in the foreground to prevent script from exiting 83 | wait "$NOBLED_PID2" 84 | -------------------------------------------------------------------------------- /ante_permissioned_hyperlane.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package noble 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | ismtypes "github.com/bcp-innovations/hyperlane-cosmos/x/core/01_interchain_security/types" 24 | hyperlanetypes "github.com/bcp-innovations/hyperlane-cosmos/x/core/types" 25 | warptypes "github.com/bcp-innovations/hyperlane-cosmos/x/warp/types" 26 | sdk "github.com/cosmos/cosmos-sdk/types" 27 | "github.com/cosmos/cosmos-sdk/x/authz" 28 | ) 29 | 30 | var _ sdk.AnteDecorator = &PermissionedHyperlaneDecorator{} 31 | 32 | // PermissionedHyperlaneDecorator is a custom ante handler that permissions all Hyperlane actions on Noble. 33 | type PermissionedHyperlaneDecorator struct { 34 | dollarKeeper DollarKeeper 35 | } 36 | 37 | // DollarKeeper defines the interface expected by PermissionedHyperlaneDecorator for the Noble Dollar module. 38 | type DollarKeeper interface { 39 | GetDenom() string 40 | } 41 | 42 | func NewPermissionedHyperlaneDecorator(dollarKeeper DollarKeeper) PermissionedHyperlaneDecorator { 43 | return PermissionedHyperlaneDecorator{ 44 | dollarKeeper: dollarKeeper, 45 | } 46 | } 47 | 48 | func (d PermissionedHyperlaneDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { 49 | for _, msg := range tx.GetMsgs() { 50 | err := d.CheckMessage(msg) 51 | if err != nil { 52 | return ctx, err 53 | } 54 | } 55 | 56 | return next(ctx, tx, simulate) 57 | } 58 | 59 | func (d PermissionedHyperlaneDecorator) CheckMessage(msg sdk.Msg) error { 60 | switch m := msg.(type) { 61 | case *ismtypes.MsgAnnounceValidator: 62 | return nil 63 | case *hyperlanetypes.MsgProcessMessage: 64 | return nil 65 | case *warptypes.MsgCreateCollateralToken: 66 | if m.OriginDenom == d.dollarKeeper.GetDenom() { 67 | return fmt.Errorf("cannot create hyperlane collateral token for denom %s", m.OriginDenom) 68 | } 69 | 70 | return nil 71 | case *warptypes.MsgSetToken, *warptypes.MsgEnrollRemoteRouter, *warptypes.MsgUnrollRemoteRouter, *warptypes.MsgRemoteTransfer: 72 | return nil 73 | case *authz.MsgExec: 74 | execMsgs, err := m.GetMessages() 75 | if err != nil { 76 | return err 77 | } 78 | 79 | for _, execMsg := range execMsgs { 80 | err = d.CheckMessage(execMsg) 81 | if err != nil { 82 | return err 83 | } 84 | } 85 | default: 86 | typeUrl := sdk.MsgTypeURL(msg) 87 | if strings.HasPrefix(typeUrl, "/hyperlane") { 88 | if strings.HasPrefix(typeUrl, "/hyperlane.core.post_dispatch") { 89 | return nil 90 | } 91 | 92 | return fmt.Errorf("%s is currently a permissioned action", typeUrl) 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /local_net/utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | update_genesis() { 4 | local HOME1=$1 5 | local AUTHORITY=$2 6 | 7 | local TEMP="$HOME1/genesis.json" 8 | touch $TEMP && jq '.app_state.authority.owner = '$AUTHORITY'' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 9 | touch $TEMP && jq '.app_state.bank.denom_metadata += [{ "description": "Circle USD Coin", "denom_units": [{ "denom": "uusdc", "exponent": 0, "aliases": ["microusdc"] }, { "denom": "usdc", "exponent": 6 }], "base": "uusdc", "display": "usdc", "name": "Circle USD Coin", "symbol": "USDC" }]' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 10 | touch $TEMP && jq '.app_state.bank.denom_metadata += [{ "description": "Ondo US Dollar Yield", "denom_units": [{ "denom": "ausdy", "exponent": 0, "aliases": ["attousdy"] }, { "denom": "usdy", "exponent": 18 }], "base": "ausdy", "display": "usdy", "name": "Ondo US Dollar Yield", "symbol": "USDY" }]' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 11 | touch $TEMP && jq '.app_state.bank.denom_metadata += [{ "description": "Hashnote US Yield Coin", "denom_units": [{ "denom": "uusyc", "exponent": 0, "aliases": ["microusyc"] }, { "denom": "usyc", "exponent": 6 }], "base": "uusyc", "display": "usyc", "name": "Hashnote US Yield Coin", "symbol": "USYC" }]' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 12 | touch $TEMP && jq '.app_state.bank.denom_metadata += [{ "description": "Monerium EUR emoney", "denom_units": [{ "denom": "ueure", "exponent": 0, "aliases": ["microeure"] }, { "denom": "eure", "exponent": 6 }], "base": "ueure", "display": "eure", "name": "Monerium EUR emoney", "symbol": "EURe" }]' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 13 | touch $TEMP && jq '.app_state.bank.denom_metadata += [{ "description": "Noble Dollar", "denom_units": [{ "denom": "uusdn", "exponent": 0, "aliases": ["microusdn"] }, { "denom": "usdn", "exponent": 6 }], "base": "uusdn", "display": "usdn", "name": "Noble Dollar", "symbol": "USDN" }]' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 14 | touch $TEMP && jq '.app_state.dollar.vaults.season_one_ended = true' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 15 | touch $TEMP && jq '.app_state.dollar.vaults.season_two_yield_collector = '$AUTHORITY'' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 16 | touch $TEMP && jq '.app_state."fiat-tokenfactory".mintingDenom = { "denom": "uusdc" }' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 17 | touch $TEMP && jq '.app_state."fiat-tokenfactory".paused.paused = false' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 18 | touch $TEMP && jq '.app_state.staking.params.bond_denom = "ustake"' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 19 | touch $TEMP && jq '.app_state.wormhole.config.chain_id = 4009' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 20 | touch $TEMP && jq '.app_state.wormhole.config.gov_chain = 1' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 21 | touch $TEMP && jq '.app_state.wormhole.config.gov_address = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ="' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 22 | touch $TEMP && jq '.app_state.wormhole.guardian_sets = {"0":{"addresses":["vvpCnVfNGLf4pNkaLamrSvBdD74="],"expiration_time":0}}' $HOME1/config/genesis.json > $TEMP && mv $TEMP $HOME1/config/genesis.json 23 | } 24 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package cmd 18 | 19 | import ( 20 | "time" 21 | 22 | cmtcfg "github.com/cometbft/cometbft/config" 23 | "github.com/cosmos/cosmos-sdk/client" 24 | "github.com/cosmos/cosmos-sdk/client/config" 25 | "github.com/cosmos/cosmos-sdk/codec" 26 | "github.com/cosmos/cosmos-sdk/server" 27 | serverconfig "github.com/cosmos/cosmos-sdk/server/config" 28 | "github.com/cosmos/cosmos-sdk/types/tx/signing" 29 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 30 | txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" 31 | "github.com/noble-assets/noble/v11/jester" 32 | "github.com/spf13/cobra" 33 | ) 34 | 35 | // NewRootCmd creates a new root command for nobled. It is called once in the main function. 36 | func NewRootCmd() *cobra.Command { 37 | Initialize() 38 | 39 | rootCmd := &cobra.Command{ 40 | Use: "nobled", 41 | PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { 42 | // set the default command outputs 43 | cmd.SetOut(cmd.OutOrStdout()) 44 | cmd.SetErr(cmd.ErrOrStderr()) 45 | 46 | ClientCtx = ClientCtx.WithCmdContext(cmd.Context()) 47 | clientCtx, err := client.ReadPersistentCommandFlags(ClientCtx, cmd.Flags()) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | clientCtx, err = config.ReadFromClientConfig(clientCtx) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | // sign mode textual is only available in online mode 58 | if !clientCtx.Offline { 59 | // This needs to go after ReadFromClientConfig, as that function ets the RPC client needed for SIGN_MODE_TEXTUAL. 60 | txConfigOpts.EnabledSignModes = append(txConfigOpts.EnabledSignModes, signing.SignMode_SIGN_MODE_TEXTUAL) 61 | txConfigOpts.TextualCoinMetadataQueryFn = txmodule.NewGRPCCoinMetadataQueryFn(clientCtx) 62 | txConfigWithTextual, err := tx.NewTxConfigWithOptions(codec.NewProtoCodec(clientCtx.InterfaceRegistry), txConfigOpts) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | clientCtx = clientCtx.WithTxConfig(txConfigWithTextual) 68 | } 69 | 70 | if err := client.SetCmdClientContextHandler(clientCtx, cmd); err != nil { 71 | return err 72 | } 73 | 74 | // overwrite the minimum gas price from the app configuration 75 | srvCfg := serverconfig.DefaultConfig() 76 | srvCfg.MinGasPrices = "0ausdy,0ueure,0uusdc,0uusdn" 77 | srvCfg.API.Enable = true 78 | srvCfg.API.Swagger = true 79 | // overwrite default commit timeout from the cometbft configuration 80 | cmtCfg := cmtcfg.DefaultConfig() 81 | cmtCfg.Consensus.TimeoutCommit = 500 * time.Millisecond 82 | 83 | customAppTemplate, appConfig := jester.AppendJesterConfig(srvCfg) 84 | 85 | return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, appConfig, cmtCfg) 86 | }, 87 | } 88 | 89 | initRootCmd(rootCmd, ClientCtx.TxConfig, ModuleBasicManager) 90 | 91 | if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { 92 | panic(err) 93 | } 94 | 95 | return rootCmd 96 | } 97 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 2 | COMMIT := $(shell git log -1 --format='%H') 3 | VERSION := $(shell git describe --tags --always --dirty --match "v*") 4 | LEDGER_ENABLED ?= true 5 | 6 | # process build tags 7 | build_tags = netgo 8 | ifeq ($(LEDGER_ENABLED),true) 9 | ifeq ($(OS),Windows_NT) 10 | GCCEXE = $(shell where gcc.exe 2> NUL) 11 | ifeq ($(GCCEXE),) 12 | $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) 13 | else 14 | build_tags += ledger 15 | endif 16 | else 17 | UNAME_S = $(shell uname -s) 18 | ifeq ($(UNAME_S),OpenBSD) 19 | $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) 20 | else 21 | GCC = $(shell command -v gcc 2> /dev/null) 22 | ifeq ($(GCC),) 23 | $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) 24 | else 25 | build_tags += ledger 26 | endif 27 | endif 28 | endif 29 | endif 30 | 31 | whitespace := 32 | whitespace += $(whitespace) 33 | comma := , 34 | build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags)) 35 | 36 | ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=Noble \ 37 | -X github.com/cosmos/cosmos-sdk/version.AppName=nobled \ 38 | -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ 39 | -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ 40 | -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" 41 | ldflags += $(LDFLAGS) 42 | ldflags := $(strip $(ldflags)) 43 | 44 | BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' 45 | 46 | ############################################################################### 47 | ### Build ### 48 | ############################################################################### 49 | 50 | build: 51 | @echo "🤖 Building nobled..." 52 | @go build -mod=readonly $(BUILD_FLAGS) -o "$(PWD)/build/nobled" ./cmd/nobled 53 | @echo "✅ Completed build!" 54 | 55 | install: 56 | @echo "🤖 Installing nobled..." 57 | @go install -mod=readonly $(BUILD_FLAGS) ./cmd/nobled 58 | @echo "✅ Completed install!" 59 | 60 | ############################################################################### 61 | ### Tooling ### 62 | ############################################################################### 63 | 64 | gofumpt_cmd=mvdan.cc/gofumpt 65 | golangci_lint_cmd=github.com/golangci/golangci-lint/cmd/golangci-lint 66 | BUILDER_VERSION=0.15.3 67 | 68 | FILES := $(shell find . -name "*.go" -not -name "*.pb.go") 69 | license: 70 | @go-license --config .github/license.yml $(FILES) 71 | 72 | check-license: 73 | @go-license --config .github/license.yml $(FILES) --verify 74 | 75 | format: 76 | @echo "🤖 Running formatter..." 77 | @go run $(gofumpt_cmd) -l -w . 78 | @echo "✅ Completed formatting!" 79 | 80 | lint: 81 | @echo "🤖 Running linter..." 82 | @go run $(golangci_lint_cmd) run --timeout=10m 83 | @echo "✅ Completed linting!" 84 | 85 | swagger: 86 | @docker run --rm --volume "$(PWD)":/workspace --workdir /workspace \ 87 | ghcr.io/cosmos/proto-builder:$(BUILDER_VERSION) sh ./api/generate.sh 88 | 89 | ############################################################################### 90 | ### Testing ### 91 | ############################################################################### 92 | 93 | local-image: 94 | @echo "🤖 Building image..." 95 | @docker build -t noble:local . 96 | @echo "✅ Completed build!" 97 | 98 | .PHONY: license format lint build install local-image 99 | -------------------------------------------------------------------------------- /e2e/ratelimit_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "context" 21 | "strconv" 22 | "testing" 23 | 24 | "cosmossdk.io/math" 25 | "github.com/strangelove-ventures/interchaintest/v8" 26 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 27 | "github.com/stretchr/testify/require" 28 | 29 | "github.com/noble-assets/noble/e2e" 30 | ) 31 | 32 | func TestRateLimit(t *testing.T) { 33 | if testing.Short() { 34 | t.Skip() 35 | } 36 | t.Parallel() 37 | 38 | const ( 39 | maxPercentSend = 10 40 | maxPercentReceive = 10 41 | ) 42 | 43 | faucet := interchaintest.FaucetAccountKeyName 44 | 45 | ctx := context.Background() 46 | 47 | nw, sim, _, r, _, _, eRep, _, _ := e2e.NobleSpinUpIBC(t, ctx, e2e.LocalImages, false) 48 | noble := nw.Chain 49 | val := noble.Validators[0] 50 | 51 | simWallet, err := sim.BuildWallet(ctx, "default", "") 52 | require.NoError(t, err) 53 | 54 | // noble -> ibcSimd channel info 55 | nobleToSimChannelInfo, err := r.GetChannels(ctx, eRep, noble.Config().ChainID) 56 | require.NoError(t, err) 57 | 58 | // add rate limit via authority module 59 | _, err = val.ExecTx( 60 | ctx, nw.Authority.FormattedAddress(), 61 | "authority", 62 | "add-rate-limit", 63 | noble.Config().Denom, 64 | nobleToSimChannelInfo[0].ChannelID, 65 | strconv.Itoa(maxPercentSend), 66 | strconv.Itoa(maxPercentReceive), 67 | "24", 68 | ) 69 | require.NoError(t, err, "failed to execute rate limit tx") 70 | 71 | // get total supply of token to calculate rate limiting thresholds 72 | totalSupply, err := noble.BankQueryTotalSupplyOf(ctx, noble.Config().Denom) 73 | require.NoError(t, err) 74 | 75 | // calculate current threshold 76 | currentSendThreshold := totalSupply.Amount.Mul(math.NewInt(maxPercentSend)).Quo(math.NewInt(100)) 77 | 78 | // send up to the the current threshold, this should succeed 79 | transfer := ibc.WalletAmount{ 80 | Address: simWallet.FormattedAddress(), 81 | Denom: noble.Config().Denom, 82 | Amount: currentSendThreshold, 83 | } 84 | 85 | ibcTx, err := noble.SendIBCTransfer(ctx, nobleToSimChannelInfo[0].ChannelID, faucet, transfer, ibc.TransferOptions{}) 86 | require.NoError(t, err) 87 | require.NoError(t, ibcTx.Validate(), "failed to validate ibc tx") 88 | 89 | // send over the current threshold, this should fail 90 | transfer.Amount = math.NewInt(1) 91 | _, err = noble.SendIBCTransfer(ctx, nobleToSimChannelInfo[0].ChannelID, faucet, transfer, ibc.TransferOptions{}) 92 | require.Error(t, err, "expected rate limit to be hit, but tx was successful") 93 | 94 | // remove rate limit via authority module 95 | _, err = val.ExecTx( 96 | ctx, nw.Authority.FormattedAddress(), 97 | "authority", 98 | "remove-rate-limit", 99 | noble.Config().Denom, 100 | nobleToSimChannelInfo[0].ChannelID, 101 | ) 102 | require.NoError(t, err, "failed to execute remove rate limit tx") 103 | 104 | // retry sending over the threshold, this should now succeed since rate limit is removed 105 | ibcTx, err = noble.SendIBCTransfer(ctx, nobleToSimChannelInfo[0].ChannelID, faucet, transfer, ibc.TransferOptions{}) 106 | require.NoError(t, err) 107 | require.NoError(t, ibcTx.Validate(), "failed to validate ibc tx") 108 | } 109 | -------------------------------------------------------------------------------- /local_net/multi-val.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source ./utils.sh 4 | 5 | BIN=../build/nobled 6 | 7 | for arg in "$@"; do 8 | case $arg in 9 | -r | --reset) 10 | rm -rf .duke 11 | shift 12 | ;; 13 | esac 14 | done 15 | 16 | HOME1=.duke/val1 17 | HOME2=.duke/val2 18 | HOME3=.duke/val3 19 | 20 | P2P1=0.0.0.0:26656 21 | P2P2=0.0.0.0:36656 22 | P2P3=0.0.0.0:46656 23 | 24 | # if private validator file does not exist, create a new network 25 | if ! [ -f .duke/data/priv_validator_state.json ]; then 26 | $BIN init val1 --chain-id "duke-1" --home $HOME1 &>/dev/null 27 | $BIN init val2 --chain-id "duke-1" --home $HOME2 &>/dev/null 28 | $BIN init val3 --chain-id "duke-1" --home $HOME3 &>/dev/null 29 | 30 | # Create keys 31 | $BIN keys add val --keyring-backend test --home $HOME1 &>/dev/null 32 | $BIN keys add val --keyring-backend test --home $HOME2 &>/dev/null 33 | $BIN keys add val --keyring-backend test --home $HOME3 &>/dev/null 34 | 35 | # Add genesis accounts from each validator 36 | $BIN genesis add-genesis-account val 1000000ustake --home $HOME1 --keyring-backend test 37 | $BIN genesis add-genesis-account val 1000000ustake --home $HOME2 --keyring-backend test 38 | $BIN genesis add-genesis-account val 1000000ustake --home $HOME3 --keyring-backend test 39 | # Add genesis accounts to validator 1 who will be collecting genesis 40 | $BIN genesis add-genesis-account "$($BIN keys show val -a --keyring-backend test --home $HOME2)" 1000000ustake --home $HOME1 41 | $BIN genesis add-genesis-account "$($BIN keys show val -a --keyring-backend test --home $HOME3)" 1000000ustake --home $HOME1 42 | 43 | # Create genesis transaction's 44 | $BIN genesis gentx val 1000000ustake --chain-id "duke-1" --keyring-backend test --home $HOME1 45 | $BIN genesis gentx val 1000000ustake --chain-id "duke-1" --output-document $HOME1/config/gentx/val2.json --keyring-backend test --home $HOME2 46 | $BIN genesis gentx val 1000000ustake --chain-id "duke-1" --output-document $HOME1/config/gentx/val3.json --keyring-backend test --home $HOME3 47 | 48 | # Collect the gentx and finalize genesis 49 | $BIN genesis collect-gentxs --home $HOME1 &>/dev/null 50 | 51 | AUTHORITY=$($BIN keys add authority --home $HOME1 --keyring-backend test --output json | jq .address) 52 | $BIN genesis add-genesis-account authority 4000000ustake --home $HOME1 --keyring-backend test 53 | 54 | update_genesis $HOME1 $AUTHORITY 55 | 56 | # Copy genesis to val 2 and 3 57 | cp "$HOME1/config/genesis.json" "$HOME2/config/genesis.json" 58 | cp "$HOME1/config/genesis.json" "$HOME3/config/genesis.json" 59 | 60 | # Configure config.toml setting not available in a flag 61 | sed -i '' 's|addr_book_strict = true|addr_book_strict = false|' $HOME1/config/config.toml 62 | sed -i '' 's|addr_book_strict = true|addr_book_strict = false|' $HOME2/config/config.toml 63 | sed -i '' 's|addr_book_strict = true|addr_book_strict = false|' $HOME3/config/config.toml 64 | 65 | sed -i '' 's|allow_duplicate_ip = false|allow_duplicate_ip = true|' $HOME1/config/config.toml 66 | sed -i '' 's|allow_duplicate_ip = false|allow_duplicate_ip = true|' $HOME2/config/config.toml 67 | sed -i '' 's|allow_duplicate_ip = false|allow_duplicate_ip = true|' $HOME3/config/config.toml 68 | fi 69 | 70 | # Get persistent peers 71 | NODE_ID1=$($BIN tendermint show-node-id --home "$HOME1") 72 | PP1="$NODE_ID1@$P2P1" 73 | 74 | NODE_ID2=$($BIN tendermint show-node-id --home "$HOME2") 75 | PP2="$NODE_ID2@$P2P2" 76 | 77 | NODE_ID3=$($BIN tendermint show-node-id --home "$HOME3") 78 | PP3="$NODE_ID3@$P2P3" 79 | 80 | # Start tmux session 81 | SESSION="3v-network" 82 | tmux new-session -d -s "$SESSION" 83 | 84 | tmux split-window -h -t "$SESSION" 85 | tmux split-window -h -t "$SESSION" 86 | 87 | # Send start command 88 | # Note: C-m is equivalent to pressing Enter 89 | tmux send-keys -t "$SESSION:0.0" "$BIN start --api.enable false --home $HOME1 > $HOME1/logs.log 2>&1 &" C-m 90 | tmux send-keys -t "$SESSION:0.1" "$BIN start --api.enable false --rpc.laddr tcp://127.0.0.1:36657 --rpc.pprof_laddr localhost:6061 --grpc.address localhost:9092 --p2p.laddr tcp://$P2P2 --p2p.persistent_peers $PP1,$PP3 --home $HOME2 > $HOME2/logs.log 2>&1 &" C-m 91 | tmux send-keys -t "$SESSION:0.2" "$BIN start --api.enable false --rpc.laddr tcp://127.0.0.1:46657 --rpc.pprof_laddr localhost:6062 --grpc.address localhost:9093 --p2p.laddr tcp://$P2P3 --p2p.persistent_peers $PP1,$PP2 --home $HOME3 > $HOME3/logs.log 2>&1 &" C-m 92 | 93 | # Watch logs 94 | tmux send-keys -t "$SESSION:0.0" "tail -f $HOME1/logs.log" C-m 95 | tmux send-keys -t "$SESSION:0.1" "tail -f $HOME2/logs.log" C-m 96 | tmux send-keys -t "$SESSION:0.2" "tail -f $HOME3/logs.log" C-m 97 | 98 | # bring up session 99 | tmux attach-session -t "$SESSION" 100 | -------------------------------------------------------------------------------- /e2e/cctp_roles_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "cosmossdk.io/math" 24 | "github.com/circlefin/noble-cctp/x/cctp/types" 25 | "github.com/cosmos/gogoproto/jsonpb" 26 | "github.com/noble-assets/noble/e2e" 27 | "github.com/strangelove-ventures/interchaintest/v8" 28 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 29 | "github.com/stretchr/testify/require" 30 | ) 31 | 32 | func TestCCTP_UpdateOwner(t *testing.T) { 33 | if testing.Short() { 34 | t.Skip() 35 | } 36 | t.Parallel() 37 | 38 | ctx := context.Background() 39 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 40 | noble := nw.Chain 41 | nobleValidator := noble.Validators[0] 42 | 43 | cctpOwner := nw.CCTPRoles.Owner 44 | newOwner := interchaintest.GetAndFundTestUsers(t, ctx, "wallet", math.OneInt(), noble)[0] 45 | 46 | _, err := nobleValidator.ExecTx(ctx, cctpOwner.KeyName(), 47 | "cctp", "update-owner", newOwner.FormattedAddress(), 48 | ) 49 | require.NoError(t, err, "failed to execute update owner tx") 50 | 51 | roles, err := getRoles(nobleValidator, ctx) 52 | require.NoError(t, err, "failed to query roles") 53 | require.Equal(t, cctpOwner.FormattedAddress(), roles.Owner) 54 | 55 | _, err = nobleValidator.ExecTx(ctx, newOwner.KeyName(), 56 | "cctp", "accept-owner", 57 | ) 58 | require.NoError(t, err, "failed to execute accept owner tx") 59 | 60 | roles, err = getRoles(nobleValidator, ctx) 61 | require.NoError(t, err, "failed to query roles") 62 | require.Equal(t, newOwner.FormattedAddress(), roles.Owner) 63 | } 64 | 65 | func TestCCTP_UpdateAttesterManager(t *testing.T) { 66 | if testing.Short() { 67 | t.Skip() 68 | } 69 | t.Parallel() 70 | 71 | ctx := context.Background() 72 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 73 | noble := nw.Chain 74 | nobleValidator := noble.Validators[0] 75 | 76 | cctpOwner := nw.CCTPRoles.Owner 77 | newAttesterManager := interchaintest.GetAndFundTestUsers(t, ctx, "wallet", math.OneInt(), noble)[0] 78 | 79 | _, err := nobleValidator.ExecTx(ctx, cctpOwner.KeyName(), 80 | "cctp", "update-attester-manager", newAttesterManager.FormattedAddress(), 81 | ) 82 | require.NoError(t, err, "failed to execute update attester manager tx") 83 | 84 | roles, err := getRoles(nobleValidator, ctx) 85 | require.NoError(t, err, "failed to query roles") 86 | require.Equal(t, newAttesterManager.FormattedAddress(), roles.AttesterManager) 87 | } 88 | 89 | func TestCCTP_UpdatePauser(t *testing.T) { 90 | if testing.Short() { 91 | t.Skip() 92 | } 93 | t.Parallel() 94 | 95 | ctx := context.Background() 96 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 97 | noble := nw.Chain 98 | nobleValidator := noble.Validators[0] 99 | 100 | cctpOwner := nw.CCTPRoles.Owner 101 | newPauser := interchaintest.GetAndFundTestUsers(t, ctx, "wallet", math.OneInt(), noble)[0] 102 | 103 | _, err := nobleValidator.ExecTx(ctx, cctpOwner.KeyName(), 104 | "cctp", "update-pauser", newPauser.FormattedAddress(), 105 | ) 106 | require.NoError(t, err, "failed to execute update pauser tx") 107 | 108 | roles, err := getRoles(nobleValidator, ctx) 109 | require.NoError(t, err, "failed to query roles") 110 | require.Equal(t, newPauser.FormattedAddress(), roles.Pauser) 111 | } 112 | 113 | func TestCCTP_UpdateTokenController(t *testing.T) { 114 | if testing.Short() { 115 | t.Skip() 116 | } 117 | t.Parallel() 118 | 119 | ctx := context.Background() 120 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 121 | noble := nw.Chain 122 | nobleValidator := noble.Validators[0] 123 | 124 | cctpOwner := nw.CCTPRoles.Owner 125 | newTokenController := interchaintest.GetAndFundTestUsers(t, ctx, "wallet", math.OneInt(), noble)[0] 126 | 127 | _, err := nobleValidator.ExecTx(ctx, cctpOwner.KeyName(), 128 | "cctp", "update-token-controller", newTokenController.FormattedAddress(), 129 | ) 130 | require.NoError(t, err, "failed to execute update token controller tx") 131 | 132 | roles, err := getRoles(nobleValidator, ctx) 133 | require.NoError(t, err, "failed to query roles") 134 | require.Equal(t, newTokenController.FormattedAddress(), roles.TokenController) 135 | } 136 | 137 | func getRoles(validator *cosmos.ChainNode, ctx context.Context) (roles types.QueryRolesResponse, err error) { 138 | res, _, err := validator.ExecQuery(ctx, "cctp", "roles") 139 | if err != nil { 140 | return 141 | } 142 | 143 | err = jsonpb.UnmarshalString(string(res), &roles) 144 | return 145 | } 146 | -------------------------------------------------------------------------------- /ante.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package noble 18 | 19 | import ( 20 | errorsmod "cosmossdk.io/errors" 21 | storetypes "cosmossdk.io/store/types" 22 | "github.com/circlefin/noble-fiattokenfactory/x/fiattokenfactory" 23 | ftfkeeper "github.com/circlefin/noble-fiattokenfactory/x/fiattokenfactory/keeper" 24 | "github.com/cosmos/cosmos-sdk/codec" 25 | sdk "github.com/cosmos/cosmos-sdk/types" 26 | sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 27 | "github.com/cosmos/cosmos-sdk/types/tx/signing" 28 | "github.com/cosmos/cosmos-sdk/x/auth/ante" 29 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 30 | ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante" 31 | ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" 32 | "github.com/noble-assets/forwarding/v2" 33 | forwardingkeeper "github.com/noble-assets/forwarding/v2/keeper" 34 | forwardingtypes "github.com/noble-assets/forwarding/v2/types" 35 | ) 36 | 37 | type BankKeeper interface { 38 | authtypes.BankKeeper 39 | forwardingtypes.BankKeeper 40 | } 41 | 42 | // HandlerOptions extends the options required by the default Cosmos SDK 43 | // AnteHandler for our custom ante decorators. 44 | type HandlerOptions struct { 45 | ante.HandlerOptions 46 | cdc codec.Codec 47 | BankKeeper BankKeeper 48 | DollarKeeper DollarKeeper 49 | ForwardingKeeper *forwardingkeeper.Keeper 50 | FTFKeeper *ftfkeeper.Keeper 51 | IBCKeeper *ibckeeper.Keeper 52 | } 53 | 54 | // NewAnteHandler extends the default Cosmos SDK AnteHandler with custom ante decorators. 55 | func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { 56 | if options.AccountKeeper == nil { 57 | return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder") 58 | } 59 | 60 | if options.BankKeeper == nil { 61 | return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder") 62 | } 63 | 64 | if options.DollarKeeper == nil { 65 | return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "dollar keeper is required for ante builder") 66 | } 67 | 68 | if options.ForwardingKeeper == nil { 69 | return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "forwarding keeper is required for ante builder") 70 | } 71 | 72 | if options.FTFKeeper == nil { 73 | return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "fiattokenfactory keeper is required for ante builder") 74 | } 75 | 76 | if options.IBCKeeper == nil { 77 | return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "ibc keeper is required for ante builder") 78 | } 79 | 80 | if options.SignModeHandler == nil { 81 | return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") 82 | } 83 | 84 | anteDecorators := []sdk.AnteDecorator{ 85 | ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first 86 | ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), 87 | ante.NewValidateBasicDecorator(), 88 | ante.NewTxTimeoutHeightDecorator(), 89 | ante.NewValidateMemoDecorator(options.AccountKeeper), 90 | 91 | fiattokenfactory.NewIsPausedDecorator(options.cdc, options.FTFKeeper), 92 | fiattokenfactory.NewIsBlacklistedDecorator(options.FTFKeeper), 93 | 94 | NewPermissionedHyperlaneDecorator(options.DollarKeeper), 95 | NewPermissionedLiquidityDecorator(), 96 | 97 | ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), 98 | ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), 99 | ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators 100 | ante.NewValidateSigCountDecorator(options.AccountKeeper), 101 | ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), 102 | 103 | NewSigVerificationDecorator(options), 104 | 105 | ante.NewIncrementSequenceDecorator(options.AccountKeeper), 106 | 107 | ibcante.NewRedundantRelayDecorator(options.IBCKeeper), 108 | } 109 | 110 | return sdk.ChainAnteDecorators(anteDecorators...), nil 111 | } 112 | 113 | func NewSigVerificationDecorator(options HandlerOptions) sdk.AnteDecorator { 114 | defaultAnte := ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler) 115 | return forwarding.NewSigVerificationDecorator(options.BankKeeper, options.ForwardingKeeper, defaultAnte) 116 | } 117 | 118 | // SigVerificationGasConsumer is a custom implementation of the signature verification gas 119 | // consumer to handle public keys defined in the Forwarding module. 120 | func SigVerificationGasConsumer(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error { 121 | switch sig.PubKey.(type) { 122 | case *forwardingtypes.ForwardingPubKey: 123 | return nil 124 | default: 125 | return ante.DefaultSigVerificationGasConsumer(meter, sig, params) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package cmd 18 | 19 | import ( 20 | "os" 21 | 22 | "cosmossdk.io/client/v2/autocli" 23 | clientv2keyring "cosmossdk.io/client/v2/autocli/keyring" 24 | "cosmossdk.io/core/address" 25 | "cosmossdk.io/core/appmodule" 26 | "cosmossdk.io/depinject" 27 | "cosmossdk.io/log" 28 | "github.com/cosmos/cosmos-sdk/client" 29 | "github.com/cosmos/cosmos-sdk/client/config" 30 | "github.com/cosmos/cosmos-sdk/codec" 31 | codectypes "github.com/cosmos/cosmos-sdk/codec/types" 32 | "github.com/cosmos/cosmos-sdk/crypto/keyring" 33 | sdk "github.com/cosmos/cosmos-sdk/types" 34 | "github.com/cosmos/cosmos-sdk/types/module" 35 | "github.com/cosmos/cosmos-sdk/x/auth/tx" 36 | "github.com/cosmos/cosmos-sdk/x/auth/types" 37 | pfm "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8/packetforward" 38 | pfmtypes "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8/packetforward/types" 39 | ratelimit "github.com/cosmos/ibc-apps/modules/rate-limiting/v8" 40 | ratelimittypes "github.com/cosmos/ibc-apps/modules/rate-limiting/v8/types" 41 | "github.com/cosmos/ibc-go/modules/capability" 42 | capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" 43 | ica "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts" 44 | icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types" 45 | "github.com/cosmos/ibc-go/v8/modules/apps/transfer" 46 | transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" 47 | ibc "github.com/cosmos/ibc-go/v8/modules/core" 48 | ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" 49 | soloclient "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" 50 | tmclient "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" 51 | "github.com/noble-assets/noble/v11" 52 | ) 53 | 54 | var ( 55 | // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address. 56 | Bech32PrefixAccAddr = "noble" 57 | // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. 58 | Bech32PrefixAccPub = Bech32PrefixAccAddr + "pub" 59 | // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address. 60 | Bech32PrefixValAddr = Bech32PrefixAccAddr + "valoper" 61 | // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key. 62 | Bech32PrefixValPub = Bech32PrefixAccAddr + "valoperpub" 63 | // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address. 64 | Bech32PrefixConsAddr = Bech32PrefixAccAddr + "valcons" 65 | // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key. 66 | Bech32PrefixConsPub = Bech32PrefixAccAddr + "valconspub" 67 | 68 | txConfigOpts tx.ConfigOptions 69 | autoCliOpts autocli.AppOptions 70 | ModuleBasicManager module.BasicManager 71 | ClientCtx client.Context 72 | ) 73 | 74 | func Initialize() { 75 | cfg := sdk.GetConfig() 76 | cfg.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) 77 | cfg.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) 78 | cfg.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) 79 | cfg.Seal() 80 | 81 | if err := depinject.Inject( 82 | depinject.Configs(noble.AppConfig(), 83 | depinject.Supply( 84 | log.NewNopLogger(), 85 | ), 86 | depinject.Provide( 87 | ProvideClientContext, 88 | ProvideKeyring, 89 | ), 90 | ), 91 | &txConfigOpts, 92 | &autoCliOpts, 93 | &ModuleBasicManager, 94 | &ClientCtx, 95 | ); err != nil { 96 | panic(err) 97 | } 98 | 99 | // Since the IBC modules don't support dependency injection, we need to 100 | // manually register the modules on the client side. 101 | // This needs to be removed after IBC supports App Wiring. 102 | modules := map[string]appmodule.AppModule{ 103 | capabilitytypes.ModuleName: capability.AppModule{}, 104 | ibcexported.ModuleName: ibc.AppModule{}, 105 | icatypes.ModuleName: ica.AppModule{}, 106 | pfmtypes.ModuleName: pfm.AppModule{}, 107 | transfertypes.ModuleName: transfer.AppModule{}, 108 | tmclient.ModuleName: tmclient.AppModule{}, 109 | soloclient.ModuleName: soloclient.AppModule{}, 110 | ratelimittypes.ModuleName: ratelimit.AppModule{}, 111 | } 112 | for name, mod := range modules { 113 | ModuleBasicManager[name] = module.CoreAppModuleBasicAdaptor(name, mod) 114 | ModuleBasicManager[name].RegisterInterfaces(ClientCtx.InterfaceRegistry) 115 | autoCliOpts.Modules[name] = mod 116 | } 117 | } 118 | 119 | func ProvideClientContext( 120 | appCodec codec.Codec, 121 | interfaceRegistry codectypes.InterfaceRegistry, 122 | txConfig client.TxConfig, 123 | legacyAmino *codec.LegacyAmino, 124 | ) client.Context { 125 | clientCtx := client.Context{}. 126 | WithCodec(appCodec). 127 | WithInterfaceRegistry(interfaceRegistry). 128 | WithTxConfig(txConfig). 129 | WithLegacyAmino(legacyAmino). 130 | WithInput(os.Stdin). 131 | WithAccountRetriever(types.AccountRetriever{}). 132 | WithHomeDir(noble.DefaultNodeHome). 133 | WithViper("") // env variable prefix 134 | 135 | // Read the config again to overwrite the default values with the values from the config file 136 | clientCtx, _ = config.ReadFromClientConfig(clientCtx) 137 | 138 | return clientCtx 139 | } 140 | 141 | func ProvideKeyring(clientCtx client.Context, addressCodec address.Codec) (clientv2keyring.Keyring, error) { 142 | kb, err := client.NewKeyringFromBackend(clientCtx, clientCtx.Keyring.Backend()) 143 | if err != nil { 144 | return nil, err 145 | } 146 | 147 | return keyring.NewAutoCLIKeyring(kb) 148 | } 149 | -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | modules: 2 | - name: runtime 3 | config: 4 | "@type": cosmos.app.runtime.v1alpha1.Module 5 | app_name: Noble 6 | pre_blockers: [upgrade] 7 | begin_blockers: 8 | [ 9 | capability, 10 | authority, 11 | slashing, 12 | evidence, 13 | staking, 14 | ibc, 15 | authz, 16 | swap, 17 | ratelimit, 18 | dollar, 19 | ] 20 | end_blockers: [crisis, staking, feegrant, forwarding, ratelimit] 21 | init_genesis: 22 | [ 23 | capability, 24 | transfer, 25 | auth, 26 | bank, 27 | staking, 28 | slashing, 29 | crisis, 30 | fiat-tokenfactory, 31 | globalfee, 32 | genutil, 33 | ibc, 34 | interchainaccounts, 35 | packetfowardmiddleware, 36 | evidence, 37 | authz, 38 | feegrant, 39 | params, 40 | upgrade, 41 | vesting, 42 | cctp, 43 | forwarding, 44 | aura, 45 | halo, 46 | florin, 47 | authority, 48 | wormhole, 49 | dollar, 50 | swap, 51 | hyperlane, 52 | warp, 53 | ratelimit, 54 | orbiter, 55 | ] 56 | override_store_keys: 57 | - module_name: auth 58 | kv_store_key: acc 59 | - module_name: fiat-tokenfactory 60 | kv_store_key: fiattokenfactory 61 | - name: auth 62 | config: 63 | "@type": cosmos.auth.module.v1.Module 64 | bech32_prefix: noble 65 | module_account_permissions: 66 | - account: fee_collector 67 | - account: interchainaccounts 68 | - account: bonded_tokens_pool 69 | permissions: [burner, staking] 70 | - account: not_bonded_tokens_pool 71 | permissions: [burner, staking] 72 | - account: transfer 73 | permissions: [burner, minter] 74 | - account: fiat-tokenfactory 75 | permissions: [burner, minter] 76 | - account: cctp 77 | - account: halo 78 | permissions: [burner, minter] 79 | - account: florin 80 | permissions: [burner, minter] 81 | - account: aura 82 | permissions: [burner, minter] 83 | - account: dollar 84 | permissions: [burner, minter] 85 | - account: dollar/yield 86 | - account: hyperlane 87 | - account: warp 88 | - account: orbiter 89 | - account: orbiter/dust_collector 90 | authority: authority # Utilize our custom x/authority module. 91 | - name: authz 92 | config: 93 | "@type": cosmos.authz.module.v1.Module 94 | - name: bank 95 | config: 96 | "@type": cosmos.bank.module.v1.Module 97 | blocked_module_accounts_override: 98 | [ 99 | auth, 100 | bonded_tokens_pool, 101 | not_bonded_tokens_pool, 102 | orbiter/dust_collector, 103 | ] 104 | authority: authority # Utilize our custom x/authority module. 105 | - name: consensus 106 | config: 107 | "@type": cosmos.consensus.module.v1.Module 108 | authority: authority # Utilize our custom x/authority module. 109 | - name: crisis 110 | config: 111 | "@type": cosmos.crisis.module.v1.Module 112 | authority: authority # Utilize our custom x/authority module. 113 | - name: evidence 114 | config: 115 | "@type": cosmos.evidence.module.v1.Module 116 | - name: feegrant 117 | config: 118 | "@type": cosmos.feegrant.module.v1.Module 119 | - name: genutil 120 | config: 121 | "@type": cosmos.genutil.module.v1.Module 122 | - name: params 123 | config: 124 | "@type": cosmos.params.module.v1.Module 125 | - name: slashing 126 | config: 127 | "@type": cosmos.slashing.module.v1.Module 128 | authority: authority # Utilize our custom x/authority module. 129 | - name: staking 130 | config: 131 | "@type": cosmos.staking.module.v1.Module 132 | authority: authority # Utilize our custom x/authority module. 133 | - name: tx 134 | config: 135 | "@type": cosmos.tx.config.v1.Config 136 | - name: upgrade 137 | config: 138 | "@type": cosmos.upgrade.module.v1.Module 139 | authority: authority # Utilize our custom x/authority module. 140 | - name: vesting 141 | config: 142 | "@type": cosmos.vesting.module.v1.Module 143 | # Circle Modules 144 | - name: cctp 145 | config: 146 | "@type": circle.cctp.module.v1.Module 147 | - name: fiat-tokenfactory 148 | config: 149 | "@type": circle.fiattokenfactory.module.v1.Module 150 | # Ondo Modules 151 | - name: aura 152 | config: 153 | "@type": aura.module.v1.Module 154 | denom: ausdy 155 | # Hashnote Modules 156 | - name: halo 157 | config: 158 | "@type": halo.module.v1.Module 159 | denom: uusyc 160 | underlying: uusdc 161 | # Hyperlane Modules 162 | - name: hyperlane 163 | config: 164 | "@type": hyperlane.core.module.v1.Module 165 | authority: authority # Utilize our custom x/authority module. 166 | - name: warp 167 | config: 168 | "@type": hyperlane.warp.module.v1.Module 169 | authority: authority # Utilize our custom x/authority module. 170 | enabled_tokens: 171 | - 1 # Enable Collateral tokens 172 | # Monerium Modules 173 | - name: florin 174 | config: 175 | "@type": florin.module.v1.Module 176 | authority: authority # Utilize our custom x/authority module. 177 | # Noble Modules 178 | - name: authority 179 | config: 180 | "@type": noble.authority.module.v1.Module 181 | - name: dollar 182 | config: 183 | "@type": noble.dollar.module.v1.Module 184 | authority: authority # Utilize our custom x/authority module. 185 | denom: uusdn 186 | vaults_minimum_lock: 1e6 187 | vaults_minimum_unlock: 1e6 188 | - name: forwarding 189 | config: 190 | "@type": noble.forwarding.module.v1.Module 191 | authority: authority # Utilize our custom x/authority module. 192 | - name: globalfee 193 | config: 194 | "@type": noble.globalfee.module.v1.Module 195 | authority: authority # Utilize our custom x/authority module. 196 | - name: orbiter 197 | config: 198 | "@type": noble.orbiter.module.v1.Module 199 | authority: authority # Utilize our custom x/authority module. 200 | - name: swap 201 | config: 202 | "@type": noble.swap.module.v1.Module 203 | authority: authority # Utilize our custom x/authority module. 204 | base_denom: uusdn 205 | base_minimum_deposit: 1e6 206 | max_add_liquidity_slippage_percentage: 0.5e4 207 | stableswap: 208 | unbonding_block_delta: 30 209 | - name: wormhole 210 | config: 211 | "@type": wormhole.module.v1.Module 212 | -------------------------------------------------------------------------------- /e2e/ibc_utils.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "path" 24 | 25 | "cosmossdk.io/math" 26 | "github.com/cosmos/cosmos-sdk/codec" 27 | sdk "github.com/cosmos/cosmos-sdk/types" 28 | icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types" 29 | chantypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" 30 | "github.com/strangelove-ventures/interchaintest/v8" 31 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 32 | "github.com/strangelove-ventures/interchaintest/v8/ibc" 33 | "github.com/strangelove-ventures/interchaintest/v8/testreporter" 34 | ) 35 | 36 | // ICATestSuite is used to test interchain accounts. The IcaAddress field is initially an empty string, 37 | // after initializing the ICA account you must update the field with the corresponding value if you need to send ICA txs in later test logic. 38 | type ICATestSuite struct { 39 | Host *cosmos.CosmosChain 40 | Controller *cosmos.CosmosChain 41 | Relayer ibc.Relayer 42 | Rep *testreporter.RelayerExecReporter 43 | OwnerAddress string 44 | IcaAddress string 45 | InitBal math.Int 46 | HostConnectionID string 47 | ControllerConnectionID string 48 | 49 | Encoding string 50 | Msgs []sdk.Msg 51 | TxMemo string 52 | } 53 | 54 | // RegisterICAAccount attempts to register a new interchain account on a host chain via a controller chain. 55 | func RegisterICAAccount(ctx context.Context, icaTs *ICATestSuite) (string, error) { 56 | height, err := icaTs.Controller.Height(ctx) 57 | if err != nil { 58 | return "", err 59 | } 60 | 61 | version, err := json.Marshal(map[string]any{ 62 | "version": icatypes.Version, 63 | "controller_connection_id": icaTs.ControllerConnectionID, 64 | "host_connection_id": icaTs.HostConnectionID, 65 | "address": "", 66 | "encoding": icaTs.Encoding, 67 | "tx_type": icatypes.TxTypeSDKMultiMsg, 68 | }) 69 | if err != nil { 70 | return "", err 71 | } 72 | 73 | cmd := []string{ 74 | "interchain-accounts", "controller", "register", 75 | icaTs.ControllerConnectionID, 76 | "--ordering", "ORDER_ORDERED", 77 | "--version", string(version), 78 | } 79 | 80 | _, err = icaTs.Controller.Validators[0].ExecTx(ctx, icaTs.OwnerAddress, cmd...) 81 | if err != nil { 82 | return "", err 83 | } 84 | 85 | // It takes a few blocks for the ICA channel handshake to be completed by the relayer. 86 | // Query for the MsgChannelOpenConfirm on the host chain so we know when the ICA has been initialized. 87 | channelFound := func(found *chantypes.MsgChannelOpenConfirm) bool { 88 | return found.PortId == icatypes.HostPortID 89 | } 90 | 91 | _, err = cosmos.PollForMessage(ctx, icaTs.Host, NobleEncoding().InterfaceRegistry, height, height+15, channelFound) 92 | if err != nil { 93 | return "", fmt.Errorf("failed to poll for channel open confirmation: %w", err) 94 | } 95 | 96 | icaAddr, err := ICAAddress(ctx, icaTs.Controller, icaTs.OwnerAddress, icaTs.ControllerConnectionID) 97 | if err != nil { 98 | return "", err 99 | } 100 | 101 | err = icaTs.Host.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ 102 | Address: icaAddr, 103 | Denom: icaTs.Host.Config().Denom, 104 | Amount: icaTs.InitBal, 105 | }) 106 | if err != nil { 107 | return "", err 108 | } 109 | 110 | return icaAddr, nil 111 | } 112 | 113 | // ICAAddressResponse represents the response from querying an interchain account via the controller module. 114 | type ICAAddressResponse struct { 115 | Address string `json:"address"` 116 | } 117 | 118 | // ICAAddress attempts to query the address of a registered interchain account on a controller chain for a given 119 | // address and connection ID. 120 | func ICAAddress( 121 | ctx context.Context, 122 | controller *cosmos.CosmosChain, 123 | address string, 124 | connectionID string, 125 | ) (string, error) { 126 | cmd := []string{ 127 | "interchain-accounts", "controller", "interchain-account", 128 | address, connectionID, 129 | } 130 | 131 | stdout, _, err := controller.Validators[0].ExecQuery(ctx, cmd...) 132 | if err != nil { 133 | return "", err 134 | } 135 | 136 | var result ICAAddressResponse 137 | err = json.Unmarshal(stdout, &result) 138 | if err != nil { 139 | return "", err 140 | } 141 | 142 | if result.Address == "" { 143 | return "", fmt.Errorf("ICA address not found for address(%s), connection(%s)", address, connectionID) 144 | } 145 | 146 | return result.Address, nil 147 | } 148 | 149 | // SendICATx attempts to serialize a slice of sdk.Msg and generate interchain account packet data, using the 150 | // specified encoding, before attempting to send an ICA tx via the controller module. 151 | func SendICATx(ctx context.Context, icaTs *ICATestSuite) error { 152 | cdc := codec.NewProtoCodec(icaTs.Controller.GetCodec().InterfaceRegistry()) 153 | 154 | dataBz, err := icatypes.SerializeCosmosTx(cdc, icaTs.Msgs, icaTs.Encoding) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | packetData := icatypes.InterchainAccountPacketData{ 160 | Type: icatypes.EXECUTE_TX, 161 | Data: dataBz, 162 | Memo: icaTs.TxMemo, 163 | } 164 | 165 | if err := packetData.ValidateBasic(); err != nil { 166 | return err 167 | } 168 | 169 | packetDataBz, err := cdc.MarshalJSON(&packetData) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | node := icaTs.Controller.Validators[0] 175 | 176 | relPath := "packet_msg.json" 177 | err = node.WriteFile(ctx, packetDataBz, relPath) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | packetMsgPath := path.Join(node.HomeDir(), relPath) 183 | 184 | height, err := icaTs.Controller.Height(ctx) 185 | if err != nil { 186 | return err 187 | } 188 | 189 | cmd := []string{ 190 | "interchain-accounts", "controller", "send-tx", 191 | icaTs.ControllerConnectionID, 192 | packetMsgPath, 193 | } 194 | 195 | _, err = node.ExecTx(ctx, icaTs.OwnerAddress, cmd...) 196 | if err != nil { 197 | return err 198 | } 199 | 200 | _, err = cosmos.PollForMessage[*chantypes.MsgAcknowledgement](ctx, icaTs.Controller, NobleEncoding().InterfaceRegistry, height, height+20, nil) 201 | if err != nil { 202 | return fmt.Errorf("failed to poll for acknowledgement: %w", err) 203 | } 204 | 205 | return nil 206 | } 207 | -------------------------------------------------------------------------------- /cmd/commands.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package cmd 18 | 19 | import ( 20 | "errors" 21 | "io" 22 | 23 | "cosmossdk.io/log" 24 | confixcmd "cosmossdk.io/tools/confix/cmd" 25 | "github.com/cometbft/cometbft/crypto" 26 | "github.com/cometbft/cometbft/libs/bytes" 27 | dbm "github.com/cosmos/cosmos-db" 28 | "github.com/cosmos/cosmos-sdk/client" 29 | "github.com/cosmos/cosmos-sdk/client/debug" 30 | "github.com/cosmos/cosmos-sdk/client/flags" 31 | "github.com/cosmos/cosmos-sdk/client/keys" 32 | "github.com/cosmos/cosmos-sdk/client/pruning" 33 | "github.com/cosmos/cosmos-sdk/client/rpc" 34 | "github.com/cosmos/cosmos-sdk/client/snapshot" 35 | "github.com/cosmos/cosmos-sdk/server" 36 | servertypes "github.com/cosmos/cosmos-sdk/server/types" 37 | sdk "github.com/cosmos/cosmos-sdk/types" 38 | "github.com/cosmos/cosmos-sdk/types/module" 39 | authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" 40 | "github.com/cosmos/cosmos-sdk/x/crisis" 41 | genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" 42 | "github.com/spf13/cobra" 43 | "github.com/spf13/viper" 44 | 45 | "github.com/noble-assets/noble/v11" 46 | "github.com/noble-assets/noble/v11/jester" 47 | ) 48 | 49 | func addStartFlags(startCmd *cobra.Command) { 50 | crisis.AddModuleInitFlags(startCmd) 51 | jester.AddFlags(startCmd) 52 | } 53 | 54 | func initRootCmd(rootCmd *cobra.Command, txConfig client.TxConfig, basicManager module.BasicManager) { 55 | cfg := sdk.GetConfig() 56 | cfg.Seal() 57 | 58 | rootCmd.AddCommand( 59 | genutilcli.InitCmd(basicManager, noble.DefaultNodeHome), 60 | debug.Cmd(), 61 | confixcmd.ConfigCommand(), 62 | pruning.Cmd(newApp, noble.DefaultNodeHome), 63 | snapshot.Cmd(newApp), 64 | ) 65 | 66 | server.AddCommands(rootCmd, noble.DefaultNodeHome, newApp, appExport, addStartFlags) 67 | server.AddTestnetCreatorCommand(rootCmd, newTestnetApp, addStartFlags) 68 | 69 | // add keybase, auxiliary RPC, query, genesis, and tx child commands 70 | rootCmd.AddCommand( 71 | server.StatusCommand(), 72 | genutilcli.Commands(txConfig, basicManager, noble.DefaultNodeHome), 73 | queryCommand(), 74 | txCommand(), 75 | keys.Commands(), 76 | ) 77 | } 78 | 79 | func queryCommand() *cobra.Command { 80 | cmd := &cobra.Command{ 81 | Use: "query", 82 | Aliases: []string{"q"}, 83 | Short: "Querying subcommands", 84 | DisableFlagParsing: false, 85 | SuggestionsMinimumDistance: 2, 86 | RunE: client.ValidateCmd, 87 | } 88 | 89 | cmd.AddCommand( 90 | rpc.ValidatorCommand(), 91 | server.QueryBlockCmd(), 92 | authcmd.QueryTxsByEventsCmd(), 93 | server.QueryBlocksCmd(), 94 | authcmd.QueryTxCmd(), 95 | server.QueryBlockResultsCmd(), 96 | ) 97 | 98 | return cmd 99 | } 100 | 101 | func txCommand() *cobra.Command { 102 | cmd := &cobra.Command{ 103 | Use: "tx", 104 | Short: "Transactions subcommands", 105 | DisableFlagParsing: false, 106 | SuggestionsMinimumDistance: 2, 107 | RunE: client.ValidateCmd, 108 | } 109 | 110 | cmd.AddCommand( 111 | authcmd.GetSignCommand(), 112 | authcmd.GetSignBatchCommand(), 113 | authcmd.GetMultiSignCommand(), 114 | authcmd.GetMultiSignBatchCmd(), 115 | authcmd.GetValidateSignaturesCommand(), 116 | authcmd.GetBroadcastCommand(), 117 | authcmd.GetEncodeCommand(), 118 | authcmd.GetDecodeCommand(), 119 | authcmd.GetSimulateCmd(), 120 | ) 121 | 122 | return cmd 123 | } 124 | 125 | // newApp is an appCreator 126 | func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { 127 | baseappOptions := server.DefaultBaseappOptions(appOpts) 128 | app, err := noble.NewApp(logger, db, traceStore, true, appOpts, baseappOptions...) 129 | if err != nil { 130 | panic(err) 131 | } 132 | 133 | return app 134 | } 135 | 136 | // newTestnetApp starts by running the normal newApp method. From there, the 137 | // app interface returned is modified in order for an in-place testnet to be 138 | // created from the provided app. 139 | func newTestnetApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts servertypes.AppOptions) servertypes.Application { 140 | app := newApp(logger, db, traceStore, appOpts).(*noble.App) 141 | 142 | newValAddr, ok := appOpts.Get(server.KeyNewValAddr).(bytes.HexBytes) 143 | if !ok { 144 | panic("newValAddr is not of type bytes.HexBytes") 145 | } 146 | newValPubKey, ok := appOpts.Get(server.KeyUserPubKey).(crypto.PubKey) 147 | if !ok { 148 | panic("newValPubKey is not of type crypto.PubKey") 149 | } 150 | newOperatorAddress, ok := appOpts.Get(server.KeyNewOpAddr).(string) 151 | if !ok { 152 | panic("newOperatorAddress is not of type string") 153 | } 154 | upgradeToTrigger, ok := appOpts.Get(server.KeyTriggerTestnetUpgrade).(string) 155 | if !ok { 156 | panic("upgradeToTrigger is not of type string") 157 | } 158 | 159 | return noble.InitAppForTestnet(app, newValPubKey, newValAddr, newOperatorAddress, upgradeToTrigger) 160 | } 161 | 162 | // appExport creates a new app (optionally at a given height) and exports state. 163 | func appExport( 164 | logger log.Logger, 165 | db dbm.DB, 166 | traceStore io.Writer, 167 | height int64, 168 | forZeroHeight bool, 169 | jailAllowedAddrs []string, 170 | appOpts servertypes.AppOptions, 171 | modulesToExport []string, 172 | ) (servertypes.ExportedApp, error) { 173 | var ( 174 | app *noble.App 175 | err error 176 | ) 177 | 178 | // this check is necessary as we use the flag in x/upgrade. 179 | // we can exit more gracefully by checking the flag here. 180 | homePath, ok := appOpts.Get(flags.FlagHome).(string) 181 | if !ok || homePath == "" { 182 | return servertypes.ExportedApp{}, errors.New("application home not set") 183 | } 184 | 185 | viperAppOpts, ok := appOpts.(*viper.Viper) 186 | if !ok { 187 | return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper") 188 | } 189 | 190 | // overwrite the FlagInvCheckPeriod 191 | viperAppOpts.Set(server.FlagInvCheckPeriod, 1) 192 | appOpts = viperAppOpts 193 | 194 | if height != -1 { 195 | app, err = noble.NewApp(logger, db, traceStore, false, appOpts) 196 | if err != nil { 197 | return servertypes.ExportedApp{}, err 198 | } 199 | 200 | if err := app.LoadHeight(height); err != nil { 201 | return servertypes.ExportedApp{}, err 202 | } 203 | } else { 204 | app, err = noble.NewApp(logger, db, traceStore, true, appOpts) 205 | if err != nil { 206 | return servertypes.ExportedApp{}, err 207 | } 208 | } 209 | 210 | return app.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) 211 | } 212 | -------------------------------------------------------------------------------- /proposal.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package noble 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "slices" 23 | "time" 24 | 25 | "cosmossdk.io/errors" 26 | abci "github.com/cometbft/cometbft/abci/types" 27 | "github.com/cosmos/cosmos-sdk/baseapp" 28 | "github.com/cosmos/cosmos-sdk/client" 29 | sdk "github.com/cosmos/cosmos-sdk/types" 30 | "github.com/cosmos/cosmos-sdk/types/mempool" 31 | 32 | "connectrpc.com/connect" 33 | jester "jester.noble.xyz/api" 34 | 35 | wormholekeeper "github.com/noble-assets/wormhole/keeper" 36 | wormholetypes "github.com/noble-assets/wormhole/types" 37 | vaautils "github.com/wormhole-foundation/wormhole/sdk/vaa" 38 | 39 | dollarkeeper "dollar.noble.xyz/v2/keeper" 40 | dollarportaltypes "dollar.noble.xyz/v2/types/portal" 41 | ) 42 | 43 | // jesterIndex is the index of the injected Jester response in a block. 44 | const jesterIndex = 0 45 | 46 | type ProposalHandler struct { 47 | txConfig client.TxConfig 48 | 49 | jesterClient jester.QueryServiceClient 50 | wormholeServer wormholetypes.QueryServer 51 | dollarKeeper *dollarkeeper.Keeper 52 | 53 | defaultPrepareProposalHandler sdk.PrepareProposalHandler 54 | defaultPreBlocker sdk.PreBlocker 55 | } 56 | 57 | func NewProposalHandler( 58 | app *baseapp.BaseApp, 59 | mempool mempool.Mempool, 60 | preBlocker sdk.PreBlocker, 61 | txConfig client.TxConfig, 62 | jesterClient jester.QueryServiceClient, 63 | dollarKeeper *dollarkeeper.Keeper, 64 | wormholeKeeper *wormholekeeper.Keeper, 65 | ) *ProposalHandler { 66 | defaultHandler := baseapp.NewDefaultProposalHandler(mempool, app) 67 | 68 | return &ProposalHandler{ 69 | txConfig: txConfig, 70 | 71 | jesterClient: jesterClient, 72 | wormholeServer: wormholekeeper.NewQueryServer(wormholeKeeper), 73 | dollarKeeper: dollarKeeper, 74 | 75 | defaultPrepareProposalHandler: defaultHandler.PrepareProposalHandler(), 76 | defaultPreBlocker: preBlocker, 77 | } 78 | } 79 | 80 | // PrepareProposal is the logic called by the current block proposer to prepare 81 | // a block proposal. Noble modifies this by making a request to our sidecar 82 | // service, Jester, to check if there are any outstanding $USDN transfers that 83 | // need to be relayed to Noble. These transfers (in the form of Wormhole VAAs) 84 | // are injected as the first transaction of the block, and are later processed 85 | // by the PreBlocker handler. 86 | func (h *ProposalHandler) PrepareProposal() sdk.PrepareProposalHandler { 87 | return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (*abci.ResponsePrepareProposal, error) { 88 | logger := ctx.Logger() 89 | 90 | res, err := h.defaultPrepareProposalHandler(ctx, req) 91 | if err != nil { 92 | return nil, errors.Wrap(err, "default PrepareProposal handler failed") 93 | } 94 | 95 | ctxWithTimeout, cancel := context.WithTimeout(ctx, 500*time.Millisecond) 96 | defer cancel() 97 | 98 | request := connect.NewRequest(&jester.GetVoteExtensionRequest{}) 99 | jesterRes, err := h.jesterClient.GetVoteExtension(ctxWithTimeout, request) 100 | if err != nil { 101 | logger.Error("failed to query jester", "err", err) 102 | } 103 | 104 | if jesterRes != nil && jesterRes.Msg != nil && jesterRes.Msg.Dollar != nil && len(jesterRes.Msg.Dollar.Vaas) > 0 { 105 | var nonExecutedVAAs []sdk.Msg 106 | 107 | for _, raw := range jesterRes.Msg.Dollar.Vaas { 108 | vaa, err := vaautils.Unmarshal(raw) 109 | if err != nil { 110 | logger.Warn("failed to unmarshal transfer from jester", "err", err) 111 | continue 112 | } 113 | 114 | wormholeRes, _ := h.wormholeServer.ExecutedVAA(ctx, &wormholetypes.QueryExecutedVAA{ 115 | Input: vaa.SigningDigest().String(), 116 | }) 117 | 118 | if wormholeRes != nil && !wormholeRes.Executed { 119 | nonExecutedVAAs = append(nonExecutedVAAs, &dollarportaltypes.MsgDeliverInjection{ 120 | Vaa: raw, 121 | }) 122 | } else { 123 | logger.Warn("skipped already executed transfer from jester", "identifier", vaa.MessageID()) 124 | } 125 | } 126 | 127 | if len(nonExecutedVAAs) > 0 { 128 | builder := h.txConfig.NewTxBuilder() 129 | 130 | err := builder.SetMsgs(nonExecutedVAAs...) 131 | if err != nil { 132 | return nil, errors.Wrap(err, "failed to set messages of injected jester tx") 133 | } 134 | 135 | tx := builder.GetTx() 136 | 137 | bz, err := h.txConfig.TxEncoder()(tx) 138 | if err != nil { 139 | return nil, errors.Wrap(err, "failed to marshal injected jester tx") 140 | } 141 | res.Txs = slices.Insert(res.Txs, jesterIndex, bz) 142 | 143 | logger.Info(fmt.Sprintf("injected %d pending transfers from jester", len(nonExecutedVAAs))) 144 | } 145 | } 146 | 147 | return &abci.ResponsePrepareProposal{Txs: res.Txs}, nil 148 | } 149 | } 150 | 151 | // PreBlocker processes all injected $USDN transfers from Jester. 152 | func (h *ProposalHandler) PreBlocker() sdk.PreBlocker { 153 | return func(ctx sdk.Context, req *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { 154 | res, err := h.defaultPreBlocker(ctx, req) 155 | if err != nil { 156 | return nil, errors.Wrap(err, "default PreBlocker failed") 157 | } 158 | 159 | if len(req.Txs) == 0 { 160 | return res, nil 161 | } 162 | 163 | tx := req.Txs[jesterIndex] 164 | h.handleJesterTx(ctx, tx) 165 | 166 | return res, nil 167 | } 168 | } 169 | 170 | // handleJesterTx is a utility that processes messages from Jester. 171 | func (h *ProposalHandler) handleJesterTx(ctx sdk.Context, bytes []byte) { 172 | logger := ctx.Logger() 173 | 174 | defer func() { 175 | if r := recover(); r != nil { 176 | logger.Error("recovered panic when handling transfers from jester", "err", r) 177 | } 178 | }() 179 | 180 | tx, err := h.txConfig.TxDecoder()(bytes) 181 | if err != nil { 182 | logger.Error("failed to unmarshal injected jester tx", "err", err) 183 | return 184 | } 185 | 186 | var count int 187 | for _, raw := range tx.GetMsgs() { 188 | msg, ok := raw.(*dollarportaltypes.MsgDeliverInjection) 189 | // If the first message is not a MsgDeliverInjection, no VAAs were injected. 190 | if !ok { 191 | break 192 | } 193 | 194 | vaa, err := vaautils.Unmarshal(msg.Vaa) 195 | if err != nil { 196 | logger.Error("failed to unmarshal transfer from jester", "err", err) 197 | continue 198 | } 199 | 200 | cachedCtx, writeCache := ctx.CacheContext() 201 | if err := h.dollarKeeper.Deliver(cachedCtx, msg.Vaa); err != nil { 202 | logger.Error("failed to process transfer from jester", "identifier", vaa.MessageID(), "err", err) 203 | } else { 204 | writeCache() 205 | count++ 206 | } 207 | 208 | } 209 | if count > 0 { 210 | logger.Info(fmt.Sprintf("processed %d transfers from jester", count)) 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /e2e/cctp_receive_message_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "crypto/ecdsa" 23 | "crypto/elliptic" 24 | "encoding/hex" 25 | "sort" 26 | "testing" 27 | "time" 28 | 29 | "cosmossdk.io/math" 30 | cctptypes "github.com/circlefin/noble-cctp/x/cctp/types" 31 | "github.com/cosmos/cosmos-sdk/types/bech32" 32 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 33 | "github.com/ethereum/go-ethereum/common" 34 | "github.com/ethereum/go-ethereum/crypto" 35 | "github.com/noble-assets/noble/e2e" 36 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 37 | "github.com/stretchr/testify/require" 38 | ) 39 | 40 | func TestCCTP_ReceiveMessage(t *testing.T) { 41 | if testing.Short() { 42 | t.Skip() 43 | } 44 | t.Parallel() 45 | 46 | ctx := context.Background() 47 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 48 | noble := nw.Chain 49 | nobleValidator := noble.Validators[0] 50 | 51 | broadcaster := cosmos.NewBroadcaster(t, noble) 52 | 53 | attesters := make([]*ecdsa.PrivateKey, 2) 54 | 55 | // attester - ECDSA public key (Circle will own these keys for mainnet) 56 | for i := range attesters { 57 | p, err := crypto.GenerateKey() // private key 58 | require.NoError(t, err) 59 | 60 | attesters[i] = p 61 | 62 | pubKey := elliptic.Marshal(p.PublicKey, p.PublicKey.X, p.PublicKey.Y) // public key 63 | 64 | attesterPub := hex.EncodeToString(pubKey) 65 | 66 | bCtx, bCancel := context.WithTimeout(ctx, 20*time.Second) 67 | defer bCancel() 68 | 69 | // Adding an attester to protocal 70 | tx, err := cosmos.BroadcastTx( 71 | bCtx, 72 | broadcaster, 73 | nw.CCTPRoles.AttesterManager, 74 | &cctptypes.MsgEnableAttester{ 75 | From: nw.CCTPRoles.AttesterManager.FormattedAddress(), 76 | Attester: attesterPub, 77 | }, 78 | ) 79 | require.NoError(t, err, "error enabling attester") 80 | require.Zero(t, tx.Code, "cctp enable attester transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 81 | } 82 | 83 | burnToken := make([]byte, 32) 84 | copy(burnToken[12:], common.FromHex("0x07865c6E87B9F70255377e024ace6630C1Eaa37F")) 85 | 86 | bCtx, bCancel := context.WithTimeout(ctx, 20*time.Second) 87 | defer bCancel() 88 | 89 | tx, err := cosmos.BroadcastTx( 90 | bCtx, 91 | broadcaster, 92 | nw.CCTPRoles.TokenController, 93 | &cctptypes.MsgLinkTokenPair{ 94 | From: nw.CCTPRoles.TokenController.FormattedAddress(), 95 | RemoteDomain: 0, 96 | RemoteToken: burnToken, 97 | LocalToken: e2e.DenomMetadataUsdc.Base, 98 | }, 99 | ) 100 | require.NoError(t, err, "error linking token pair") 101 | require.Zero(t, tx.Code, "cctp link token pair transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 102 | 103 | tokenMessenger := make([]byte, 32) 104 | copy(tokenMessenger[12:], common.FromHex("0xBd3fa81B58Ba92a82136038B25aDec7066af3155")) 105 | 106 | bCtx, bCancel = context.WithTimeout(ctx, 20*time.Second) 107 | defer bCancel() 108 | 109 | tx, err = cosmos.BroadcastTx( 110 | bCtx, 111 | broadcaster, 112 | nw.CCTPRoles.Owner, 113 | &cctptypes.MsgAddRemoteTokenMessenger{ 114 | From: nw.CCTPRoles.Owner.FormattedAddress(), 115 | DomainId: 0, 116 | Address: tokenMessenger, 117 | }, 118 | ) 119 | require.NoError(t, err, "error adding remote token messenger") 120 | require.Zero(t, tx.Code, "cctp adding remote token messenger transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 121 | 122 | _, bCancel = context.WithTimeout(ctx, 20*time.Second) 123 | defer bCancel() 124 | 125 | cctpModuleAccount := authtypes.NewModuleAddress(cctptypes.ModuleName).String() 126 | 127 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MasterMinter.KeyName(), 128 | "fiat-tokenfactory", "configure-minter-controller", nw.FiatTfRoles.MinterController.FormattedAddress(), cctpModuleAccount, 129 | ) 130 | require.NoError(t, err, "failed to execute configure minter controller tx") 131 | 132 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MinterController.KeyName(), 133 | "fiat-tokenfactory", "configure-minter", cctpModuleAccount, "1000000"+e2e.DenomMetadataUsdc.Base, 134 | ) 135 | require.NoError(t, err, "failed to execute configure minter tx") 136 | 137 | const receiver = "9B6CA0C13EB603EF207C4657E1E619EF531A4D27" // account 138 | 139 | receiverBz, err := hex.DecodeString(receiver) 140 | require.NoError(t, err) 141 | 142 | nobleReceiver, err := bech32.ConvertAndEncode(nw.Chain.Config().Bech32Prefix, receiverBz) 143 | require.NoError(t, err) 144 | 145 | burnRecipientPadded := append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, receiverBz...) 146 | 147 | // someone burned USDC on Ethereum -> Mint on Noble 148 | depositForBurn := cctptypes.BurnMessage{ 149 | BurnToken: burnToken, 150 | MintRecipient: burnRecipientPadded, 151 | Amount: math.NewInt(1000000), 152 | MessageSender: burnRecipientPadded, 153 | } 154 | 155 | depositForBurnBz, err := depositForBurn.Bytes() 156 | require.NoError(t, err) 157 | 158 | emptyDestinationCaller := make([]byte, 32) 159 | 160 | wrappedDepositForBurn := cctptypes.Message{ 161 | Version: 0, 162 | SourceDomain: 0, 163 | DestinationDomain: 4, // Noble is 4 164 | Nonce: 0, // dif per message 165 | Sender: tokenMessenger, 166 | Recipient: cctptypes.PaddedModuleAddress, 167 | DestinationCaller: emptyDestinationCaller, 168 | MessageBody: depositForBurnBz, 169 | } 170 | 171 | wrappedDepositForBurnBz, err := wrappedDepositForBurn.Bytes() 172 | require.NoError(t, err) 173 | 174 | digestBurn := crypto.Keccak256(wrappedDepositForBurnBz) // hashed message is the key to the attestation 175 | 176 | attestationBurn := make([]byte, 0, len(attesters)*65) // 65 byte 177 | 178 | // CCTP requires attestations to have signatures sorted by address 179 | sort.Slice(attesters, func(i, j int) bool { 180 | return bytes.Compare( 181 | crypto.PubkeyToAddress(attesters[i].PublicKey).Bytes(), 182 | crypto.PubkeyToAddress(attesters[j].PublicKey).Bytes(), 183 | ) < 0 184 | }) 185 | 186 | for i := range attesters { 187 | sig, err := crypto.Sign(digestBurn, attesters[i]) 188 | require.NoError(t, err) 189 | 190 | attestationBurn = append(attestationBurn, sig...) 191 | } 192 | 193 | t.Logf("Attested to messages: %s", tx.TxHash) 194 | 195 | bCtx, bCancel = context.WithTimeout(ctx, 20*time.Second) 196 | defer bCancel() 197 | tx, err = cosmos.BroadcastTx( 198 | bCtx, 199 | broadcaster, 200 | nw.CCTPRoles.Owner, 201 | &cctptypes.MsgReceiveMessage{ // note: all messages that go to noble go through MsgReceiveMessage 202 | From: nw.CCTPRoles.Owner.FormattedAddress(), 203 | Message: wrappedDepositForBurnBz, 204 | Attestation: attestationBurn, 205 | }, 206 | ) 207 | require.NoError(t, err, "error submitting cctp burn recv tx") 208 | require.Zerof(t, tx.Code, "cctp burn recv transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 209 | 210 | t.Logf("CCTP burn message successfully received: %s", tx.TxHash) 211 | 212 | balance, err := noble.GetBalance(ctx, nobleReceiver, e2e.DenomMetadataUsdc.Base) 213 | require.NoError(t, err) 214 | 215 | require.Equal(t, math.NewInt(1000000), balance) 216 | } 217 | -------------------------------------------------------------------------------- /e2e/cctp_receive_message_with_caller_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "crypto/ecdsa" 23 | "crypto/elliptic" 24 | "encoding/hex" 25 | "sort" 26 | "testing" 27 | "time" 28 | 29 | "cosmossdk.io/math" 30 | cctptypes "github.com/circlefin/noble-cctp/x/cctp/types" 31 | "github.com/cosmos/cosmos-sdk/types/bech32" 32 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 33 | "github.com/ethereum/go-ethereum/common" 34 | "github.com/ethereum/go-ethereum/crypto" 35 | "github.com/noble-assets/noble/e2e" 36 | "github.com/strangelove-ventures/interchaintest/v8" 37 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 38 | "github.com/stretchr/testify/require" 39 | ) 40 | 41 | func TestCCTP_ReceiveMessageWithCaller(t *testing.T) { 42 | if testing.Short() { 43 | t.Skip() 44 | } 45 | t.Parallel() 46 | 47 | ctx := context.Background() 48 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 49 | noble := nw.Chain 50 | nobleValidator := noble.Validators[0] 51 | 52 | broadcaster := cosmos.NewBroadcaster(t, noble) 53 | 54 | attesters := make([]*ecdsa.PrivateKey, 2) 55 | 56 | // attester - ECDSA public key (Circle will own these keys for mainnet) 57 | for i := range attesters { 58 | p, err := crypto.GenerateKey() // private key 59 | require.NoError(t, err) 60 | 61 | attesters[i] = p 62 | 63 | pubKey := elliptic.Marshal(p.PublicKey, p.PublicKey.X, p.PublicKey.Y) // public key 64 | 65 | attesterPub := hex.EncodeToString(pubKey) 66 | 67 | bCtx, bCancel := context.WithTimeout(ctx, 20*time.Second) 68 | defer bCancel() 69 | 70 | // Adding an attester to protocal 71 | tx, err := cosmos.BroadcastTx( 72 | bCtx, 73 | broadcaster, 74 | nw.CCTPRoles.AttesterManager, 75 | &cctptypes.MsgEnableAttester{ 76 | From: nw.CCTPRoles.AttesterManager.FormattedAddress(), 77 | Attester: attesterPub, 78 | }, 79 | ) 80 | require.NoError(t, err, "error enabling attester") 81 | require.Zero(t, tx.Code, "cctp enable attester transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 82 | } 83 | 84 | burnToken := make([]byte, 32) 85 | copy(burnToken[12:], common.FromHex("0x07865c6E87B9F70255377e024ace6630C1Eaa37F")) 86 | 87 | bCtx, bCancel := context.WithTimeout(ctx, 20*time.Second) 88 | defer bCancel() 89 | 90 | tx, err := cosmos.BroadcastTx( 91 | bCtx, 92 | broadcaster, 93 | nw.CCTPRoles.TokenController, 94 | &cctptypes.MsgLinkTokenPair{ 95 | From: nw.CCTPRoles.TokenController.FormattedAddress(), 96 | RemoteDomain: 0, 97 | RemoteToken: burnToken, 98 | LocalToken: e2e.DenomMetadataUsdc.Base, 99 | }, 100 | ) 101 | require.NoError(t, err, "error linking token pair") 102 | require.Zero(t, tx.Code, "cctp link token pair transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 103 | 104 | tokenMessenger := make([]byte, 32) 105 | copy(tokenMessenger[12:], common.FromHex("0xBd3fa81B58Ba92a82136038B25aDec7066af3155")) 106 | 107 | bCtx, bCancel = context.WithTimeout(ctx, 20*time.Second) 108 | defer bCancel() 109 | 110 | tx, err = cosmos.BroadcastTx( 111 | bCtx, 112 | broadcaster, 113 | nw.CCTPRoles.Owner, 114 | &cctptypes.MsgAddRemoteTokenMessenger{ 115 | From: nw.CCTPRoles.Owner.FormattedAddress(), 116 | DomainId: 0, 117 | Address: tokenMessenger, 118 | }, 119 | ) 120 | require.NoError(t, err, "error adding remote token messenger") 121 | require.Zero(t, tx.Code, "cctp adding remote token messenger transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 122 | 123 | _, bCancel = context.WithTimeout(ctx, 20*time.Second) 124 | defer bCancel() 125 | 126 | cctpModuleAccount := authtypes.NewModuleAddress(cctptypes.ModuleName).String() 127 | 128 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MasterMinter.KeyName(), 129 | "fiat-tokenfactory", "configure-minter-controller", nw.FiatTfRoles.MinterController.FormattedAddress(), cctpModuleAccount, 130 | ) 131 | require.NoError(t, err, "failed to execute configure minter controller tx") 132 | 133 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MinterController.KeyName(), 134 | "fiat-tokenfactory", "configure-minter", cctpModuleAccount, "1000000"+e2e.DenomMetadataUsdc.Base, 135 | ) 136 | require.NoError(t, err, "failed to execute configure minter tx") 137 | 138 | const receiver = "9B6CA0C13EB603EF207C4657E1E619EF531A4D27" // account 139 | 140 | receiverBz, err := hex.DecodeString(receiver) 141 | require.NoError(t, err) 142 | 143 | nobleReceiver, err := bech32.ConvertAndEncode(nw.Chain.Config().Bech32Prefix, receiverBz) 144 | require.NoError(t, err) 145 | 146 | burnRecipientPadded := append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, receiverBz...) 147 | 148 | // someone burned USDC on Ethereum -> Mint on Noble 149 | depositForBurn := cctptypes.BurnMessage{ 150 | BurnToken: burnToken, 151 | MintRecipient: burnRecipientPadded, 152 | Amount: math.NewInt(1000000), 153 | MessageSender: burnRecipientPadded, 154 | } 155 | 156 | depositForBurnBz, err := depositForBurn.Bytes() 157 | require.NoError(t, err) 158 | 159 | relayer := interchaintest.GetAndFundTestUsers(t, ctx, "relayer", math.OneInt(), noble)[0] 160 | 161 | destinationCaller := make([]byte, 32) 162 | copy(destinationCaller[12:], relayer.Address()) 163 | 164 | wrappedDepositForBurn := cctptypes.Message{ 165 | Version: 0, 166 | SourceDomain: 0, 167 | DestinationDomain: 4, // Noble is 4 168 | Nonce: 0, // dif per message 169 | Sender: tokenMessenger, 170 | Recipient: cctptypes.PaddedModuleAddress, 171 | DestinationCaller: destinationCaller, 172 | MessageBody: depositForBurnBz, 173 | } 174 | 175 | wrappedDepositForBurnBz, err := wrappedDepositForBurn.Bytes() 176 | require.NoError(t, err) 177 | 178 | digestBurn := crypto.Keccak256(wrappedDepositForBurnBz) // hashed message is the key to the attestation 179 | 180 | attestationBurn := make([]byte, 0, len(attesters)*65) // 65 byte 181 | 182 | // CCTP requires attestations to have signatures sorted by address 183 | sort.Slice(attesters, func(i, j int) bool { 184 | return bytes.Compare( 185 | crypto.PubkeyToAddress(attesters[i].PublicKey).Bytes(), 186 | crypto.PubkeyToAddress(attesters[j].PublicKey).Bytes(), 187 | ) < 0 188 | }) 189 | 190 | for i := range attesters { 191 | sig, err := crypto.Sign(digestBurn, attesters[i]) 192 | require.NoError(t, err) 193 | 194 | attestationBurn = append(attestationBurn, sig...) 195 | } 196 | 197 | t.Logf("Attested to messages: %s", tx.TxHash) 198 | 199 | bCtx, bCancel = context.WithTimeout(ctx, 20*time.Second) 200 | defer bCancel() 201 | 202 | tx, err = cosmos.BroadcastTx( 203 | bCtx, 204 | broadcaster, 205 | relayer, 206 | &cctptypes.MsgReceiveMessage{ // note: all messages that go to noble go through MsgReceiveMessage 207 | From: relayer.FormattedAddress(), 208 | Message: wrappedDepositForBurnBz, 209 | Attestation: attestationBurn, 210 | }, 211 | ) 212 | require.NoError(t, err, "error submitting cctp burn recv tx") 213 | require.Zerof(t, tx.Code, "cctp burn recv transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 214 | 215 | t.Logf("CCTP burn message successfully received: %s", tx.TxHash) 216 | 217 | balance, err := noble.GetBalance(ctx, nobleReceiver, e2e.DenomMetadataUsdc.Base) 218 | require.NoError(t, err) 219 | 220 | require.Equal(t, math.NewInt(1000000), balance) 221 | } 222 | -------------------------------------------------------------------------------- /legacy.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package noble 18 | 19 | import ( 20 | storetypes "cosmossdk.io/store/types" 21 | "dollar.noble.xyz/v2" 22 | "github.com/circlefin/noble-fiattokenfactory/x/blockibc" 23 | "github.com/cosmos/cosmos-sdk/runtime" 24 | pfm "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8/packetforward" 25 | pfmkeeper "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8/packetforward/keeper" 26 | pfmtypes "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8/packetforward/types" 27 | ratelimit "github.com/cosmos/ibc-apps/modules/rate-limiting/v8" 28 | ratelimitkeeper "github.com/cosmos/ibc-apps/modules/rate-limiting/v8/keeper" 29 | ratelimittypes "github.com/cosmos/ibc-apps/modules/rate-limiting/v8/types" 30 | "github.com/cosmos/ibc-go/modules/capability" 31 | capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" 32 | capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" 33 | ica "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts" 34 | icahost "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host" 35 | icahostkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/keeper" 36 | icahosttypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/types" 37 | "github.com/cosmos/ibc-go/v8/modules/apps/transfer" 38 | transferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper" 39 | transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" 40 | ibc "github.com/cosmos/ibc-go/v8/modules/core" 41 | clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" 42 | connectiontypes "github.com/cosmos/ibc-go/v8/modules/core/03-connection/types" 43 | porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" 44 | ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" 45 | ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper" 46 | soloclient "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine" 47 | tmclient "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" 48 | authoritytypes "github.com/noble-assets/authority/types" 49 | "github.com/noble-assets/forwarding/v2" 50 | orbiter "github.com/noble-assets/orbiter/v2/entrypoint" 51 | "github.com/noble-assets/wormhole" 52 | wormholetypes "github.com/noble-assets/wormhole/types" 53 | ) 54 | 55 | func (app *App) RegisterLegacyModules() error { 56 | if err := app.RegisterStores( 57 | storetypes.NewKVStoreKey(capabilitytypes.StoreKey), 58 | storetypes.NewMemoryStoreKey(capabilitytypes.MemStoreKey), 59 | storetypes.NewKVStoreKey(ibcexported.StoreKey), 60 | storetypes.NewKVStoreKey(icahosttypes.StoreKey), 61 | storetypes.NewKVStoreKey(pfmtypes.StoreKey), 62 | storetypes.NewKVStoreKey(ratelimittypes.StoreKey), 63 | storetypes.NewKVStoreKey(transfertypes.StoreKey), 64 | ); err != nil { 65 | return err 66 | } 67 | 68 | app.ParamsKeeper.Subspace(ibcexported.ModuleName).WithKeyTable(clienttypes.ParamKeyTable().RegisterParamSet(&connectiontypes.Params{})) 69 | app.ParamsKeeper.Subspace(icahosttypes.SubModuleName).WithKeyTable(icahosttypes.ParamKeyTable()) 70 | app.ParamsKeeper.Subspace(ratelimittypes.ModuleName).WithKeyTable(ratelimittypes.ParamKeyTable()) 71 | app.ParamsKeeper.Subspace(transfertypes.ModuleName).WithKeyTable(transfertypes.ParamKeyTable()) 72 | 73 | app.CapabilityKeeper = capabilitykeeper.NewKeeper( 74 | app.appCodec, 75 | app.GetKey(capabilitytypes.StoreKey), 76 | app.GetMemKey(capabilitytypes.MemStoreKey), 77 | ) 78 | 79 | scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) 80 | app.IBCKeeper = ibckeeper.NewKeeper( 81 | app.appCodec, 82 | app.GetKey(ibcexported.StoreKey), 83 | app.GetSubspace(ibcexported.ModuleName), 84 | app.StakingKeeper, 85 | app.UpgradeKeeper, 86 | scopedIBCKeeper, 87 | authoritytypes.ModuleAddress.String(), 88 | ) 89 | 90 | scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) 91 | app.ICAHostKeeper = icahostkeeper.NewKeeper( 92 | app.appCodec, 93 | app.GetKey(icahosttypes.StoreKey), 94 | app.GetSubspace(icahosttypes.SubModuleName), 95 | app.IBCKeeper.ChannelKeeper, 96 | app.IBCKeeper.ChannelKeeper, 97 | app.IBCKeeper.PortKeeper, 98 | app.AccountKeeper, 99 | scopedICAHostKeeper, 100 | app.MsgServiceRouter(), 101 | authoritytypes.ModuleAddress.String(), 102 | ) 103 | app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) 104 | 105 | // Create custom ICS4Wrapper so that we can block outgoing $USDN IBC transfers. 106 | ics4Wrapper := dollar.NewICS4Wrapper(app.IBCKeeper.ChannelKeeper, app.DollarKeeper) 107 | 108 | app.RateLimitKeeper = *ratelimitkeeper.NewKeeper( 109 | app.appCodec, 110 | runtime.NewKVStoreService(app.GetKey(ratelimittypes.StoreKey)), 111 | app.GetSubspace(ratelimittypes.ModuleName), 112 | authoritytypes.ModuleAddress.String(), 113 | app.BankKeeper, 114 | app.IBCKeeper.ChannelKeeper, 115 | ics4Wrapper, 116 | ) 117 | 118 | scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(transfertypes.ModuleName) 119 | app.TransferKeeper = transferkeeper.NewKeeper( 120 | app.appCodec, 121 | app.GetKey(transfertypes.StoreKey), 122 | app.GetSubspace(transfertypes.ModuleName), 123 | app.RateLimitKeeper, 124 | app.IBCKeeper.ChannelKeeper, 125 | app.IBCKeeper.PortKeeper, 126 | app.AccountKeeper, 127 | app.BankKeeper, 128 | scopedTransferKeeper, 129 | authoritytypes.ModuleAddress.String(), 130 | ) 131 | app.PFMKeeper = pfmkeeper.NewKeeper( 132 | app.appCodec, 133 | app.GetKey(pfmtypes.StoreKey), 134 | app.TransferKeeper, 135 | app.IBCKeeper.ChannelKeeper, 136 | app.BankKeeper, 137 | app.IBCKeeper.ChannelKeeper, 138 | authoritytypes.ModuleAddress.String(), 139 | ) 140 | 141 | var transferStack porttypes.IBCModule 142 | transferStack = transfer.NewIBCModule(app.TransferKeeper) 143 | transferStack = ratelimit.NewIBCMiddleware(app.RateLimitKeeper, transferStack) 144 | transferStack = forwarding.NewMiddleware(transferStack, app.AccountKeeper, app.ForwardingKeeper) 145 | transferStack = orbiter.NewIBCMiddleware( 146 | transferStack, 147 | app.IBCKeeper.ChannelKeeper, 148 | app.OrbiterKeeper.Adapter(), 149 | ) 150 | transferStack = pfm.NewIBCMiddleware( 151 | transferStack, 152 | app.PFMKeeper, 153 | 0, 154 | pfmkeeper.DefaultForwardTransferPacketTimeoutTimestamp, 155 | ) 156 | transferStack = blockibc.NewIBCMiddleware(transferStack, app.FTFKeeper) 157 | transferStack = dollar.NewIBCModule(transferStack, app.DollarKeeper) 158 | 159 | ibcRouter := porttypes.NewRouter(). 160 | AddRoute(icahosttypes.SubModuleName, icahost.NewIBCModule(app.ICAHostKeeper)). 161 | AddRoute(transfertypes.ModuleName, transferStack). 162 | AddRoute(wormholetypes.ModuleName, wormhole.NewIBCModule(app.WormholeKeeper)) 163 | app.IBCKeeper.SetRouter(ibcRouter) 164 | 165 | app.DollarKeeper.SetIBCKeepers(app.IBCKeeper.ChannelKeeper, app.TransferKeeper) 166 | 167 | app.ForwardingKeeper.SetIBCKeepers(app.IBCKeeper.ChannelKeeper, app.TransferKeeper) 168 | 169 | scopedWormholeKeeper := app.CapabilityKeeper.ScopeToModule(wormholetypes.ModuleName) 170 | app.WormholeKeeper.SetIBCKeepers(app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, scopedWormholeKeeper) 171 | 172 | return app.RegisterModules( 173 | capability.NewAppModule(app.appCodec, *app.CapabilityKeeper, true), 174 | ibc.NewAppModule(app.IBCKeeper), 175 | ica.NewAppModule(nil, &app.ICAHostKeeper), 176 | pfm.NewAppModule(app.PFMKeeper, app.GetSubspace(pfmtypes.ModuleName)), 177 | transfer.NewAppModule(app.TransferKeeper), 178 | tmclient.NewAppModule(), 179 | soloclient.NewAppModule(), 180 | ratelimit.NewAppModule(app.appCodec, app.RateLimitKeeper), 181 | ) 182 | } 183 | -------------------------------------------------------------------------------- /e2e/cctp_deposit_for_burn_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "context" 21 | "encoding/hex" 22 | "testing" 23 | "time" 24 | 25 | "cosmossdk.io/math" 26 | cctptypes "github.com/circlefin/noble-cctp/x/cctp/types" 27 | sdk "github.com/cosmos/cosmos-sdk/types" 28 | "github.com/ethereum/go-ethereum/common" 29 | "github.com/ethereum/go-ethereum/crypto" 30 | "github.com/noble-assets/noble/e2e" 31 | "github.com/strangelove-ventures/interchaintest/v8" 32 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 33 | "github.com/stretchr/testify/require" 34 | ) 35 | 36 | func TestCCTP_DepositForBurn(t *testing.T) { 37 | if testing.Short() { 38 | t.Skip() 39 | } 40 | t.Parallel() 41 | 42 | ctx := context.Background() 43 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 44 | noble := nw.Chain 45 | nobleValidator := noble.Validators[0] 46 | 47 | // SET UP FIAT TOKEN FACTORY AND MINT 48 | 49 | user := interchaintest.GetAndFundTestUsers(t, ctx, "wallet", math.OneInt(), noble)[0] 50 | 51 | _, err := nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MasterMinter.KeyName(), 52 | "fiat-tokenfactory", "configure-minter-controller", nw.FiatTfRoles.MinterController.FormattedAddress(), nw.FiatTfRoles.Minter.FormattedAddress(), 53 | ) 54 | require.NoError(t, err, "failed to execute configure minter controller tx") 55 | 56 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MinterController.KeyName(), 57 | "fiat-tokenfactory", "configure-minter", nw.FiatTfRoles.Minter.FormattedAddress(), "1000000000000"+e2e.DenomMetadataUsdc.Base, 58 | ) 59 | require.NoError(t, err, "failed to execute configure minter tx") 60 | 61 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.Minter.KeyName(), 62 | "fiat-tokenfactory", "mint", user.FormattedAddress(), "1000000000000"+e2e.DenomMetadataUsdc.Base, 63 | ) 64 | require.NoError(t, err, "failed to execute mint to user tx") 65 | 66 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MasterMinter.KeyName(), 67 | "fiat-tokenfactory", "configure-minter-controller", nw.FiatTfRoles.MinterController.FormattedAddress(), cctptypes.ModuleAddress.String(), 68 | ) 69 | require.NoError(t, err, "failed to configure cctp minter controller") 70 | 71 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MinterController.KeyName(), 72 | "fiat-tokenfactory", "configure-minter", cctptypes.ModuleAddress.String(), "1000000000000"+e2e.DenomMetadataUsdc.Base, 73 | ) 74 | require.NoError(t, err, "failed to configure cctp minter") 75 | 76 | // ---- 77 | 78 | broadcaster := cosmos.NewBroadcaster(t, noble) 79 | 80 | burnToken := make([]byte, 32) 81 | copy(burnToken[12:], common.FromHex("0x07865c6E87B9F70255377e024ace6630C1Eaa37F")) 82 | 83 | tokenMessenger := make([]byte, 32) 84 | copy(tokenMessenger[12:], common.FromHex("0xD0C3da58f55358142b8d3e06C1C30c5C6114EFE8")) 85 | 86 | bCtx, bCancel := context.WithTimeout(ctx, 20*time.Second) 87 | defer bCancel() 88 | 89 | tx, err := cosmos.BroadcastTx( 90 | bCtx, 91 | broadcaster, 92 | nw.CCTPRoles.Owner, 93 | &cctptypes.MsgAddRemoteTokenMessenger{ 94 | From: nw.CCTPRoles.Owner.FormattedAddress(), 95 | DomainId: 0, 96 | Address: tokenMessenger, 97 | }, 98 | ) 99 | require.NoError(t, err, "error adding remote token messenger") 100 | require.Zero(t, tx.Code, "adding remote token messenger failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 101 | 102 | bCtx, bCancel = context.WithTimeout(ctx, 20*time.Second) 103 | defer bCancel() 104 | 105 | tx, err = cosmos.BroadcastTx( 106 | bCtx, 107 | broadcaster, 108 | nw.CCTPRoles.TokenController, 109 | &cctptypes.MsgLinkTokenPair{ 110 | From: nw.CCTPRoles.TokenController.FormattedAddress(), 111 | RemoteDomain: 0, 112 | RemoteToken: burnToken, 113 | LocalToken: e2e.DenomMetadataUsdc.Base, 114 | }, 115 | ) 116 | require.NoError(t, err, "error linking token pair") 117 | require.Zero(t, tx.Code, "linking token pair failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 118 | 119 | beforeBurnBal, err := noble.GetBalance(ctx, user.FormattedAddress(), e2e.DenomMetadataUsdc.Base) 120 | require.NoError(t, err) 121 | 122 | mintRecipient := make([]byte, 32) 123 | copy(mintRecipient[12:], common.FromHex("0xfCE4cE85e1F74C01e0ecccd8BbC4606f83D3FC90")) 124 | 125 | msgDepositForBurnNoble := &cctptypes.MsgDepositForBurn{ 126 | From: user.FormattedAddress(), 127 | Amount: math.NewInt(1000000), 128 | BurnToken: e2e.DenomMetadataUsdc.Base, 129 | DestinationDomain: 0, 130 | MintRecipient: mintRecipient, 131 | } 132 | 133 | bCtx, bCancel = context.WithTimeout(ctx, 20*time.Second) 134 | defer bCancel() 135 | 136 | tx, err = cosmos.BroadcastTx( 137 | bCtx, 138 | broadcaster, 139 | user, 140 | msgDepositForBurnNoble, 141 | ) 142 | require.NoError(t, err, "error broadcasting msgDepositForBurn") 143 | require.Zero(t, tx.Code, "msgDepositForBurn failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 144 | 145 | afterBurnBal, err := noble.GetBalance(ctx, user.FormattedAddress(), e2e.DenomMetadataUsdc.Base) 146 | require.NoError(t, err) 147 | 148 | require.Equal(t, afterBurnBal, beforeBurnBal.Sub(math.NewInt(1000000))) 149 | for _, rawEvent := range tx.Events { 150 | switch rawEvent.Type { 151 | case "circle.cctp.v1.DepositForBurn": 152 | parsedEvent, err := sdk.ParseTypedEvent(rawEvent) 153 | require.NoError(t, err) 154 | depositForBurn, ok := parsedEvent.(*cctptypes.DepositForBurn) 155 | require.True(t, ok) 156 | 157 | expectedBurnToken := hex.EncodeToString(crypto.Keccak256([]byte(e2e.DenomMetadataUsdc.Base))) 158 | 159 | require.Equal(t, uint64(0), depositForBurn.Nonce) 160 | require.Equal(t, expectedBurnToken, depositForBurn.BurnToken) 161 | require.Equal(t, msgDepositForBurnNoble.Amount, depositForBurn.Amount) 162 | require.Equal(t, user.FormattedAddress(), depositForBurn.Depositor) 163 | require.Equal(t, mintRecipient, depositForBurn.MintRecipient) 164 | require.Equal(t, uint32(0), depositForBurn.DestinationDomain) 165 | require.Equal(t, tokenMessenger, depositForBurn.DestinationTokenMessenger) 166 | require.Equal(t, []byte{}, depositForBurn.DestinationCaller) 167 | 168 | case "circle.cctp.v1.MessageSent": 169 | parsedEvent, err := sdk.ParseTypedEvent(rawEvent) 170 | require.NoError(t, err) 171 | event, ok := parsedEvent.(*cctptypes.MessageSent) 172 | require.True(t, ok) 173 | 174 | message, err := new(cctptypes.Message).Parse(event.Message) 175 | require.NoError(t, err) 176 | 177 | messageSender := make([]byte, 32) 178 | copy(messageSender[12:], sdk.MustAccAddressFromBech32(cctptypes.ModuleAddress.String())) 179 | 180 | expectedBurnToken := crypto.Keccak256([]byte(msgDepositForBurnNoble.BurnToken)) 181 | 182 | moduleAddress := make([]byte, 32) 183 | copy(moduleAddress[12:], sdk.MustAccAddressFromBech32(user.FormattedAddress())) 184 | 185 | destinationCaller := make([]byte, 32) 186 | 187 | require.Equal(t, uint32(0), message.Version) 188 | require.Equal(t, uint32(4), message.SourceDomain) 189 | require.Equal(t, uint32(0), message.DestinationDomain) 190 | require.Equal(t, uint64(0), message.Nonce) 191 | require.Equal(t, messageSender, message.Sender) 192 | require.Equal(t, tokenMessenger, message.Recipient) 193 | require.Equal(t, destinationCaller, message.DestinationCaller) 194 | 195 | body, err := new(cctptypes.BurnMessage).Parse(message.MessageBody) 196 | require.NoError(t, err) 197 | 198 | require.Equal(t, uint32(0), body.Version) 199 | require.Equal(t, mintRecipient, body.MintRecipient) 200 | require.Equal(t, msgDepositForBurnNoble.Amount, body.Amount) 201 | require.Equal(t, expectedBurnToken, body.BurnToken) 202 | require.Equal(t, moduleAddress, body.MessageSender) 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /e2e/cctp_deposit_for_burn_with_caller_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "context" 21 | "encoding/hex" 22 | "testing" 23 | "time" 24 | 25 | "cosmossdk.io/math" 26 | cctptypes "github.com/circlefin/noble-cctp/x/cctp/types" 27 | sdk "github.com/cosmos/cosmos-sdk/types" 28 | "github.com/ethereum/go-ethereum/common" 29 | "github.com/ethereum/go-ethereum/crypto" 30 | "github.com/noble-assets/noble/e2e" 31 | "github.com/strangelove-ventures/interchaintest/v8" 32 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 33 | "github.com/stretchr/testify/require" 34 | ) 35 | 36 | func TestCCTP_DepositForBurnWithCaller(t *testing.T) { 37 | if testing.Short() { 38 | t.Skip() 39 | } 40 | t.Parallel() 41 | 42 | ctx := context.Background() 43 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 44 | noble := nw.Chain 45 | nobleValidator := noble.Validators[0] 46 | 47 | // SET UP FIAT TOKEN FACTORY AND MINT 48 | 49 | user := interchaintest.GetAndFundTestUsers(t, ctx, "wallet", math.OneInt(), noble)[0] 50 | 51 | _, err := nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MasterMinter.KeyName(), 52 | "fiat-tokenfactory", "configure-minter-controller", nw.FiatTfRoles.MinterController.FormattedAddress(), nw.FiatTfRoles.Minter.FormattedAddress(), 53 | ) 54 | require.NoError(t, err, "failed to execute configure minter controller tx") 55 | 56 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MinterController.KeyName(), 57 | "fiat-tokenfactory", "configure-minter", nw.FiatTfRoles.Minter.FormattedAddress(), "1000000000000"+e2e.DenomMetadataUsdc.Base, 58 | ) 59 | require.NoError(t, err, "failed to execute configure minter tx") 60 | 61 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.Minter.KeyName(), 62 | "fiat-tokenfactory", "mint", user.FormattedAddress(), "1000000000000"+e2e.DenomMetadataUsdc.Base, 63 | ) 64 | require.NoError(t, err, "failed to execute mint to user tx") 65 | 66 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MasterMinter.KeyName(), 67 | "fiat-tokenfactory", "configure-minter-controller", nw.FiatTfRoles.MinterController.FormattedAddress(), cctptypes.ModuleAddress.String(), 68 | ) 69 | require.NoError(t, err, "failed to configure cctp minter controller") 70 | 71 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MinterController.KeyName(), 72 | "fiat-tokenfactory", "configure-minter", cctptypes.ModuleAddress.String(), "1000000000000"+e2e.DenomMetadataUsdc.Base, 73 | ) 74 | require.NoError(t, err, "failed to configure cctp minter") 75 | 76 | // ---- 77 | 78 | broadcaster := cosmos.NewBroadcaster(t, noble) 79 | 80 | burnToken := make([]byte, 32) 81 | copy(burnToken[12:], common.FromHex("0x07865c6E87B9F70255377e024ace6630C1Eaa37F")) 82 | 83 | tokenMessenger := make([]byte, 32) 84 | copy(tokenMessenger[12:], common.FromHex("0xD0C3da58f55358142b8d3e06C1C30c5C6114EFE8")) 85 | 86 | bCtx, bCancel := context.WithTimeout(ctx, 20*time.Second) 87 | defer bCancel() 88 | 89 | tx, err := cosmos.BroadcastTx( 90 | bCtx, 91 | broadcaster, 92 | nw.CCTPRoles.Owner, 93 | &cctptypes.MsgAddRemoteTokenMessenger{ 94 | From: nw.CCTPRoles.Owner.FormattedAddress(), 95 | DomainId: 0, 96 | Address: tokenMessenger, 97 | }, 98 | ) 99 | require.NoError(t, err, "error adding remote token messenger") 100 | require.Zero(t, tx.Code, "adding remote token messenger failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 101 | 102 | bCtx, bCancel = context.WithTimeout(ctx, 20*time.Second) 103 | defer bCancel() 104 | 105 | tx, err = cosmos.BroadcastTx( 106 | bCtx, 107 | broadcaster, 108 | nw.CCTPRoles.TokenController, 109 | &cctptypes.MsgLinkTokenPair{ 110 | From: nw.CCTPRoles.TokenController.FormattedAddress(), 111 | RemoteDomain: 0, 112 | RemoteToken: burnToken, 113 | LocalToken: e2e.DenomMetadataUsdc.Base, 114 | }, 115 | ) 116 | require.NoError(t, err, "error linking token pair") 117 | require.Zero(t, tx.Code, "linking token pair failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 118 | 119 | beforeBurnBal, err := noble.GetBalance(ctx, user.FormattedAddress(), e2e.DenomMetadataUsdc.Base) 120 | require.NoError(t, err) 121 | 122 | mintRecipient := make([]byte, 32) 123 | copy(mintRecipient[12:], common.FromHex("0xfCE4cE85e1F74C01e0ecccd8BbC4606f83D3FC90")) 124 | 125 | destinationCaller := []byte("12345678901234567890123456789012") 126 | 127 | msgDepositForBurnWithCallerNoble := &cctptypes.MsgDepositForBurnWithCaller{ 128 | From: user.FormattedAddress(), 129 | Amount: math.NewInt(1000000), 130 | BurnToken: e2e.DenomMetadataUsdc.Base, 131 | DestinationDomain: 0, 132 | MintRecipient: mintRecipient, 133 | DestinationCaller: destinationCaller, 134 | } 135 | 136 | tx, err = cosmos.BroadcastTx( 137 | bCtx, 138 | broadcaster, 139 | user, 140 | msgDepositForBurnWithCallerNoble, 141 | ) 142 | require.NoError(t, err, "error broadcasting msgDepositForBurnWithCaller") 143 | require.Zero(t, tx.Code, "msgDepositForBurnWithCaller failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 144 | 145 | afterBurnBal, err := noble.GetBalance(ctx, user.FormattedAddress(), e2e.DenomMetadataUsdc.Base) 146 | require.NoError(t, err) 147 | 148 | require.Equal(t, afterBurnBal, beforeBurnBal.Sub(math.NewInt(1000000))) 149 | 150 | for _, rawEvent := range tx.Events { 151 | switch rawEvent.Type { 152 | case "circle.cctp.v1.DepositForBurn": 153 | parsedEvent, err := sdk.ParseTypedEvent(rawEvent) 154 | require.NoError(t, err) 155 | depositForBurn, ok := parsedEvent.(*cctptypes.DepositForBurn) 156 | require.True(t, ok) 157 | 158 | expectedBurnToken := hex.EncodeToString(crypto.Keccak256([]byte(e2e.DenomMetadataUsdc.Base))) 159 | 160 | require.Equal(t, uint64(0), depositForBurn.Nonce) 161 | require.Equal(t, expectedBurnToken, depositForBurn.BurnToken) 162 | require.Equal(t, msgDepositForBurnWithCallerNoble.Amount, depositForBurn.Amount) 163 | require.Equal(t, user.FormattedAddress(), depositForBurn.Depositor) 164 | require.Equal(t, mintRecipient, depositForBurn.MintRecipient) 165 | require.Equal(t, uint32(0), depositForBurn.DestinationDomain) 166 | require.Equal(t, tokenMessenger, depositForBurn.DestinationTokenMessenger) 167 | require.Equal(t, destinationCaller, depositForBurn.DestinationCaller) 168 | 169 | case "circle.cctp.v1.MessageSent": 170 | parsedEvent, err := sdk.ParseTypedEvent(rawEvent) 171 | require.NoError(t, err) 172 | event, ok := parsedEvent.(*cctptypes.MessageSent) 173 | require.True(t, ok) 174 | 175 | message, err := new(cctptypes.Message).Parse(event.Message) 176 | require.NoError(t, err) 177 | 178 | messageSender := make([]byte, 32) 179 | copy(messageSender[12:], sdk.MustAccAddressFromBech32(cctptypes.ModuleAddress.String())) 180 | 181 | expectedBurnToken := crypto.Keccak256([]byte(msgDepositForBurnWithCallerNoble.BurnToken)) 182 | 183 | moduleAddress := make([]byte, 32) 184 | copy(moduleAddress[12:], sdk.MustAccAddressFromBech32(user.FormattedAddress())) 185 | 186 | require.Equal(t, uint32(0), message.Version) 187 | require.Equal(t, uint32(4), message.SourceDomain) 188 | require.Equal(t, uint32(0), message.DestinationDomain) 189 | require.Equal(t, uint64(0), message.Nonce) 190 | require.Equal(t, messageSender, message.Sender) 191 | require.Equal(t, tokenMessenger, message.Recipient) 192 | require.Equal(t, destinationCaller, message.DestinationCaller) 193 | 194 | body, err := new(cctptypes.BurnMessage).Parse(message.MessageBody) 195 | require.NoError(t, err) 196 | 197 | require.Equal(t, uint32(0), body.Version) 198 | require.Equal(t, mintRecipient, body.MintRecipient) 199 | require.Equal(t, msgDepositForBurnWithCallerNoble.Amount, body.Amount) 200 | require.Equal(t, expectedBurnToken, body.BurnToken) 201 | require.Equal(t, moduleAddress, body.MessageSender) 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /e2e/cctp_replace_deposit_for_burn_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package e2e_test 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "crypto/ecdsa" 23 | "crypto/elliptic" 24 | "encoding/hex" 25 | "fmt" 26 | "sort" 27 | "testing" 28 | "time" 29 | 30 | "cosmossdk.io/math" 31 | cctptypes "github.com/circlefin/noble-cctp/x/cctp/types" 32 | sdk "github.com/cosmos/cosmos-sdk/types" 33 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 34 | "github.com/ethereum/go-ethereum/common" 35 | "github.com/ethereum/go-ethereum/crypto" 36 | "github.com/noble-assets/noble/e2e" 37 | "github.com/strangelove-ventures/interchaintest/v8" 38 | "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" 39 | "github.com/stretchr/testify/require" 40 | ) 41 | 42 | func TestCCTP_ReplaceDepositForBurn(t *testing.T) { 43 | if testing.Short() { 44 | t.Skip() 45 | } 46 | t.Parallel() 47 | 48 | ctx := context.Background() 49 | nw, _ := e2e.NobleSpinUp(t, ctx, e2e.LocalImages, true) 50 | noble := nw.Chain 51 | nobleValidator := noble.Validators[0] 52 | 53 | broadcaster := cosmos.NewBroadcaster(t, noble) 54 | 55 | attesters := make([]*ecdsa.PrivateKey, 2) 56 | 57 | // attester - ECDSA public key (Circle will own these keys for mainnet) 58 | for i := range attesters { 59 | p, err := crypto.GenerateKey() // private key 60 | require.NoError(t, err) 61 | 62 | attesters[i] = p 63 | 64 | pubKey := elliptic.Marshal(p.PublicKey, p.PublicKey.X, p.PublicKey.Y) // public key 65 | 66 | attesterPub := hex.EncodeToString(pubKey) 67 | 68 | bCtx, bCancel := context.WithTimeout(ctx, 20*time.Second) 69 | defer bCancel() 70 | 71 | // Adding an attester to protocal 72 | tx, err := cosmos.BroadcastTx( 73 | bCtx, 74 | broadcaster, 75 | nw.CCTPRoles.AttesterManager, 76 | &cctptypes.MsgEnableAttester{ 77 | From: nw.CCTPRoles.AttesterManager.FormattedAddress(), 78 | Attester: attesterPub, 79 | }, 80 | ) 81 | require.NoError(t, err, "error enabling attester") 82 | require.Zero(t, tx.Code, "cctp enable attester transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 83 | } 84 | 85 | burnToken := make([]byte, 32) 86 | copy(burnToken[12:], common.FromHex("0x07865c6E87B9F70255377e024ace6630C1Eaa37F")) 87 | 88 | // maps remote token on remote domain to a local token -- used for minting 89 | bCtx, bCancel := context.WithTimeout(ctx, 20*time.Second) 90 | defer bCancel() 91 | 92 | tx, err := cosmos.BroadcastTx( 93 | bCtx, 94 | broadcaster, 95 | nw.CCTPRoles.TokenController, 96 | &cctptypes.MsgLinkTokenPair{ 97 | From: nw.CCTPRoles.TokenController.FormattedAddress(), 98 | RemoteDomain: 0, 99 | RemoteToken: burnToken, 100 | LocalToken: e2e.DenomMetadataUsdc.Base, 101 | }, 102 | ) 103 | require.NoError(t, err, "error linking token pair") 104 | require.Zero(t, tx.Code, "cctp link token pair transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 105 | 106 | t.Logf("Submitted add public keys tx: %s", tx.TxHash) 107 | 108 | cctpModuleAccount := authtypes.NewModuleAddress(cctptypes.ModuleName).String() 109 | 110 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MasterMinter.KeyName(), 111 | "fiat-tokenfactory", "configure-minter-controller", nw.FiatTfRoles.MinterController.FormattedAddress(), cctpModuleAccount, 112 | ) 113 | require.NoError(t, err, "failed to execute configure minter controller tx") 114 | 115 | _, err = nobleValidator.ExecTx(ctx, nw.FiatTfRoles.MinterController.KeyName(), 116 | "fiat-tokenfactory", "configure-minter", cctpModuleAccount, "1000000"+e2e.DenomMetadataUsdc.Base, 117 | ) 118 | require.NoError(t, err, "failed to execute configure minter tx") 119 | 120 | const receiver = "9B6CA0C13EB603EF207C4657E1E619EF531A4D27" // account 121 | 122 | receiverBz, err := hex.DecodeString(receiver) 123 | require.NoError(t, err) 124 | 125 | burnRecipientPadded := append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, receiverBz...) 126 | 127 | user := interchaintest.GetAndFundTestUsers(t, ctx, "wallet", math.OneInt(), noble)[0] 128 | 129 | messageSender := make([]byte, 32) 130 | copy(messageSender[12:], sdk.MustAccAddressFromBech32(user.FormattedAddress())) 131 | 132 | // someone burned USDC on Ethereum -> Mint on Noble 133 | depositForBurn := cctptypes.BurnMessage{ 134 | BurnToken: burnToken, 135 | MintRecipient: burnRecipientPadded, 136 | Amount: math.NewInt(1000000), 137 | MessageSender: messageSender, 138 | } 139 | 140 | depositForBurnBz, err := depositForBurn.Bytes() 141 | require.NoError(t, err) 142 | 143 | emptyDestinationCaller := make([]byte, 32) 144 | 145 | wrappedDepositForBurn := cctptypes.Message{ 146 | Version: 0, 147 | SourceDomain: 4, // noble is 4 148 | DestinationDomain: 0, 149 | Nonce: 0, // dif per message 150 | Sender: cctptypes.PaddedModuleAddress, 151 | Recipient: cctptypes.PaddedModuleAddress, 152 | DestinationCaller: emptyDestinationCaller, 153 | MessageBody: depositForBurnBz, 154 | } 155 | 156 | wrappedDepositForBurnBz, err := wrappedDepositForBurn.Bytes() 157 | require.NoError(t, err) 158 | 159 | digestBurn := crypto.Keccak256(wrappedDepositForBurnBz) // hashed message is the key to the attestation 160 | 161 | attestationBurn := make([]byte, 0, len(attesters)*65) // 65 byte 162 | 163 | // CCTP requires attestations to have signatures sorted by address 164 | sort.Slice(attesters, func(i, j int) bool { 165 | return bytes.Compare( 166 | crypto.PubkeyToAddress(attesters[i].PublicKey).Bytes(), 167 | crypto.PubkeyToAddress(attesters[j].PublicKey).Bytes(), 168 | ) < 0 169 | }) 170 | 171 | for i := range attesters { 172 | sig, err := crypto.Sign(digestBurn, attesters[i]) 173 | require.NoError(t, err) 174 | 175 | attestationBurn = append(attestationBurn, sig...) 176 | } 177 | 178 | t.Logf("Attested to messages: %s", tx.TxHash) 179 | 180 | newDestCaller := []byte("12345678901234567890123456789012") 181 | newMintRecipient := []byte("12345678901234567890123456789012") 182 | 183 | bCtx, bCancel = context.WithTimeout(ctx, 20*time.Second) 184 | defer bCancel() 185 | tx, err = cosmos.BroadcastTx( 186 | bCtx, 187 | broadcaster, 188 | user, 189 | &cctptypes.MsgReplaceDepositForBurn{ 190 | From: user.FormattedAddress(), 191 | OriginalMessage: wrappedDepositForBurnBz, 192 | OriginalAttestation: attestationBurn, 193 | NewDestinationCaller: newDestCaller, 194 | NewMintRecipient: newMintRecipient, 195 | }, 196 | ) 197 | require.NoError(t, err, "error submitting cctp replace deposit for burn tx") 198 | require.Zerof(t, tx.Code, "cctp replace deposit for burn transaction failed: %s - %s - %s", tx.Codespace, tx.RawLog, tx.Data) 199 | 200 | t.Logf("CCTP replace message successfully received: %s", tx.TxHash) 201 | 202 | for _, rawEvent := range tx.Events { 203 | switch rawEvent.Type { 204 | case "circle.cctp.v1.DepositForBurn": 205 | parsedEvent, err := sdk.ParseTypedEvent(rawEvent) 206 | require.NoError(t, err) 207 | actualDepositForBurn, ok := parsedEvent.(*cctptypes.DepositForBurn) 208 | require.True(t, ok) 209 | 210 | expectedBurnToken := hex.EncodeToString(crypto.Keccak256(depositForBurn.BurnToken)) 211 | 212 | require.Equal(t, wrappedDepositForBurn.Nonce, actualDepositForBurn.Nonce) 213 | require.Equal(t, expectedBurnToken, actualDepositForBurn.BurnToken) 214 | require.Equal(t, depositForBurn.Amount, actualDepositForBurn.Amount) 215 | require.Equal(t, user.FormattedAddress(), actualDepositForBurn.Depositor) 216 | require.Equal(t, newMintRecipient, actualDepositForBurn.MintRecipient) // new 217 | require.Equal(t, wrappedDepositForBurn.DestinationDomain, actualDepositForBurn.DestinationDomain) 218 | require.Equal(t, wrappedDepositForBurn.Recipient, actualDepositForBurn.DestinationTokenMessenger) 219 | require.Equal(t, newDestCaller, actualDepositForBurn.DestinationCaller) // new 220 | case "circle.cctp.v1.MessageSent": 221 | parsedEvent, err := sdk.ParseTypedEvent(rawEvent) 222 | require.NoError(t, err) 223 | event, ok := parsedEvent.(*cctptypes.MessageSent) 224 | require.True(t, ok) 225 | 226 | message, err := new(cctptypes.Message).Parse(event.Message) 227 | require.NoError(t, err) 228 | 229 | expectedBurnToken := hex.EncodeToString(crypto.Keccak256(depositForBurn.BurnToken)) 230 | fmt.Println(expectedBurnToken) 231 | 232 | moduleAddress := make([]byte, 32) 233 | copy(moduleAddress[12:], sdk.MustAccAddressFromBech32(user.FormattedAddress())) 234 | 235 | require.Equal(t, wrappedDepositForBurn.Version, message.Version) 236 | require.Equal(t, wrappedDepositForBurn.SourceDomain, message.SourceDomain) 237 | require.Equal(t, wrappedDepositForBurn.DestinationDomain, message.DestinationDomain) 238 | require.Equal(t, wrappedDepositForBurn.Nonce, message.Nonce) 239 | require.Equal(t, cctptypes.PaddedModuleAddress, message.Sender) 240 | require.Equal(t, cctptypes.PaddedModuleAddress, message.Recipient) 241 | require.Equal(t, newDestCaller, message.DestinationCaller) 242 | 243 | body, err := new(cctptypes.BurnMessage).Parse(message.MessageBody) 244 | require.NoError(t, err) 245 | 246 | require.Equal(t, depositForBurn.Version, body.Version) 247 | require.Equal(t, newMintRecipient, body.MintRecipient) 248 | require.Equal(t, depositForBurn.Amount, body.Amount) 249 | require.True(t, bytes.Equal(depositForBurn.BurnToken, body.BurnToken)) 250 | require.Equal(t, depositForBurn.MessageSender, body.MessageSender) 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /upgrade/upgrade.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // Copyright 2025 NASD Inc. All Rights Reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package upgrade 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | 24 | "cosmossdk.io/log" 25 | upgradetypes "cosmossdk.io/x/upgrade/types" 26 | sdk "github.com/cosmos/cosmos-sdk/types" 27 | "github.com/cosmos/cosmos-sdk/types/module" 28 | authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" 29 | authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 30 | orbiterkeeper "github.com/noble-assets/orbiter/v2/keeper" 31 | dispatchercomp "github.com/noble-assets/orbiter/v2/keeper/component/dispatcher" 32 | orbitercore "github.com/noble-assets/orbiter/v2/types/core" 33 | ) 34 | 35 | func CreateUpgradeHandler( 36 | mm *module.Manager, 37 | cfg module.Configurator, 38 | logger log.Logger, 39 | accountKeeper *authkeeper.AccountKeeper, 40 | orbiterKeeper *orbiterkeeper.Keeper, 41 | ) upgradetypes.UpgradeHandler { 42 | return func(ctx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { 43 | vm, err := mm.RunMigrations(ctx, cfg, vm) 44 | if err != nil { 45 | return vm, err 46 | } 47 | 48 | sdkCtx := sdk.UnwrapSDKContext(ctx) 49 | cachedCtx, writeCache := sdkCtx.CacheContext() 50 | err = updateOrbiterStats(cachedCtx, logger, orbiterKeeper) 51 | if err != nil { 52 | logger.Error("failed to updated Orbiter stats", "error", err) 53 | } else { 54 | writeCache() 55 | } 56 | 57 | cachedCtx, writeCache = sdkCtx.CacheContext() 58 | err = updateOrbiterModuleAccounts(cachedCtx, logger, *accountKeeper) 59 | if err != nil { 60 | logger.Error("failed to update Orbiter module accounts", "error", err) 61 | } else { 62 | writeCache() 63 | } 64 | 65 | logger.Info(UpgradeASCII) 66 | 67 | return vm, nil 68 | } 69 | } 70 | 71 | // The Orbiter module and dust collector accounts should be module accounts. If they received funds 72 | // before the v11 upgrade, they remained stored as base account. This causes the query to the module 73 | // account to fail. This handler migrates them to module accounts. 74 | func updateOrbiterModuleAccounts(ctx sdk.Context, logger log.Logger, accountKeeper authkeeper.AccountKeeper) error { 75 | for _, name := range []string{orbitercore.ModuleName, orbitercore.DustCollectorName} { 76 | addr, perms := accountKeeper.GetModuleAddressAndPermissions(name) 77 | if addr == nil { 78 | return fmt.Errorf("failed to get module address and permissions for %s", name) 79 | } 80 | 81 | // Module account registration is lazy. When we run this function without real mainnet or 82 | // testnet data, the query will return `nil` because the modules are not registered yet. 83 | // We should skip this situation to perform e2e upgrade tests. 84 | acc := accountKeeper.GetAccount(ctx, addr) 85 | if acc == nil { 86 | continue 87 | } 88 | 89 | baseAcc, ok := (acc).(*authtypes.BaseAccount) 90 | if !ok { 91 | // We should skip the case in which the address is already associated with a module 92 | // address. 93 | _, ok := (acc).(*authtypes.ModuleAccount) 94 | if ok { 95 | logger.Info(fmt.Sprintf("skipped migration of %s, already a module account", name), "address", addr.String()) 96 | continue 97 | } 98 | // If we are very unlucky... 99 | return fmt.Errorf("error creating the base account for %s: %T", name, acc) 100 | } 101 | 102 | macc := authtypes.NewModuleAccount(baseAcc, name, perms...) 103 | accountKeeper.SetModuleAccount(ctx, macc) 104 | logger.Info(fmt.Sprintf("migrated %s to a module account", name), "address", addr.String()) 105 | } 106 | 107 | return nil 108 | } 109 | 110 | // updateOrbiterStats updates the statistics of the Orbiter module to improve readability of the 111 | // denom and fix the used couterparty id. We have two cases to fix here: 112 | // 1. Update entries that use the countertparty channel id OF Noble with the counterparty channel 113 | // id ON Noble (this error applies only for testnet since it is caused by the beta release) 114 | // 2. Use the denom representation on Noble and not the IBC one. This means converting this 115 | // transfer/channel-4280/uusdc into uusdc 116 | func updateOrbiterStats(ctx sdk.Context, logger log.Logger, orbiterKeeper *orbiterkeeper.Keeper) error { 117 | expectedDenom := "uusdc" 118 | channelsToCorrect := make(map[string]string) 119 | switch ctx.ChainID() { 120 | case MainnetChainID: 121 | // No-op for mainnet since channels are correct there. 122 | case TestnetChainID: 123 | // Counterparty channel to Noble -> Noble to counterparty channel. 124 | channelsToCorrect = map[string]string{ 125 | "channel-4280": "channel-22", // osmosis 126 | "channel-27": "channel-639", // namada 127 | "channel-3": "channel-333", // xion 128 | "channel-496": "channel-43", // neutron 129 | } 130 | default: 131 | return nil 132 | } 133 | 134 | dispatcher := orbiterKeeper.Dispatcher() 135 | if dispatcher == nil { 136 | return errors.New("received nil orbiter dispatcher component") 137 | } 138 | 139 | err := updateDispatchedAmounts(ctx, logger, expectedDenom, channelsToCorrect, dispatcher) 140 | if err != nil { 141 | return fmt.Errorf("failed to update dispatcher amounts: %w", err) 142 | } 143 | 144 | err = updateDispatchedCounts(ctx, logger, channelsToCorrect, dispatcher) 145 | if err != nil { 146 | return fmt.Errorf("failed to update dispatcher counts: %w", err) 147 | } 148 | 149 | return nil 150 | } 151 | 152 | func updateDispatchedAmounts( 153 | ctx context.Context, 154 | logger log.Logger, 155 | expectedDenom string, 156 | channelsToCorrect map[string]string, 157 | dispatcher *dispatchercomp.Dispatcher, 158 | ) error { 159 | amounts := dispatcher.GetAllDispatchedAmounts(ctx) 160 | 161 | var numDenomUpdated, numChannelUpdated int 162 | for _, entry := range amounts { 163 | correctChannel, isWrongChannelID := channelsToCorrect[entry.SourceId.GetCounterpartyId()] 164 | 165 | // The only available route so far is IBC to CCTP. Since CCTP supports only USDC, we have 166 | // to update all the denoms to USDC. 167 | isWrongDenom := entry.Denom != expectedDenom 168 | 169 | // If the channel is not in the wrong channel list, or the denom is USDC, then the entry is 170 | // correct. We basically skip all the entries with 0 incoming dispatched amount but a non 171 | // zero outgoing amount. 172 | if !isWrongChannelID && !isWrongDenom { 173 | // Source protocol is always IBC and destination is always CCTP, no need to log them. 174 | logger.Debug("skipping dispatched amounts entry", 175 | "src_counterparty_id", entry.SourceId.GetCounterpartyId(), 176 | "dst_countertparty_id", entry.DestinationId.GetCounterpartyId(), 177 | "denom", entry.Denom, 178 | "amount_incoming", entry.AmountDispatched.Incoming.String(), 179 | "amount_outgoing", entry.AmountDispatched.Outgoing.String(), 180 | ) 181 | continue 182 | } 183 | 184 | // One of the situations to fix, is with a correct channel ID but a wrong denom. Since the 185 | // correct channel ID is not in the map, we have to use the values of the entry, otherwise 186 | // it will use the empty string from the miss in the map. 187 | if !isWrongChannelID { 188 | correctChannel = entry.SourceId.GetCounterpartyId() 189 | numDenomUpdated += 1 190 | } else { 191 | numChannelUpdated += 1 192 | } 193 | 194 | logger.Debug("handling dispatched amounts entry", 195 | "src_counterparty_id", entry.SourceId.GetCounterpartyId(), 196 | "dst_countertparty_id", entry.DestinationId.GetCounterpartyId(), 197 | "denom", entry.Denom, 198 | "amount_incoming", entry.AmountDispatched.Incoming.String(), 199 | "amount_outgoing", entry.AmountDispatched.Outgoing.String(), 200 | ) 201 | 202 | // We remove from the store the wrong entry. 203 | err := dispatcher.RemoveDispatchedAmount(ctx, entry.SourceId, entry.DestinationId, entry.Denom) 204 | if err != nil { 205 | return fmt.Errorf("failed to remove dispatched amount from state: %w", err) 206 | } 207 | 208 | correctSourceID, err := orbitercore.NewCrossChainID(entry.SourceId.GetProtocolId(), correctChannel) 209 | if err != nil { 210 | return fmt.Errorf("failed to create cross chain ID: %w", err) 211 | } 212 | 213 | // Get the entry with the correct outgoing value from the state. Returns zero amounts if 214 | // not present. 215 | oldValue := dispatcher.GetDispatchedAmount(ctx, &correctSourceID, entry.DestinationId, expectedDenom) 216 | 217 | dispatchedAmount := oldValue.AmountDispatched 218 | // Add the entry incoming amount as the incoming amount of the correct entry. 219 | if entry.AmountDispatched.Incoming.IsPositive() { 220 | dispatchedAmount.Incoming = dispatchedAmount.Incoming.Add(entry.AmountDispatched.Incoming) 221 | } 222 | if entry.AmountDispatched.Outgoing.IsPositive() { 223 | dispatchedAmount.Outgoing = dispatchedAmount.Outgoing.Add(entry.AmountDispatched.Outgoing) 224 | } 225 | 226 | // Update the entry in the store 227 | err = dispatcher.SetDispatchedAmount(ctx, &correctSourceID, entry.DestinationId, expectedDenom, dispatchedAmount) 228 | if err != nil { 229 | return fmt.Errorf("failed to update the dispatched amount in state: %w", err) 230 | } 231 | } 232 | 233 | logger.Info("completed orbiter stats denom update", "updated_entries", numDenomUpdated) 234 | logger.Info("completed orbiter stats channel update", "updated_entries", numChannelUpdated) 235 | return nil 236 | } 237 | 238 | func updateDispatchedCounts( 239 | ctx context.Context, 240 | logger log.Logger, 241 | channelsToCorrect map[string]string, 242 | dispatcher *dispatchercomp.Dispatcher, 243 | ) error { 244 | counts := dispatcher.GetAllDispatchedCounts(ctx) 245 | 246 | var numCountsUpdated int 247 | for _, entry := range counts { 248 | correctChannel, isWrongChannelID := channelsToCorrect[entry.SourceId.GetCounterpartyId()] 249 | 250 | // If the channel is not wrong, then we don't have to do anything. 251 | if !isWrongChannelID { 252 | // Source protocol is always IBC and destination is always CCTP 253 | logger.Debug("skipping dispatched counts entry", 254 | "src_counterparty_id", entry.SourceId.GetCounterpartyId(), 255 | "dst_countertparty_id", entry.DestinationId.GetCounterpartyId(), 256 | "count", entry.Count, 257 | ) 258 | continue 259 | } 260 | 261 | logger.Debug("handling dispatched counts entry", 262 | "src_counterparty_id", entry.SourceId.GetCounterpartyId(), 263 | "dst_countertparty_id", entry.DestinationId.GetCounterpartyId(), 264 | "count", entry.Count, 265 | ) 266 | 267 | // We remove from the store the wrong entry. 268 | err := dispatcher.RemoveDispatchedCounts(ctx, entry.SourceId, entry.DestinationId) 269 | if err != nil { 270 | return fmt.Errorf("failed to remove dispatched counts from state: %w", err) 271 | } 272 | 273 | correctSourceID, err := orbitercore.NewCrossChainID(entry.SourceId.GetProtocolId(), correctChannel) 274 | if err != nil { 275 | return fmt.Errorf("failed to create cross chain ID: %w", err) 276 | } 277 | 278 | // Get the entry with the correct counts value from the state. Returns zero if not present. 279 | oldValue := dispatcher.GetDispatchedCounts(ctx, &correctSourceID, entry.DestinationId) 280 | 281 | counts := oldValue.Count + entry.Count 282 | 283 | // Update the entry in the store 284 | err = dispatcher.SetDispatchedCounts(ctx, &correctSourceID, entry.DestinationId, counts) 285 | if err != nil { 286 | return fmt.Errorf("failed to update the dispatched counts in state: %w", err) 287 | } 288 | 289 | numCountsUpdated += 1 290 | } 291 | logger.Info("completed orbiter stats counts update", "updated_entries", numCountsUpdated) 292 | 293 | return nil 294 | } 295 | --------------------------------------------------------------------------------