├── .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 | 
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 |
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 |
--------------------------------------------------------------------------------