mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 01:41:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84536d3477 | ||
|
|
2a3ea4b8b4 | ||
|
|
4448a10430 | ||
|
|
bddf742cd9 | ||
|
|
d8356c03e2 | ||
|
|
3c7c4bb253 | ||
|
|
6dd00e8cf6 |
+13
-12
@@ -1,18 +1,19 @@
|
||||
[scopes]
|
||||
deps = ["go.sum", "go.mod", "package.json", "bun.lock", "pnpm-lock.yaml"]
|
||||
techdocs = ["docs"]
|
||||
actions = [".github/workflows"]
|
||||
config = [".github", "networks/localnet", "networks/testnet", "networks", "devbox.json", "devbox.lock", "docker-compose.yml", "wrangler.toml"]
|
||||
cli = ["src", "app/commands", "cmd/sonrctl"]
|
||||
cmd = ["cmd/snrd", "cmd"]
|
||||
app = ["app/context", "app/decorators", "app/params", "app/upgrades", "app"]
|
||||
ante = ["app/ante"]
|
||||
api = ["api/dex", "api/did", "api/dwn", "api/svc", "api"]
|
||||
app = ["app/context", "app/decorators", "app/params", "app/upgrades", "app"]
|
||||
proto = ["proto/dex", "proto/did", "proto/dwn", "proto/svc", "proto"]
|
||||
dex = ["x/dex"]
|
||||
did = ["x/did"]
|
||||
dwn = ["x/dwn"]
|
||||
svc = ["x/svc"]
|
||||
chains = ["chains"]
|
||||
ci = ["Makefile", ".github"]
|
||||
cli = ["app/commands"]
|
||||
cmd = ["cmd/snrd", "cmd"]
|
||||
config = ["networks/localnet", "networks/testnet", "networks"]
|
||||
deps = ["go.sum", "go.mod"]
|
||||
dex = ["proto/dex", "x/dex"]
|
||||
did = ["proto/did", "x/did"]
|
||||
docs = ["docs"]
|
||||
dwn = ["proto/dwn", "x/dwn"]
|
||||
proto = ["proto"]
|
||||
scripts = ["scripts"]
|
||||
svc = ["proto/svc", "x/svc"]
|
||||
test = ["test/e2e", "test/integration", "test/oauth", "test"]
|
||||
build = ["Makefile"]
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
[
|
||||
{
|
||||
"scope": "deps",
|
||||
"path": "go.sum"
|
||||
},
|
||||
{
|
||||
"scope": "deps",
|
||||
"path": "go.mod"
|
||||
},
|
||||
{
|
||||
"scope": "mdx",
|
||||
"path": "docs"
|
||||
},
|
||||
{
|
||||
"scope": "ci",
|
||||
"path": "Makefile"
|
||||
},
|
||||
{
|
||||
"scope": "ci",
|
||||
"path": ".github"
|
||||
},
|
||||
{
|
||||
"scope": "dex",
|
||||
"path": "x/dex"
|
||||
},
|
||||
{
|
||||
"scope": "dex",
|
||||
"path": "proto/dex"
|
||||
},
|
||||
{
|
||||
"scope": "did",
|
||||
"path": "x/did"
|
||||
},
|
||||
{
|
||||
"scope": "did",
|
||||
"path": "proto/did"
|
||||
},
|
||||
{
|
||||
"scope": "dwn",
|
||||
"path": "x/dwn"
|
||||
},
|
||||
{
|
||||
"scope": "dwn",
|
||||
"path": "proto/dwn"
|
||||
},
|
||||
{
|
||||
"scope": "svc",
|
||||
"path": "x/svc"
|
||||
},
|
||||
{
|
||||
"scope": "svc",
|
||||
"path": "proto/svc"
|
||||
},
|
||||
{
|
||||
"scope": "config",
|
||||
"path": "networks"
|
||||
}
|
||||
]
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
report:
|
||||
name: Report CI Status
|
||||
runs-on: tenki-standard-autoscale
|
||||
runs-on: builder
|
||||
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
|
||||
steps:
|
||||
- name: Report Failure
|
||||
|
||||
@@ -13,12 +13,12 @@ env:
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate & Test
|
||||
runs-on: tenki-standard-autoscale
|
||||
runs-on: builder # Self-hosted runner
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Required for github-env.sh comparisons
|
||||
fetch-depth: 0 # Required for github-env.sh comparisons
|
||||
# Cache can be local on self-hosted runner for better performance
|
||||
- name: Cache Dependencies
|
||||
uses: actions/cache@v4
|
||||
@@ -57,4 +57,4 @@ jobs:
|
||||
# Clean build artifacts to prevent disk fill
|
||||
make clean || true
|
||||
# Keep caches but remove build outputs
|
||||
rm -rf dist/ build/ || true
|
||||
rm -rf dist/ build/ || true
|
||||
|
||||
@@ -122,4 +122,3 @@ tmp-openapi-gen/**/*.json
|
||||
!docs/static/openapi.yml
|
||||
.mcp.json
|
||||
node_modules
|
||||
.claude
|
||||
|
||||
@@ -253,19 +253,6 @@ test-e2e-all:
|
||||
@gum log --level info "Running all e2e tests"
|
||||
@cd test/e2e && go test -race -v ./tests/...
|
||||
|
||||
e2e-usdc-swap-did:
|
||||
@gum log --level info "Running USDC Swap E2E tests with DID integration"
|
||||
@gum log --level warn "Note: These tests require manual setup. See test/e2e/usdc-swap-did/README.md"
|
||||
@cd test/e2e/usdc-swap-did && make test
|
||||
|
||||
e2e-usdc-swap-did-setup:
|
||||
@gum log --level info "Running USDC Swap E2E test setup..."
|
||||
@cd test/e2e/usdc-swap-did && bash setup.sh
|
||||
|
||||
e2e-usdc-swap-did-verbose:
|
||||
@gum log --level info "Running USDC Swap E2E tests (verbose mode)"
|
||||
@cd test/e2e/usdc-swap-did && make test-verbose
|
||||
|
||||
test-build-snrd: build
|
||||
@ls -la build/snrd
|
||||
@chmod +x build/snrd
|
||||
@@ -319,7 +306,7 @@ test-proto:
|
||||
test-benchmark:
|
||||
@go test -mod=readonly -bench=. ./...
|
||||
|
||||
.PHONY: test test-all test-unit test-race test-cover test-tdd test-module test-benchmark test-e2e test-e2e-all e2e-usdc-swap-did e2e-usdc-swap-did-setup e2e-usdc-swap-did-verbose
|
||||
.PHONY: test test-all test-unit test-race test-cover test-tdd test-module test-benchmark
|
||||
|
||||
###############################################################################
|
||||
### Protobuf ###
|
||||
@@ -415,11 +402,6 @@ help:
|
||||
@gum log --level info " test-e2e-all Run all e2e tests"
|
||||
@gum log --level info " test-module Test specific module (MODULE=did|dwn|svc)"
|
||||
@gum log --level info ""
|
||||
@gum log --level info "🔄 E2E Test Suites:"
|
||||
@gum log --level info " e2e-usdc-swap-did Run USDC swap E2E tests"
|
||||
@gum log --level info " e2e-usdc-swap-did-setup Interactive setup for USDC swap tests"
|
||||
@gum log --level info " e2e-usdc-swap-did-verbose Run USDC swap tests with verbose output"
|
||||
@gum log --level info ""
|
||||
@gum log --level info "📚 Module Testing Examples:"
|
||||
@gum log --level info " make test-module MODULE=did # Test DID module"
|
||||
@gum log --level info " make test-module MODULE=dwn VARIANT=cover # DWN with coverage"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[scopes]
|
||||
deps = ["go.sum", "go.mod"]
|
||||
cli = ["src", "package.json", "app/commands"]
|
||||
api = ["api/dex", "api/did", "api/dwn", "api/svc", "api"]
|
||||
ante = ["app/ante"]
|
||||
app = ["app/context", "app/decorators", "app/params", "app/upgrades", "app"]
|
||||
chains = ["chains"]
|
||||
cmd = ["cmd/snrd", "cmd"]
|
||||
docs = ["docs"]
|
||||
config = ["networks/localnet", "networks/testnet", "networks"]
|
||||
dex = ["proto/dex", "x/dex"]
|
||||
did = ["proto/did", "x/did"]
|
||||
dwn = ["proto/dwn", "x/dwn"]
|
||||
svc = ["proto/svc", "x/svc"]
|
||||
proto = ["proto"]
|
||||
scripts = ["scripts"]
|
||||
test = ["test/e2e", "test/integration", "test/oauth", "test"]
|
||||
ci = ["Makefile", ".github"]
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "sonr",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
||||
|
||||
"@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@@ -36,25 +35,3 @@ func SetBech32Prefixes(config *sdk.Config) {
|
||||
config.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub)
|
||||
config.SetBech32PrefixForConsensusNode(app.Bech32PrefixConsAddr, app.Bech32PrefixConsPub)
|
||||
}
|
||||
|
||||
func sonrLogo() string {
|
||||
lines := []string{
|
||||
"[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m░[0m [38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m░[0m [38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m░[0m [38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m░[0m [38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m█[0m[38;2;153;255;255m░[0m",
|
||||
"[38;2;119;233;242m█[0m[38;2;119;233;242m█[0m[38;2;119;233;242m░[0m [38;2;119;233;242m█[0m[38;2;119;233;242m█[0m[38;2;119;233;242m░[0m [38;2;119;233;242m█[0m[38;2;119;233;242m█[0m[38;2;119;233;242m░[0m [38;2;119;233;242m█[0m[38;2;119;233;242m█[0m[38;2;119;233;242m░[0m [38;2;119;233;242m█[0m[38;2;119;233;242m█[0m[38;2;119;233;242m░[0m [38;2;119;233;242m█[0m[38;2;119;233;242m█[0m[38;2;119;233;242m░[0m [38;2;119;233;242m█[0m[38;2;119;233;242m█[0m[38;2;119;233;242m░[0m [38;2;119;233;242m█[0m[38;2;119;233;242m█[0m[38;2;119;233;242m░[0m",
|
||||
"[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m░[0m [38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m░[0m [38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m░[0m [38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m░[0m [38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m░[0m [38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m░[0m [38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m█[0m[38;2;85;211;230m░[0m",
|
||||
" [38;2;51;189;217m█[0m[38;2;51;189;217m█[0m[38;2;51;189;217m░[0m [38;2;51;189;217m█[0m[38;2;51;189;217m█[0m[38;2;51;189;217m░[0m [38;2;51;189;217m█[0m[38;2;51;189;217m█[0m[38;2;51;189;217m░[0m [38;2;51;189;217m█[0m[38;2;51;189;217m█[0m[38;2;51;189;217m░[0m [38;2;51;189;217m█[0m[38;2;51;189;217m█[0m[38;2;51;189;217m░[0m [38;2;51;189;217m█[0m[38;2;51;189;217m█[0m[38;2;51;189;217m░[0m [38;2;51;189;217m█[0m[38;2;51;189;217m█[0m[38;2;51;189;217m░[0m ",
|
||||
"[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m░[0m [38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m░[0m [38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m░[0m [38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m░[0m [38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m░[0m [38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m█[0m[38;2;17;168;205m░[0m",
|
||||
}
|
||||
var logo string
|
||||
// Add top padding
|
||||
logo += "\n"
|
||||
for _, line := range lines {
|
||||
// Replace [ with \x1b[ to make valid ANSI escape sequences
|
||||
line = strings.ReplaceAll(line, "[", "\x1b[")
|
||||
// Add horizontal padding
|
||||
logo += " " + line + "\n"
|
||||
}
|
||||
// Add bottom padding
|
||||
logo += "\n"
|
||||
return logo
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ func NewRootCmd() *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: version.AppName,
|
||||
Short: version.AppName + " Daemon (server)",
|
||||
Long: sonrLogo(),
|
||||
SilenceErrors: true,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
// set the default command outputs
|
||||
|
||||
+4
-5
@@ -8,8 +8,7 @@
|
||||
"jq@latest",
|
||||
"trunk@latest",
|
||||
"mods@latest",
|
||||
"yq@latest",
|
||||
"dasel@latest"
|
||||
"yq@latest"
|
||||
],
|
||||
"env": {},
|
||||
"shell": {
|
||||
@@ -17,10 +16,10 @@
|
||||
"go mod download"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "make build-all",
|
||||
"test": "make test-all",
|
||||
"build": "make build-all",
|
||||
"test": "make test-all",
|
||||
"snapshot": "make snapshot",
|
||||
"release": "make release"
|
||||
"release": "make release"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-48
@@ -1,54 +1,6 @@
|
||||
{
|
||||
"lockfile_version": "1",
|
||||
"packages": {
|
||||
"dasel@latest": {
|
||||
"last_modified": "2025-10-07T08:41:47Z",
|
||||
"resolved": "github:NixOS/nixpkgs/bce5fe2bb998488d8e7e7856315f90496723793c#dasel",
|
||||
"source": "devbox-search",
|
||||
"version": "2.8.1",
|
||||
"systems": {
|
||||
"aarch64-darwin": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/kahwgfqz0x48aj3ikrd963pinkjsgi1p-dasel-2.8.1",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/kahwgfqz0x48aj3ikrd963pinkjsgi1p-dasel-2.8.1"
|
||||
},
|
||||
"aarch64-linux": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/gcnpff6qc8r8rz6paphc3j2q20hhys5r-dasel-2.8.1",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/gcnpff6qc8r8rz6paphc3j2q20hhys5r-dasel-2.8.1"
|
||||
},
|
||||
"x86_64-darwin": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/c97l94fqsic4nqrgrzrn5fjbw18lggjw-dasel-2.8.1",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/c97l94fqsic4nqrgrzrn5fjbw18lggjw-dasel-2.8.1"
|
||||
},
|
||||
"x86_64-linux": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/5494996bmd4nqv17l4ji9r3r2n5b1aqj-dasel-2.8.1",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/5494996bmd4nqv17l4ji9r3r2n5b1aqj-dasel-2.8.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker@latest": {
|
||||
"last_modified": "2025-08-05T11:35:34Z",
|
||||
"resolved": "github:NixOS/nixpkgs/a683adc19ff5228af548c6539dbc3440509bfed3#docker",
|
||||
|
||||
@@ -32,6 +32,26 @@ services:
|
||||
networks:
|
||||
- sonr-network
|
||||
|
||||
caddy:
|
||||
container_name: caddy
|
||||
image: caddy:2-alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
snrd:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
volumes:
|
||||
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
environment:
|
||||
CADDY_DOMAIN: ${CADDY_DOMAIN:-localhost}
|
||||
networks:
|
||||
- sonr-network
|
||||
|
||||
snrd:
|
||||
container_name: snrd
|
||||
image: onsonr/snrd:latest
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: "Migrating from Centralized to Decentralized Authentication"
|
||||
description: "A comprehensive guide to migrating from centralized 8787 endpoints to decentralized OIDC authentication with WebAuthn"
|
||||
sidebarTitle: "Decentralized Authentication"
|
||||
icon: "key"
|
||||
---
|
||||
|
||||
# Migrating from Centralized to Decentralized Authentication
|
||||
|
||||
## Overview
|
||||
|
||||
Sonr is transitioning from a centralized authentication model using 8787 endpoints to a fully decentralized, privacy-preserving authentication system leveraging OpenID Connect (OIDC), Self-Issued OpenID Provider (SIOP), and WebAuthn technologies.
|
||||
|
||||
### Key Improvements
|
||||
|
||||
- **Decentralization**: Move from centralized identity management to self-sovereign identity
|
||||
- **WebAuthn Integration**: Hardware-backed, phishing-resistance authentication
|
||||
- **Gasless Onboarding**: Zero-cost user registration and transactions
|
||||
- **Automatic Vault Creation**: Seamless user experience with instant identity setup
|
||||
- **Enhanced Privacy**: DID-based authentication with verifiable presentations
|
||||
|
||||
## Architecture Comparison
|
||||
|
||||
### Old Architecture: Centralized 8787 Endpoints
|
||||
|
||||
- Centralized authentication server
|
||||
- Fixed credential storage
|
||||
- Limited authentication methods
|
||||
- Higher security risks
|
||||
|
||||
### New Architecture: Decentralized OIDC Provider
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[User Device] --> B{WebAuthn Registration}
|
||||
B --> |Credential Generation| C[Blockchain Bridge]
|
||||
C --> |Broadcast Credential| D[Sonr Blockchain]
|
||||
D --> |Create DID| E[User's Decentralized Identity]
|
||||
E --> |SIOP Flow| F[OIDC Provider]
|
||||
F --> |Verifiable Presentation| G[Service Provider]
|
||||
```
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### 1. Prerequisites
|
||||
|
||||
- Go 1.24.1+
|
||||
- Cosmos SDK v0.50.14
|
||||
- WebAuthn-compatible browser/device
|
||||
- Updated Sonr SDK
|
||||
|
||||
### 2. Configuration Changes
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
<CodeGroup>
|
||||
```bash title="Old Configuration"
|
||||
# Centralized auth endpoints
|
||||
AUTH_ENDPOINT=https://8787.sonr.io/auth
|
||||
AUTH_CLIENT_ID=legacy-client-id
|
||||
```
|
||||
|
||||
```bash title="New Configuration"
|
||||
# Decentralized OIDC configuration
|
||||
OIDC_PROVIDER_URL=https://oidc.sonr.network
|
||||
WEBAUTHN_ORIGIN=https://your-app.com
|
||||
DID_RESOLVER_URL=https://did.sonr.network
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### 3. Endpoint Migration
|
||||
|
||||
| Old Endpoint | New Endpoint | Changes |
|
||||
| ---------------- | -------------------- | ----------------------- |
|
||||
| `/auth/login` | `/oidc/authorize` | OIDC authorization flow |
|
||||
| `/auth/register` | `/webauthn/register` | WebAuthn registration |
|
||||
| `/auth/token` | `/oidc/token` | Token issuance via SIOP |
|
||||
|
||||
### 4. API Changes
|
||||
|
||||
#### Request Format
|
||||
|
||||
<CodeGroup>
|
||||
```json title="Legacy Request"
|
||||
{
|
||||
"username": "user@example.com",
|
||||
"password": "legacy-password"
|
||||
}
|
||||
```
|
||||
|
||||
```json title="New WebAuthn Request"
|
||||
{
|
||||
"challenge": "base64-encoded-challenge",
|
||||
"attestation": {
|
||||
"type": "public-key",
|
||||
"id": "credential-id",
|
||||
"rawId": "base64-encoded-raw-credential",
|
||||
"response": {
|
||||
"clientDataJSON": "...",
|
||||
"attestationObject": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### 5. WebAuthn Integration
|
||||
|
||||
#### Registration Flow
|
||||
|
||||
1. Generate registration challenge
|
||||
2. Create WebAuthn credential
|
||||
3. Broadcast credential to blockchain
|
||||
4. Automatically create user vault
|
||||
5. Issue decentralized identifier (DID)
|
||||
|
||||
#### Code Example
|
||||
|
||||
```go title="WebAuthn Registration Handler"
|
||||
func (s *Server) HandleWebAuthnRegistration(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Validate WebAuthn attestation
|
||||
credential, err := s.webAuthnService.VerifyRegistration(attestationData)
|
||||
|
||||
// 2. Broadcast to blockchain
|
||||
didDoc, err := s.blockchainBridge.CreateDID(credential)
|
||||
|
||||
// 3. Create user vault
|
||||
vault, err := s.vaultService.CreateVault(didDoc)
|
||||
|
||||
// 4. Issue OIDC token
|
||||
token := s.oidcService.IssueToken(didDoc)
|
||||
}
|
||||
```
|
||||
|
||||
### 6. SIOP (Self-Issued OpenID Provider)
|
||||
|
||||
#### Key Concepts
|
||||
|
||||
- Verifiable Presentations
|
||||
- Decentralized Identifiers (DIDs)
|
||||
- User-controlled authentication
|
||||
|
||||
### 7. Error Handling
|
||||
|
||||
#### Migration Errors
|
||||
|
||||
| Error Code | Description | Mitigation |
|
||||
| ------------------------ | ---------------------- | -------------------- |
|
||||
| `AUTH_LEGACY_DEPRECATED` | Legacy auth method | Upgrade client |
|
||||
| `WEBAUTHN_UNSUPPORTED` | Device incompatibility | Use alternative auth |
|
||||
| `DID_RESOLUTION_FAILED` | Identity verification | Retry registration |
|
||||
|
||||
### 8. Testing Migration
|
||||
|
||||
```bash
|
||||
# Validate WebAuthn registration
|
||||
sonr webauthn test-registration
|
||||
|
||||
# Verify OIDC provider
|
||||
sonr oidc validate-provider
|
||||
|
||||
# Check DID resolution
|
||||
sonr did resolve did:sonr:example
|
||||
```
|
||||
|
||||
### 9. Breaking Changes
|
||||
|
||||
- Legacy password authentication removed
|
||||
- Token format changed to JWT with DID claims
|
||||
- New client libraries required
|
||||
- WebAuthn mandatory for registration
|
||||
|
||||
## Rollback Procedure
|
||||
|
||||
If issues arise:
|
||||
|
||||
1. Maintain legacy user mappings
|
||||
2. Provide fallback authentication
|
||||
3. Gradual, opt-in migration
|
||||
|
||||
## Conclusion
|
||||
|
||||
This migration represents a significant leap in authentication security and user privacy. By adopting WebAuthn, SIOP, and blockchain-backed identities, we're creating a more robust, user-controlled authentication ecosystem.
|
||||
|
||||
## Support
|
||||
|
||||
- [Sonr Developer Docs](/docs)
|
||||
- [WebAuthn Specification](https://www.w3.org/TR/webauthn-2/)
|
||||
- [OIDC Community](https://openid.net/developers/specs/)
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
title: "DID Generation and Management"
|
||||
description: "Learn how to generate and manage Decentralized Identifiers (DIDs) using advanced crypto interfaces"
|
||||
sidebarTitle: "Identity Generation"
|
||||
icon: "signature"
|
||||
---
|
||||
|
||||
# DID Generation Patterns
|
||||
|
||||
The Sonr project provides a robust and flexible DID (Decentralized Identifier) generation system supporting multiple key types and cryptographic methods.
|
||||
|
||||
## Supported Key Types
|
||||
|
||||
The `didkey.go` interface supports the following key types:
|
||||
|
||||
- RSA Public Keys
|
||||
- Ed25519 Public Keys
|
||||
- Secp256k1 Public Keys
|
||||
|
||||
## Basic DID Generation
|
||||
|
||||
### Creating a DID from a Public Key
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/sonr-io/crypto/keys"
|
||||
)
|
||||
|
||||
// Generate a new key pair
|
||||
privateKey, publicKey, err := crypto.GenerateKeyPair(crypto.Ed25519, -1)
|
||||
|
||||
// Create a DID
|
||||
did, err := keys.NewDID(publicKey)
|
||||
```
|
||||
|
||||
### Parsing an Existing DID
|
||||
|
||||
```go
|
||||
// Parse a DID string
|
||||
did, err := keys.Parse("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGTsyDFWYviJr4")
|
||||
```
|
||||
|
||||
## Advanced DID Operations
|
||||
|
||||
### Address Derivation
|
||||
|
||||
```go
|
||||
// Get a blockchain-compatible address from a DID
|
||||
address, err := did.Address()
|
||||
```
|
||||
|
||||
### Compressed Public Key Retrieval
|
||||
|
||||
```go
|
||||
// Get a compressed public key (for Secp256k1)
|
||||
compressedPubKey, err := did.CompressedPubKey()
|
||||
```
|
||||
|
||||
## MPC Enclave DID Generation
|
||||
|
||||
For MPC (Multi-Party Computation) enclaves, use a specialized method:
|
||||
|
||||
```go
|
||||
// Create a DID from MPC enclave public key bytes
|
||||
did, err := keys.NewFromMPCPubKey(enclavePublicKeyBytes)
|
||||
```
|
||||
|
||||
## DID Validation
|
||||
|
||||
```go
|
||||
// Validate a DID format
|
||||
err := keys.ValidateFormat("did:key:example123")
|
||||
```
|
||||
|
||||
## Multicodec Support
|
||||
|
||||
The library supports various multicodec prefixes for different key types:
|
||||
|
||||
- RSA: `0x1205`
|
||||
- Ed25519: `0xed`
|
||||
- Secp256k1: `0xe7`
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Always generate keys using cryptographically secure methods
|
||||
- Validate DIDs before using them in critical operations
|
||||
- Use the appropriate key type for your security requirements
|
||||
|
||||
## Example: Complete DID Workflow
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/sonr-io/crypto/keys"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Generate a new key pair
|
||||
privateKey, publicKey, err := crypto.GenerateKeyPair(crypto.Ed25519, -1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create a DID
|
||||
did, err := keys.NewDID(publicKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Get the DID string
|
||||
didString := did.String()
|
||||
fmt.Println("Generated DID:", didString)
|
||||
|
||||
// Derive blockchain address
|
||||
address, err := did.Address()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Blockchain Address:", address)
|
||||
}
|
||||
```
|
||||
|
||||
## Compatibility and Interoperability
|
||||
|
||||
The Sonr DID implementation follows the W3C DID specification, ensuring broad compatibility with other decentralized identity systems.
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: "Migrating from Centralized to Decentralized Authentication"
|
||||
description: "A comprehensive guide to migrating from centralized 8787 endpoints to decentralized OIDC authentication with WebAuthn"
|
||||
sidebarTitle: "Decentralized Authentication"
|
||||
icon: "key"
|
||||
---
|
||||
|
||||
# Migrating from Centralized to Decentralized Authentication
|
||||
|
||||
## Overview
|
||||
|
||||
Sonr is transitioning from a centralized authentication model using 8787 endpoints to a fully decentralized, privacy-preserving authentication system leveraging OpenID Connect (OIDC), Self-Issued OpenID Provider (SIOP), and WebAuthn technologies.
|
||||
|
||||
### Key Improvements
|
||||
|
||||
- **Decentralization**: Move from centralized identity management to self-sovereign identity
|
||||
- **WebAuthn Integration**: Hardware-backed, phishing-resistance authentication
|
||||
- **Gasless Onboarding**: Zero-cost user registration and transactions
|
||||
- **Automatic Vault Creation**: Seamless user experience with instant identity setup
|
||||
- **Enhanced Privacy**: DID-based authentication with verifiable presentations
|
||||
|
||||
## Architecture Comparison
|
||||
|
||||
### Old Architecture: Centralized 8787 Endpoints
|
||||
|
||||
- Centralized authentication server
|
||||
- Fixed credential storage
|
||||
- Limited authentication methods
|
||||
- Higher security risks
|
||||
|
||||
### New Architecture: Decentralized OIDC Provider
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[User Device] --> B{WebAuthn Registration}
|
||||
B --> |Credential Generation| C[Blockchain Bridge]
|
||||
C --> |Broadcast Credential| D[Sonr Blockchain]
|
||||
D --> |Create DID| E[User's Decentralized Identity]
|
||||
E --> |SIOP Flow| F[OIDC Provider]
|
||||
F --> |Verifiable Presentation| G[Service Provider]
|
||||
```
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### 1. Prerequisites
|
||||
|
||||
- Go 1.24.1+
|
||||
- Cosmos SDK v0.50.14
|
||||
- WebAuthn-compatible browser/device
|
||||
- Updated Sonr SDK
|
||||
|
||||
### 2. Configuration Changes
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
<CodeGroup>
|
||||
```bash title="Old Configuration"
|
||||
# Centralized auth endpoints
|
||||
AUTH_ENDPOINT=https://8787.sonr.io/auth
|
||||
AUTH_CLIENT_ID=legacy-client-id
|
||||
```
|
||||
|
||||
```bash title="New Configuration"
|
||||
# Decentralized OIDC configuration
|
||||
OIDC_PROVIDER_URL=https://oidc.sonr.network
|
||||
WEBAUTHN_ORIGIN=https://your-app.com
|
||||
DID_RESOLVER_URL=https://did.sonr.network
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### 3. Endpoint Migration
|
||||
|
||||
| Old Endpoint | New Endpoint | Changes |
|
||||
| ---------------- | -------------------- | ----------------------- |
|
||||
| `/auth/login` | `/oidc/authorize` | OIDC authorization flow |
|
||||
| `/auth/register` | `/webauthn/register` | WebAuthn registration |
|
||||
| `/auth/token` | `/oidc/token` | Token issuance via SIOP |
|
||||
|
||||
### 4. API Changes
|
||||
|
||||
#### Request Format
|
||||
|
||||
<CodeGroup>
|
||||
```json title="Legacy Request"
|
||||
{
|
||||
"username": "user@example.com",
|
||||
"password": "legacy-password"
|
||||
}
|
||||
```
|
||||
|
||||
```json title="New WebAuthn Request"
|
||||
{
|
||||
"challenge": "base64-encoded-challenge",
|
||||
"attestation": {
|
||||
"type": "public-key",
|
||||
"id": "credential-id",
|
||||
"rawId": "base64-encoded-raw-credential",
|
||||
"response": {
|
||||
"clientDataJSON": "...",
|
||||
"attestationObject": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
### 5. WebAuthn Integration
|
||||
|
||||
#### Registration Flow
|
||||
|
||||
1. Generate registration challenge
|
||||
2. Create WebAuthn credential
|
||||
3. Broadcast credential to blockchain
|
||||
4. Automatically create user vault
|
||||
5. Issue decentralized identifier (DID)
|
||||
|
||||
#### Code Example
|
||||
|
||||
```go title="WebAuthn Registration Handler"
|
||||
func (s *Server) HandleWebAuthnRegistration(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Validate WebAuthn attestation
|
||||
credential, err := s.webAuthnService.VerifyRegistration(attestationData)
|
||||
|
||||
// 2. Broadcast to blockchain
|
||||
didDoc, err := s.blockchainBridge.CreateDID(credential)
|
||||
|
||||
// 3. Create user vault
|
||||
vault, err := s.vaultService.CreateVault(didDoc)
|
||||
|
||||
// 4. Issue OIDC token
|
||||
token := s.oidcService.IssueToken(didDoc)
|
||||
}
|
||||
```
|
||||
|
||||
### 6. SIOP (Self-Issued OpenID Provider)
|
||||
|
||||
#### Key Concepts
|
||||
|
||||
- Verifiable Presentations
|
||||
- Decentralized Identifiers (DIDs)
|
||||
- User-controlled authentication
|
||||
|
||||
### 7. Error Handling
|
||||
|
||||
#### Migration Errors
|
||||
|
||||
| Error Code | Description | Mitigation |
|
||||
| ------------------------ | ---------------------- | -------------------- |
|
||||
| `AUTH_LEGACY_DEPRECATED` | Legacy auth method | Upgrade client |
|
||||
| `WEBAUTHN_UNSUPPORTED` | Device incompatibility | Use alternative auth |
|
||||
| `DID_RESOLUTION_FAILED` | Identity verification | Retry registration |
|
||||
|
||||
### 8. Testing Migration
|
||||
|
||||
```bash
|
||||
# Validate WebAuthn registration
|
||||
sonr webauthn test-registration
|
||||
|
||||
# Verify OIDC provider
|
||||
sonr oidc validate-provider
|
||||
|
||||
# Check DID resolution
|
||||
sonr did resolve did:sonr:example
|
||||
```
|
||||
|
||||
### 9. Breaking Changes
|
||||
|
||||
- Legacy password authentication removed
|
||||
- Token format changed to JWT with DID claims
|
||||
- New client libraries required
|
||||
- WebAuthn mandatory for registration
|
||||
|
||||
## Rollback Procedure
|
||||
|
||||
If issues arise:
|
||||
|
||||
1. Maintain legacy user mappings
|
||||
2. Provide fallback authentication
|
||||
3. Gradual, opt-in migration
|
||||
|
||||
## Conclusion
|
||||
|
||||
This migration represents a significant leap in authentication security and user privacy. By adopting WebAuthn, SIOP, and blockchain-backed identities, we're creating a more robust, user-controlled authentication ecosystem.
|
||||
|
||||
## Support
|
||||
|
||||
- [Sonr Developer Docs](/docs)
|
||||
- [WebAuthn Specification](https://www.w3.org/TR/webauthn-2/)
|
||||
- [OIDC Community](https://openid.net/developers/specs/)
|
||||
@@ -37,22 +37,22 @@ replace (
|
||||
)
|
||||
|
||||
require (
|
||||
cosmossdk.io/api v0.9.2
|
||||
cosmossdk.io/api v0.7.6
|
||||
cosmossdk.io/client/v2 v2.0.0-beta.7
|
||||
cosmossdk.io/collections v1.2.0
|
||||
cosmossdk.io/collections v0.4.0
|
||||
cosmossdk.io/core v0.12.0
|
||||
cosmossdk.io/depinject v1.2.0
|
||||
cosmossdk.io/errors v1.0.2
|
||||
cosmossdk.io/log v1.5.1
|
||||
cosmossdk.io/math v1.5.3
|
||||
cosmossdk.io/depinject v1.1.0
|
||||
cosmossdk.io/errors v1.0.1
|
||||
cosmossdk.io/log v1.5.0
|
||||
cosmossdk.io/math v1.5.0
|
||||
cosmossdk.io/orm v1.0.0-beta.3
|
||||
cosmossdk.io/store v1.1.2
|
||||
cosmossdk.io/store v1.1.1
|
||||
cosmossdk.io/tools/confix v0.1.2
|
||||
cosmossdk.io/x/circuit v0.1.1
|
||||
cosmossdk.io/x/evidence v0.1.1
|
||||
cosmossdk.io/x/feegrant v0.1.1
|
||||
cosmossdk.io/x/nft v0.2.0
|
||||
cosmossdk.io/x/tx v0.14.0
|
||||
cosmossdk.io/x/nft v0.1.0
|
||||
cosmossdk.io/x/tx v0.13.7
|
||||
cosmossdk.io/x/upgrade v0.1.4
|
||||
github.com/CosmWasm/wasmvm v1.5.8
|
||||
github.com/asynkron/protoactor-go v0.0.0-20240822202345-3c0e61ca19c9
|
||||
@@ -77,8 +77,8 @@ require (
|
||||
github.com/sonr-io/common v0.0.0-20251010142707-ab6d2fe7e9c9
|
||||
github.com/sonr-io/crypto v1.0.1
|
||||
github.com/spf13/cast v1.9.2
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/strangelove-ventures/tokenfactory v0.50.3
|
||||
github.com/stretchr/testify v1.11.1
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5
|
||||
@@ -89,26 +89,16 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.21.2 // indirect
|
||||
cosmossdk.io/schema v1.1.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
|
||||
github.com/Oudwins/zog v0.21.6 // indirect
|
||||
github.com/alexvec/go-bip39 v1.1.0 // indirect
|
||||
github.com/biter777/countries v1.7.5 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20250407164829-2945557346d5 // indirect
|
||||
github.com/cosmos/go-bip39 v1.0.0 // indirect
|
||||
github.com/creachadair/atomicfile v0.3.1 // indirect
|
||||
github.com/creachadair/tomledit v0.0.24 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.1.0 // indirect
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/gammazero/chanqueue v1.1.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/google/go-tpm v0.9.6 // indirect
|
||||
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a // indirect
|
||||
@@ -123,27 +113,23 @@ require (
|
||||
github.com/nxadm/tail v1.4.11 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/smartystreets/goconvey v1.8.1 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
|
||||
nhooyr.io/websocket v1.8.10 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/auth v0.13.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
|
||||
cloud.google.com/go v0.115.0 // indirect
|
||||
cloud.google.com/go/auth v0.6.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.7.0 // indirect
|
||||
cloud.google.com/go/iam v1.2.2 // indirect
|
||||
cloud.google.com/go/storage v1.49.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.9 // indirect
|
||||
cloud.google.com/go/storage v1.41.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
github.com/99designs/keyring v1.2.1 // indirect
|
||||
@@ -156,7 +142,7 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.55.6 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/bgentry/speakeasy v0.2.0 // indirect
|
||||
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.0 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/btcsuite/btcd v0.24.2 // indirect
|
||||
@@ -171,11 +157,12 @@ require (
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
|
||||
github.com/cockroachdb/errors v1.11.3 // indirect
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||
github.com/cockroachdb/pebble v1.1.5 // indirect
|
||||
github.com/cockroachdb/redact v1.1.6 // indirect
|
||||
github.com/cockroachdb/pebble v1.1.2 // indirect
|
||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
|
||||
github.com/cometbft/cometbft-db v0.14.1 // indirect
|
||||
github.com/consensys/gnark-crypto v0.19.0 // indirect
|
||||
@@ -201,7 +188,7 @@ require (
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gammazero/deque v1.1.0 // indirect
|
||||
github.com/getsentry/sentry-go v0.27.0 // indirect
|
||||
github.com/go-kit/kit v0.13.0 // indirect
|
||||
@@ -226,10 +213,10 @@ require (
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/orderedcode v0.0.1 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||
@@ -237,20 +224,20 @@ require (
|
||||
github.com/gtank/merlin v0.1.1 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-getter v1.7.9 // indirect
|
||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-metrics v0.5.4 // indirect
|
||||
github.com/hashicorp/go-plugin v1.6.3 // indirect
|
||||
github.com/hashicorp/go-metrics v0.5.3 // indirect
|
||||
github.com/hashicorp/go-plugin v1.5.2 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
|
||||
github.com/holiman/uint256 v1.3.2 // indirect
|
||||
github.com/huandu/skiplist v1.2.1 // indirect
|
||||
github.com/huandu/skiplist v1.2.0 // indirect
|
||||
github.com/iancoleman/orderedmap v0.3.0 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect
|
||||
@@ -298,6 +285,7 @@ require (
|
||||
github.com/minio/highwayhash v1.0.3 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
@@ -315,7 +303,7 @@ require (
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/orcaman/concurrent-map v1.0.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
@@ -330,14 +318,14 @@ require (
|
||||
github.com/rjeczalik/notify v0.9.3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/rs/cors v1.11.1 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/rs/zerolog v1.33.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sasha-s/go-deadlock v0.3.5 // indirect
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
@@ -361,7 +349,7 @@ require (
|
||||
go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.56.0 // indirect
|
||||
@@ -373,7 +361,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
@@ -386,15 +374,15 @@ require (
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
gonum.org/v1/gonum v0.16.0 // indirect
|
||||
google.golang.org/api v0.215.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
google.golang.org/api v0.186.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.5.2 // indirect
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
lukechampine.com/blake3 v1.4.1
|
||||
pgregory.net/rapid v1.2.0 // indirect
|
||||
pgregory.net/rapid v1.1.0 // indirect
|
||||
sigs.k8s.io/yaml v1.5.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
@@ -38,8 +36,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
|
||||
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
|
||||
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
|
||||
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
|
||||
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
|
||||
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
|
||||
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
||||
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
||||
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
|
||||
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
|
||||
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
|
||||
@@ -101,10 +99,10 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo
|
||||
cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=
|
||||
cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
|
||||
cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=
|
||||
cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
|
||||
cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
|
||||
cloud.google.com/go/auth v0.6.0 h1:5x+d6b5zdezZ7gmLWD1m/xNjnaQ2YDhmIz/HH3doy1g=
|
||||
cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
|
||||
cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
|
||||
cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8=
|
||||
@@ -319,8 +317,8 @@ cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGE
|
||||
cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY=
|
||||
cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY=
|
||||
cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=
|
||||
cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA=
|
||||
cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY=
|
||||
cloud.google.com/go/iam v1.1.9 h1:oSkYLVtVme29uGYrOcKcvJRht7cHJpYD09GM9JaR0TE=
|
||||
cloud.google.com/go/iam v1.1.9/go.mod h1:Nt1eDWNYH9nGQg3d/mY7U1hvfGmsaG9o/kLGoLoLXjQ=
|
||||
cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc=
|
||||
cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=
|
||||
cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk=
|
||||
@@ -350,13 +348,9 @@ cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6
|
||||
cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo=
|
||||
cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=
|
||||
cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=
|
||||
cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk=
|
||||
cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM=
|
||||
cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=
|
||||
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
|
||||
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
|
||||
cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
|
||||
cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
|
||||
cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE=
|
||||
cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=
|
||||
cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA=
|
||||
@@ -380,8 +374,6 @@ cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhI
|
||||
cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=
|
||||
cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w=
|
||||
cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw=
|
||||
cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU=
|
||||
cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU=
|
||||
cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
|
||||
cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
|
||||
cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=
|
||||
@@ -545,8 +537,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL
|
||||
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
|
||||
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
|
||||
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
|
||||
cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw=
|
||||
cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU=
|
||||
cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0=
|
||||
cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80=
|
||||
cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
|
||||
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
|
||||
cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
|
||||
@@ -566,8 +558,6 @@ cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg
|
||||
cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=
|
||||
cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA=
|
||||
cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk=
|
||||
cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI=
|
||||
cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io=
|
||||
cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=
|
||||
cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=
|
||||
cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0=
|
||||
@@ -614,26 +604,24 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS
|
||||
cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=
|
||||
cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
|
||||
cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
|
||||
cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg=
|
||||
cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU=
|
||||
cosmossdk.io/api v0.7.6 h1:PC20PcXy1xYKH2KU4RMurVoFjjKkCgYRbVAD4PdqUuY=
|
||||
cosmossdk.io/api v0.7.6/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
|
||||
cosmossdk.io/client/v2 v2.0.0-beta.7 h1:O0PfZL5kC3Sp54wZASLNihQ612Gd6duMp11aM9wawNg=
|
||||
cosmossdk.io/client/v2 v2.0.0-beta.7/go.mod h1:TzwwrzeK+AfSVSESVEIOYO/9xuCh1fPv0HgeocmfVnM=
|
||||
cosmossdk.io/collections v1.2.0 h1:IesfVG8G/+FYCMVMP01frS/Cw99Omk5vBh3cHbO01Gg=
|
||||
cosmossdk.io/collections v1.2.0/go.mod h1:4NkMoYw6qRA8fnSH/yn1D/MOutr8qyQnwsO50Mz9ItU=
|
||||
cosmossdk.io/collections v0.4.0 h1:PFmwj2W8szgpD5nOd8GWH6AbYNi1f2J6akWXJ7P5t9s=
|
||||
cosmossdk.io/collections v0.4.0/go.mod h1:oa5lUING2dP+gdDquow+QjlF45eL1t4TJDypgGd+tv0=
|
||||
cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo=
|
||||
cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w=
|
||||
cosmossdk.io/depinject v1.2.0 h1:6NW/FSK1IkWTrX7XxUpBmX1QMBozpEI9SsWkKTBc5zw=
|
||||
cosmossdk.io/depinject v1.2.0/go.mod h1:pvitjtUxZZZTQESKNS9KhGjWVslJZxtO9VooRJYyPjk=
|
||||
cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo=
|
||||
cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k=
|
||||
cosmossdk.io/log v1.5.1 h1:wLwiYXmfrort/O+j6EkjF+HvbdrRQd+4cYCPKFSm+zM=
|
||||
cosmossdk.io/log v1.5.1/go.mod h1:5cXXBvfBkR2/BcXmosdCSLXllvgSjphrrDVdfVRmBGM=
|
||||
cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U=
|
||||
cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ=
|
||||
cosmossdk.io/depinject v1.1.0 h1:wLan7LG35VM7Yo6ov0jId3RHWCGRhe8E8bsuARorl5E=
|
||||
cosmossdk.io/depinject v1.1.0/go.mod h1:kkI5H9jCGHeKeYWXTqYdruogYrEeWvBQCw1Pj4/eCFI=
|
||||
cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
|
||||
cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
|
||||
cosmossdk.io/log v1.5.0 h1:dVdzPJW9kMrnAYyMf1duqacoidB9uZIl+7c6z0mnq0g=
|
||||
cosmossdk.io/log v1.5.0/go.mod h1:Tr46PUJjiUthlwQ+hxYtUtPn4D/oCZXAkYevBeh5+FI=
|
||||
cosmossdk.io/math v1.5.0 h1:sbOASxee9Zxdjd6OkzogvBZ25/hP929vdcYcBJQbkLc=
|
||||
cosmossdk.io/math v1.5.0/go.mod h1:AAwwBmUhqtk2nlku174JwSll+/DepUXW3rWIXN5q+Nw=
|
||||
cosmossdk.io/orm v1.0.0-beta.3 h1:XmffCwsIZE+y0sS4kEfRUfIgvJfGGn3HFKntZ91sWcU=
|
||||
cosmossdk.io/orm v1.0.0-beta.3/go.mod h1:KSH9lKA+0K++2OKECWwPAasKbUIEtZ7xYG+0ikChiyU=
|
||||
cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE=
|
||||
cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI=
|
||||
cosmossdk.io/tools/confix v0.1.2 h1:2hoM1oFCNisd0ltSAAZw2i4ponARPmlhuNu3yy0VwI4=
|
||||
cosmossdk.io/tools/confix v0.1.2/go.mod h1:7XfcbK9sC/KNgVGxgLM0BrFbVcR/+6Dg7MFfpx7duYo=
|
||||
cosmossdk.io/x/circuit v0.1.1 h1:KPJCnLChWrxD4jLwUiuQaf5mFD/1m7Omyo7oooefBVQ=
|
||||
@@ -642,10 +630,10 @@ cosmossdk.io/x/evidence v0.1.1 h1:Ks+BLTa3uftFpElLTDp9L76t2b58htjVbSZ86aoK/E4=
|
||||
cosmossdk.io/x/evidence v0.1.1/go.mod h1:OoDsWlbtuyqS70LY51aX8FBTvguQqvFrt78qL7UzeNc=
|
||||
cosmossdk.io/x/feegrant v0.1.1 h1:EKFWOeo/pup0yF0svDisWWKAA9Zags6Zd0P3nRvVvw8=
|
||||
cosmossdk.io/x/feegrant v0.1.1/go.mod h1:2GjVVxX6G2fta8LWj7pC/ytHjryA6MHAJroBWHFNiEQ=
|
||||
cosmossdk.io/x/nft v0.2.0 h1:cd8QGeThxtvspOYGu0WJX0ioI9YnUG4qNwo3/Ac03GM=
|
||||
cosmossdk.io/x/nft v0.2.0/go.mod h1:KsJBxkrPvcNRNLQYzlj7MHiJjSMw7MwU7p8/P9EyDwo=
|
||||
cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA=
|
||||
cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM=
|
||||
cosmossdk.io/x/nft v0.1.0 h1:VhcsFiEK33ODN27kxKLa0r/CeFd8laBfbDBwYqCyYCM=
|
||||
cosmossdk.io/x/nft v0.1.0/go.mod h1:ec4j4QAO4mJZ+45jeYRnW7awLHby1JZANqe1hNZ4S3g=
|
||||
cosmossdk.io/x/tx v0.13.7 h1:8WSk6B/OHJLYjiZeMKhq7DK7lHDMyK0UfDbBMxVmeOI=
|
||||
cosmossdk.io/x/tx v0.13.7/go.mod h1:V6DImnwJMTq5qFjeGWpXNiT/fjgE4HtmclRmTqRVM3w=
|
||||
cosmossdk.io/x/upgrade v0.1.4 h1:/BWJim24QHoXde8Bc64/2BSEB6W4eTydq0X/2f8+g38=
|
||||
cosmossdk.io/x/upgrade v0.1.4/go.mod h1:9v0Aj+fs97O+Ztw+tG3/tp5JSlrmT7IcFhAebQHmOPo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
@@ -665,20 +653,12 @@ github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dX
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
|
||||
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE=
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
|
||||
github.com/Jorropo/jsync v1.0.1 h1:6HgRolFZnsdfzRUj+ImB9og1JYOxQoReSywkHOGSaUU=
|
||||
github.com/Jorropo/jsync v1.0.1/go.mod h1:jCOZj3vrBCri3bSU3ErUYvevKlnbssrXeCivybS5ABQ=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
@@ -728,7 +708,6 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb
|
||||
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/asynkron/protoactor-go v0.0.0-20240822202345-3c0e61ca19c9 h1:mFWX0/oYqQ4Z+er0U56vA+ZPisr3kaYs1QsQetAVs6E=
|
||||
@@ -749,8 +728,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
|
||||
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s=
|
||||
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/biter777/countries v1.7.5 h1:MJ+n3+rSxWQdqVJU8eBy9RqcdH6ePPn4PJHocVWUa+Q=
|
||||
github.com/biter777/countries v1.7.5/go.mod h1:1HSpZ526mYqKJcpT5Ti1kcGQ0L0SrXWIaptUWjFfv2E=
|
||||
github.com/bits-and-blooms/bitset v1.24.0 h1:H4x4TuulnokZKvHLfzVRTHJfFfnHEeSYJizujEZvmAM=
|
||||
@@ -842,8 +821,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
|
||||
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
|
||||
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
|
||||
@@ -859,12 +836,12 @@ github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/e
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||
github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw=
|
||||
github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo=
|
||||
github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA=
|
||||
github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
|
||||
github.com/cockroachdb/pebble/v2 v2.1.0 h1:6KZvjSpWcEXZUvlLzTRC7T1A2G7r+bFskIzggklxixo=
|
||||
github.com/cockroachdb/pebble/v2 v2.1.0/go.mod h1:Aza05DCCc05ghIJZkB4Q/axv/JK9wx5cFwWcnhG0eGw=
|
||||
github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314=
|
||||
github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/cockroachdb/swiss v0.0.0-20250624142022-d6e517c1d961 h1:Nua446ru3juLHLZd4AwKNzClZgL1co3pUPGv3o8FlcA=
|
||||
github.com/cockroachdb/swiss v0.0.0-20250624142022-d6e517c1d961/go.mod h1:yBRu/cnL4ks9bgy4vAASdjIW+/xMlFwuHKqtmh3GZQg=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||
@@ -914,7 +891,7 @@ github.com/cosmos/keyring v1.2.0/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStK
|
||||
github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE=
|
||||
github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20241020182519-7843d2ba8fdf h1:dwGgBWn84wUS1pVikGiruW+x5XM4amhjaZO20vCjay4=
|
||||
@@ -1003,19 +980,11 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
|
||||
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.0/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/evmos/cosmos-sdk/store v0.0.0-20240718141609-414cbd051fbe h1:CKvjP3CcWckOiwffAARb9qe+t0+VWoVDiicYkQMvZfQ=
|
||||
github.com/evmos/cosmos-sdk/store v0.0.0-20240718141609-414cbd051fbe/go.mod h1:Bm6h8ZkYgVTytHK5vhHOMKw9OHyiumm3b1UbkYIJ/Ug=
|
||||
github.com/evmos/go-ethereum v1.10.26-evmos-rc2 h1:tYghk1ZZ8X4/OQ4YI9hvtm8aSN8OSqO0g9vo/sCMdBo=
|
||||
@@ -1049,8 +1018,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
@@ -1072,8 +1041,6 @@ github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmn
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
|
||||
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
@@ -1246,8 +1213,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18=
|
||||
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -1258,8 +1225,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
@@ -1271,8 +1238,8 @@ github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqE
|
||||
github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
|
||||
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
|
||||
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
|
||||
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
|
||||
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -1314,17 +1281,17 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-getter v1.7.9 h1:G9gcjrDixz7glqJ+ll5IWvggSBR+R0B54DSRt4qfdC4=
|
||||
github.com/hashicorp/go-getter v1.7.9/go.mod h1:dyFCmT1AQkDfOIt9NH8pw9XBDqNrIKJT5ylbpi7zPNE=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY=
|
||||
github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI=
|
||||
github.com/hashicorp/go-metrics v0.5.3 h1:M5uADWMOGCTUNU1YuC4hfknOeHNaX54LDm4oYSucoNE=
|
||||
github.com/hashicorp/go-metrics v0.5.3/go.mod h1:KEjodfebIOuBYSAe/bHTm+HChmKSxAOXPBieMLYozDE=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
|
||||
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
|
||||
github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y=
|
||||
github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
|
||||
@@ -1352,10 +1319,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
|
||||
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
||||
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
||||
@@ -1363,8 +1330,8 @@ github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXei
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c=
|
||||
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
||||
github.com/huandu/skiplist v1.2.1 h1:dTi93MgjwErA/8idWTzIw4Y1kZsMWx35fmI2c8Rij7w=
|
||||
github.com/huandu/skiplist v1.2.1/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w=
|
||||
github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw=
|
||||
github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
||||
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
@@ -1462,7 +1429,6 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
@@ -1609,6 +1575,8 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
@@ -1713,8 +1681,8 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk=
|
||||
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw=
|
||||
@@ -1775,8 +1743,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -1789,7 +1755,6 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
@@ -1807,7 +1772,6 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
@@ -1816,7 +1780,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
|
||||
@@ -1852,17 +1815,17 @@ github.com/rollchains/wasmd v0.50.0-evm/go.mod h1:DNg2PW+6ublDwg723H+p9NmopjDSMj
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||
github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
@@ -1900,21 +1863,19 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
|
||||
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
|
||||
github.com/strangelove-ventures/cosmos-evm v0.2.0 h1:Abua+Wsvna9njxTuRLX/d0QmQnKIpImf/VbKrxBuHlU=
|
||||
github.com/strangelove-ventures/cosmos-evm v0.2.0/go.mod h1:5sAR9ETde0vSs4b/FUE1MXA9cWZ4aPsDCm9zVb49/As=
|
||||
github.com/strangelove-ventures/cosmos-sdk v0.0.0-20250317212103-0767f8c5b1e5 h1:5v7j5P2QTOiV3Uftja+1bwwuyaGe/lpnsC7dZNgoo/U=
|
||||
@@ -2025,8 +1986,6 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
|
||||
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
|
||||
github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
|
||||
@@ -2049,18 +2008,14 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
@@ -2106,8 +2061,8 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
|
||||
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -2379,7 +2334,6 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -2392,7 +2346,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -2647,8 +2600,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
|
||||
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
|
||||
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
|
||||
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
|
||||
google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0=
|
||||
google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY=
|
||||
google.golang.org/api v0.186.0 h1:n2OPp+PPXX0Axh4GuSsL5QL8xQCTb2oDwyzPnQvqUug=
|
||||
google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -2795,8 +2748,8 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl
|
||||
google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
|
||||
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc=
|
||||
google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094 h1:6whtk83KtD3FkGrVb2hFXuQ+ZMbCNdakARIn/aHMmG8=
|
||||
google.golang.org/genproto v0.0.0-20240701130421-f6361c86f094/go.mod h1:Zs4wYw8z1zr6RNF4cwYb31mvN/EGaKAdQjNCF3DW6K4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||
@@ -2910,8 +2863,8 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
||||
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -2960,8 +2913,8 @@ modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
|
||||
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
|
||||
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
|
||||
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "sonrctl",
|
||||
"version": "0.1.0",
|
||||
"module": "src/index.ts",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"bin": {
|
||||
"sonrctl": "./src/index.ts"
|
||||
},
|
||||
"description": "Sonr Network Control CLI - Manage Sonr blockchain nodes, validators, and networks",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@iarna/toml": "^2.2.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Config command - Manage sonrctl configuration
|
||||
import { Logger } from '../lib/logger';
|
||||
import { ConfigManager } from '../lib/config';
|
||||
|
||||
export async function config(args: string[]) {
|
||||
const subcommand = args[0];
|
||||
|
||||
if (!subcommand || !['get', 'set', 'list'].includes(subcommand)) {
|
||||
Logger.error('Usage: sonrctl config <get|set|list> [key] [value]');
|
||||
Logger.info('');
|
||||
Logger.info('Examples:');
|
||||
Logger.info(' sonrctl config list # List all configuration');
|
||||
Logger.info(' sonrctl config get chain_id # Get a specific value');
|
||||
Logger.info(' sonrctl config set home ~/.sonr # Set a configuration value');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const manager = new ConfigManager();
|
||||
|
||||
try {
|
||||
if (subcommand === 'list') {
|
||||
await listConfig(manager);
|
||||
} else if (subcommand === 'get') {
|
||||
await getConfig(manager, args[1]);
|
||||
} else if (subcommand === 'set') {
|
||||
await setConfig(manager, args[1], args[2]);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`Config operation failed: ${error}`);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
manager.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function listConfig(manager: ConfigManager) {
|
||||
Logger.header('Sonrctl Configuration');
|
||||
|
||||
const allConfig = manager.getAll();
|
||||
|
||||
if (Object.keys(allConfig).length === 0) {
|
||||
Logger.info('No configuration set');
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.table(allConfig);
|
||||
}
|
||||
|
||||
async function getConfig(manager: ConfigManager, key?: string) {
|
||||
if (!key) {
|
||||
Logger.error('Key is required');
|
||||
Logger.info('Usage: sonrctl config get <key>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const value = manager.get(key);
|
||||
|
||||
if (value === null) {
|
||||
Logger.warn(`Configuration key '${key}' not found`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
async function setConfig(manager: ConfigManager, key?: string, value?: string) {
|
||||
if (!key || !value) {
|
||||
Logger.error('Key and value are required');
|
||||
Logger.info('Usage: sonrctl config set <key> <value>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
manager.set(key, value);
|
||||
Logger.success(`Set ${key} = ${value}`);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Init command - Initialize node configurations
|
||||
import { Logger } from '../lib/logger';
|
||||
import { ConfigManager } from '../lib/config';
|
||||
import { NodeManager } from '../lib/node';
|
||||
import { DEFAULT_CHAIN_ID, DEFAULT_HOME_DIR, DEFAULT_PORTS } from '../lib/constants';
|
||||
import type { NodeConfig } from '../types';
|
||||
|
||||
export async function init(args: string[]) {
|
||||
const subcommand = args[0];
|
||||
|
||||
if (!subcommand || !['validator', 'sentry', 'full'].includes(subcommand)) {
|
||||
Logger.error('Usage: sonrctl init <validator|sentry|full> <name> [options]');
|
||||
Logger.info('');
|
||||
Logger.info('Examples:');
|
||||
Logger.info(' sonrctl init validator val-naruto');
|
||||
Logger.info(' sonrctl init sentry sentry-naruto --home ~/.sonr/sentry');
|
||||
Logger.info(' sonrctl init full my-node --chain-id sonrtest_1-1');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const name = args[1];
|
||||
if (!name) {
|
||||
Logger.error('Node name is required');
|
||||
Logger.info('Usage: sonrctl init <validator|sentry|full> <name>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse options
|
||||
const options: Record<string, any> = {};
|
||||
for (let i = 2; i < args.length; i += 2) {
|
||||
const key = args[i].replace(/^--/, '');
|
||||
const value = args[i + 1];
|
||||
options[key] = value;
|
||||
}
|
||||
|
||||
const config = new ConfigManager();
|
||||
|
||||
// Build node configuration
|
||||
const nodeConfig: NodeConfig = {
|
||||
moniker: name,
|
||||
chainId: options['chain-id'] || config.get('chain_id') || DEFAULT_CHAIN_ID,
|
||||
home: options.home || `${DEFAULT_HOME_DIR}/${name}`,
|
||||
nodeType: subcommand as 'validator' | 'sentry' | 'full',
|
||||
ports: {
|
||||
rpc: parseInt(options['rpc-port']) || DEFAULT_PORTS.rpc,
|
||||
rest: parseInt(options['rest-port']) || DEFAULT_PORTS.rest,
|
||||
grpc: parseInt(options['grpc-port']) || DEFAULT_PORTS.grpc,
|
||||
grpcWeb: parseInt(options['grpc-web-port']) || DEFAULT_PORTS.grpcWeb,
|
||||
jsonRpc: parseInt(options['json-rpc-port']) || DEFAULT_PORTS.jsonRpc,
|
||||
jsonRpcWs: parseInt(options['json-rpc-ws-port']) || DEFAULT_PORTS.jsonRpcWs,
|
||||
},
|
||||
};
|
||||
|
||||
Logger.header(`Initialize ${nodeConfig.nodeType.toUpperCase()} Node`);
|
||||
|
||||
try {
|
||||
// Check if binary is available
|
||||
const binary = config.get('binary') || 'snrd';
|
||||
const available = await NodeManager.isBinaryAvailable(binary);
|
||||
|
||||
if (!available) {
|
||||
Logger.error(`Binary '${binary}' not found`);
|
||||
Logger.info('Install it using: sonrctl install');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if home directory already exists
|
||||
const homeExists = await Bun.file(nodeConfig.home).exists();
|
||||
if (homeExists) {
|
||||
Logger.warn(`Home directory already exists: ${nodeConfig.home}`);
|
||||
const response = await confirmPrompt('Overwrite existing configuration?');
|
||||
|
||||
if (!response) {
|
||||
Logger.info('Initialization cancelled');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Remove existing directory
|
||||
await Bun.$`rm -rf ${nodeConfig.home}`;
|
||||
}
|
||||
|
||||
// Initialize node
|
||||
await NodeManager.init(nodeConfig, binary);
|
||||
|
||||
// Register node in database
|
||||
config.addNode(name, nodeConfig.nodeType, nodeConfig.chainId, nodeConfig.home, nodeConfig.moniker);
|
||||
|
||||
Logger.success('Node initialized successfully!');
|
||||
Logger.table({
|
||||
Name: name,
|
||||
Type: nodeConfig.nodeType,
|
||||
'Chain ID': nodeConfig.chainId,
|
||||
Home: nodeConfig.home,
|
||||
Moniker: nodeConfig.moniker,
|
||||
});
|
||||
|
||||
Logger.info('');
|
||||
Logger.info('Next steps:');
|
||||
Logger.info(` 1. Configure genesis: ${nodeConfig.home}/config/genesis.json`);
|
||||
Logger.info(` 2. Start the node: sonrctl start ${name}`);
|
||||
Logger.info(` 3. Check status: sonrctl status ${name}`);
|
||||
} catch (error) {
|
||||
Logger.error(`Initialization failed: ${error}`);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
config.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Simple confirmation prompt
|
||||
async function confirmPrompt(message: string): Promise<boolean> {
|
||||
process.stdout.write(`${message} (y/N): `);
|
||||
|
||||
for await (const line of console) {
|
||||
const answer = line.trim().toLowerCase();
|
||||
return answer === 'y' || answer === 'yes';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Install command - Install or update snrd binary
|
||||
import { Logger } from '../lib/logger';
|
||||
import { ConfigManager } from '../lib/config';
|
||||
|
||||
const GITHUB_REPO = 'sonr-io/sonr';
|
||||
const BINARY_NAME = 'snrd';
|
||||
|
||||
export async function install(args: string[]) {
|
||||
Logger.header('Install Sonr Node Binary');
|
||||
|
||||
const version = args[0] || 'latest';
|
||||
|
||||
Logger.info(`Installing ${BINARY_NAME} version: ${version}`);
|
||||
|
||||
try {
|
||||
// Check if running on supported platform
|
||||
const platform = process.platform;
|
||||
const arch = process.arch;
|
||||
|
||||
Logger.info(`Detected platform: ${platform}/${arch}`);
|
||||
|
||||
if (version === 'latest') {
|
||||
// Get latest release from GitHub
|
||||
Logger.step(1, 3, 'Fetching latest release information');
|
||||
const response = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch release information');
|
||||
}
|
||||
|
||||
const release = await response.json();
|
||||
const latestVersion = release.tag_name;
|
||||
|
||||
Logger.info(`Latest version: ${latestVersion}`);
|
||||
|
||||
// Download binary
|
||||
Logger.step(2, 3, `Downloading ${BINARY_NAME}`);
|
||||
const assetName = `${BINARY_NAME}-${platform}-${arch}`;
|
||||
const asset = release.assets.find((a: any) => a.name.includes(assetName));
|
||||
|
||||
if (!asset) {
|
||||
throw new Error(`No binary found for ${platform}/${arch}`);
|
||||
}
|
||||
|
||||
const stop = Logger.loading('Downloading binary...');
|
||||
const binaryResponse = await fetch(asset.browser_download_url);
|
||||
|
||||
if (!binaryResponse.ok) {
|
||||
stop();
|
||||
throw new Error('Failed to download binary');
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
// Save binary to /usr/local/bin or ~/.local/bin
|
||||
Logger.step(3, 3, 'Installing binary');
|
||||
const binaryData = await binaryResponse.arrayBuffer();
|
||||
|
||||
try {
|
||||
// Try to install to /usr/local/bin first (system-wide)
|
||||
await Bun.write(`/usr/local/bin/${BINARY_NAME}`, binaryData);
|
||||
await Bun.$`chmod +x /usr/local/bin/${BINARY_NAME}`;
|
||||
Logger.success(`Installed ${BINARY_NAME} to /usr/local/bin`);
|
||||
} catch {
|
||||
// Fall back to ~/.local/bin (user-level)
|
||||
const localBinDir = `${process.env.HOME}/.local/bin`;
|
||||
await Bun.$`mkdir -p ${localBinDir}`;
|
||||
await Bun.write(`${localBinDir}/${BINARY_NAME}`, binaryData);
|
||||
await Bun.$`chmod +x ${localBinDir}/${BINARY_NAME}`;
|
||||
Logger.success(`Installed ${BINARY_NAME} to ${localBinDir}`);
|
||||
Logger.warn(`Make sure ${localBinDir} is in your PATH`);
|
||||
}
|
||||
|
||||
// Verify installation
|
||||
const versionCheck = await Bun.$`${BINARY_NAME} version`.quiet();
|
||||
const installedVersion = (await versionCheck.text()).trim();
|
||||
|
||||
Logger.success(`Successfully installed ${BINARY_NAME} ${installedVersion}`);
|
||||
|
||||
// Update config
|
||||
const config = new ConfigManager();
|
||||
config.set('binary', BINARY_NAME);
|
||||
config.set('version', installedVersion);
|
||||
config.close();
|
||||
} else {
|
||||
throw new Error('Specific version installation not yet supported. Use "latest" for now.');
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`Installation failed: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// Start command - Start nodes or network
|
||||
import { Logger } from '../lib/logger';
|
||||
import { ConfigManager } from '../lib/config';
|
||||
import { DockerManager } from '../lib/docker';
|
||||
import { NodeManager } from '../lib/node';
|
||||
import { join } from 'path';
|
||||
|
||||
export async function start(args: string[]) {
|
||||
const subcommand = args[0];
|
||||
|
||||
if (!subcommand) {
|
||||
Logger.error('Usage: sonrctl start <node-name|network>');
|
||||
Logger.info('');
|
||||
Logger.info('Examples:');
|
||||
Logger.info(' sonrctl start val-naruto # Start a specific node');
|
||||
Logger.info(' sonrctl start network # Start entire testnet with docker-compose');
|
||||
Logger.info(' sonrctl start network --detach # Start network in background');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = new ConfigManager();
|
||||
|
||||
try {
|
||||
if (subcommand === 'network') {
|
||||
await startNetwork(args.slice(1));
|
||||
} else {
|
||||
await startNode(subcommand, config);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`Failed to start: ${error}`);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
config.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function startNode(name: string, config: ConfigManager) {
|
||||
Logger.header(`Start Node: ${name}`);
|
||||
|
||||
// Get node from registry
|
||||
const node = config.getNode(name);
|
||||
|
||||
if (!node) {
|
||||
Logger.error(`Node '${name}' not found`);
|
||||
Logger.info('Initialize it first using: sonrctl init <type> <name>');
|
||||
Logger.info('');
|
||||
Logger.info('Registered nodes:');
|
||||
const nodes = config.listNodes();
|
||||
if (nodes.length === 0) {
|
||||
Logger.info(' (none)');
|
||||
} else {
|
||||
nodes.forEach((n: any) => Logger.info(` - ${n.name} (${n.type})`));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const binary = config.get('binary') || 'snrd';
|
||||
|
||||
Logger.info(`Starting ${node.type} node: ${node.moniker}`);
|
||||
Logger.table({
|
||||
Name: node.name,
|
||||
Type: node.type,
|
||||
'Chain ID': node.chain_id,
|
||||
Home: node.home,
|
||||
});
|
||||
|
||||
// Check if binary is available
|
||||
const available = await NodeManager.isBinaryAvailable(binary);
|
||||
if (!available) {
|
||||
Logger.error(`Binary '${binary}' not found`);
|
||||
Logger.info('Install it using: sonrctl install');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Logger.info('');
|
||||
Logger.info('Starting node...');
|
||||
Logger.info('Press Ctrl+C to stop');
|
||||
Logger.info('');
|
||||
|
||||
// Start node (this will block)
|
||||
await NodeManager.start(node.home, node.chain_id, binary);
|
||||
}
|
||||
|
||||
async function startNetwork(args: string[]) {
|
||||
Logger.header('Start Sonr Testnet Network');
|
||||
|
||||
// Check if docker is available
|
||||
const dockerAvailable = await DockerManager.isAvailable();
|
||||
if (!dockerAvailable) {
|
||||
Logger.error('Docker is not available or not running');
|
||||
Logger.info('Install Docker: https://docs.docker.com/get-docker/');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const composeAvailable = await DockerManager.isComposeAvailable();
|
||||
if (!composeAvailable) {
|
||||
Logger.error('docker-compose is not available');
|
||||
Logger.info('Install docker-compose: https://docs.docker.com/compose/install/');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse options
|
||||
const detached = args.includes('--detach') || args.includes('-d');
|
||||
|
||||
// Find docker-compose file
|
||||
const cwd = process.cwd();
|
||||
const composePath = join(cwd, 'networks', 'testnet', 'docker-compose.yml');
|
||||
|
||||
const composeExists = await Bun.file(composePath).exists();
|
||||
if (!composeExists) {
|
||||
Logger.error(`docker-compose.yml not found at: ${composePath}`);
|
||||
Logger.info('Make sure you are in the sonr repository root directory');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Logger.info('Starting network with docker-compose...');
|
||||
Logger.info(`Compose file: ${composePath}`);
|
||||
Logger.info('');
|
||||
|
||||
try {
|
||||
await DockerManager.composeUp(composePath, detached);
|
||||
|
||||
if (detached) {
|
||||
Logger.success('Network started in background');
|
||||
Logger.info('');
|
||||
Logger.info('Check status with: docker ps');
|
||||
Logger.info('View logs with: docker logs -f <container-name>');
|
||||
Logger.info('Stop network with: sonrctl stop network');
|
||||
} else {
|
||||
Logger.success('Network started');
|
||||
Logger.info('Press Ctrl+C to stop');
|
||||
}
|
||||
|
||||
// Show running containers
|
||||
Logger.info('');
|
||||
Logger.section('Running Containers');
|
||||
const containers = await DockerManager.listContainers('sonr-testnet');
|
||||
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
Logger.info(` ${container.name}: ${container.status}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`Failed to start network: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// Status command - Check node and network status
|
||||
import { Logger } from '../lib/logger';
|
||||
import { ConfigManager } from '../lib/config';
|
||||
import { DockerManager } from '../lib/docker';
|
||||
import { NodeManager } from '../lib/node';
|
||||
|
||||
export async function status(args: string[]) {
|
||||
const target = args[0];
|
||||
|
||||
if (!target) {
|
||||
// Show overall status
|
||||
await showOverallStatus();
|
||||
} else if (target === 'network') {
|
||||
await showNetworkStatus();
|
||||
} else {
|
||||
await showNodeStatus(target);
|
||||
}
|
||||
}
|
||||
|
||||
async function showOverallStatus() {
|
||||
Logger.header('Sonr Network Status');
|
||||
|
||||
const config = new ConfigManager();
|
||||
|
||||
// Show registered nodes
|
||||
Logger.section('Registered Nodes');
|
||||
const nodes = config.listNodes();
|
||||
|
||||
if (nodes.length === 0) {
|
||||
Logger.info('No nodes registered');
|
||||
Logger.info('Initialize a node using: sonrctl init <type> <name>');
|
||||
} else {
|
||||
console.log();
|
||||
console.log(' Name Type Chain ID Home');
|
||||
console.log(' ───────────────── ────────── ──────────────── ─────────────────────');
|
||||
|
||||
for (const node of nodes) {
|
||||
const name = node.name.padEnd(17);
|
||||
const type = node.type.padEnd(10);
|
||||
const chainId = node.chain_id.padEnd(16);
|
||||
console.log(` ${name} ${type} ${chainId} ${node.home}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// Show docker containers
|
||||
const dockerAvailable = await DockerManager.isAvailable();
|
||||
if (dockerAvailable) {
|
||||
Logger.section('Docker Containers');
|
||||
const containers = await DockerManager.listContainers();
|
||||
|
||||
if (containers.length === 0) {
|
||||
Logger.info('No containers running');
|
||||
} else {
|
||||
console.log();
|
||||
console.log(' Name Status Image');
|
||||
console.log(' ────────────────────── ──────────────────────── ────────────────────');
|
||||
|
||||
for (const container of containers) {
|
||||
const name = container.name.padEnd(22);
|
||||
const status = container.status.substring(0, 24).padEnd(24);
|
||||
console.log(` ${name} ${status} ${container.image}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
config.close();
|
||||
}
|
||||
|
||||
async function showNetworkStatus() {
|
||||
Logger.header('Testnet Network Status');
|
||||
|
||||
const dockerAvailable = await DockerManager.isAvailable();
|
||||
if (!dockerAvailable) {
|
||||
Logger.error('Docker is not available');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get all testnet containers
|
||||
const containers = await DockerManager.listContainers('sonr-testnet');
|
||||
|
||||
if (containers.length === 0) {
|
||||
Logger.info('No network containers found');
|
||||
Logger.info('Start the network using: sonrctl start network');
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.section('Network Containers');
|
||||
console.log();
|
||||
|
||||
for (const container of containers) {
|
||||
const { running, status } = await DockerManager.getContainerStatus(container.name);
|
||||
const statusIcon = running ? '✓' : '✗';
|
||||
const statusColor = running ? '\x1b[32m' : '\x1b[31m';
|
||||
|
||||
console.log(` ${statusColor}${statusIcon}\x1b[0m ${container.name}`);
|
||||
console.log(` Status: ${status}`);
|
||||
console.log(` Image: ${container.image}`);
|
||||
|
||||
// Try to get node status if running
|
||||
if (running) {
|
||||
try {
|
||||
const rpcPort = container.name.includes('sentry') ? '26657' : null;
|
||||
if (rpcPort) {
|
||||
const nodeStatus = await NodeManager.getStatus(`http://localhost:${rpcPort}`);
|
||||
const syncInfo = nodeStatus.result.sync_info;
|
||||
const catchingUp = syncInfo.catching_up ? 'Yes' : 'No';
|
||||
|
||||
console.log(` Height: ${syncInfo.latest_block_height}`);
|
||||
console.log(` Syncing: ${catchingUp}`);
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors getting node status
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
async function showNodeStatus(name: string) {
|
||||
Logger.header(`Node Status: ${name}`);
|
||||
|
||||
const config = new ConfigManager();
|
||||
const node = config.getNode(name);
|
||||
|
||||
if (!node) {
|
||||
Logger.error(`Node '${name}' not found`);
|
||||
Logger.info('List registered nodes using: sonrctl status');
|
||||
config.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Show node info
|
||||
Logger.section('Node Information');
|
||||
Logger.table({
|
||||
Name: node.name,
|
||||
Type: node.type,
|
||||
'Chain ID': node.chain_id,
|
||||
Home: node.home,
|
||||
Moniker: node.moniker,
|
||||
});
|
||||
|
||||
// Check docker container status
|
||||
const dockerAvailable = await DockerManager.isAvailable();
|
||||
if (dockerAvailable) {
|
||||
const { running, status } = await DockerManager.getContainerStatus(name);
|
||||
|
||||
Logger.section('Container Status');
|
||||
Logger.info(`Running: ${running ? 'Yes' : 'No'}`);
|
||||
Logger.info(`Status: ${status}`);
|
||||
|
||||
if (running) {
|
||||
// Try to get node status
|
||||
try {
|
||||
const nodeStatus = await NodeManager.getStatus('http://localhost:26657');
|
||||
const syncInfo = nodeStatus.result.sync_info;
|
||||
|
||||
Logger.section('Blockchain Status');
|
||||
Logger.table({
|
||||
'Latest Height': syncInfo.latest_block_height,
|
||||
'Latest Block Time': new Date(syncInfo.latest_block_time).toLocaleString(),
|
||||
'Catching Up': syncInfo.catching_up ? 'Yes' : 'No',
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.warn('Unable to fetch node status');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.close();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Stop command - Stop nodes or network
|
||||
import { Logger } from '../lib/logger';
|
||||
import { ConfigManager } from '../lib/config';
|
||||
import { DockerManager } from '../lib/docker';
|
||||
import { join } from 'path';
|
||||
|
||||
export async function stop(args: string[]) {
|
||||
const target = args[0];
|
||||
|
||||
if (!target) {
|
||||
Logger.error('Usage: sonrctl stop <node-name|network|all>');
|
||||
Logger.info('');
|
||||
Logger.info('Examples:');
|
||||
Logger.info(' sonrctl stop val-naruto # Stop a specific node container');
|
||||
Logger.info(' sonrctl stop network # Stop entire testnet network');
|
||||
Logger.info(' sonrctl stop all # Stop all running containers');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
if (target === 'network') {
|
||||
await stopNetwork();
|
||||
} else if (target === 'all') {
|
||||
await stopAll();
|
||||
} else {
|
||||
await stopNode(target);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`Failed to stop: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopNode(name: string) {
|
||||
Logger.header(`Stop Node: ${name}`);
|
||||
|
||||
const dockerAvailable = await DockerManager.isAvailable();
|
||||
if (!dockerAvailable) {
|
||||
Logger.error('Docker is not available');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if container is running
|
||||
const running = await DockerManager.isContainerRunning(name);
|
||||
if (!running) {
|
||||
Logger.warn(`Container '${name}' is not running`);
|
||||
return;
|
||||
}
|
||||
|
||||
await DockerManager.stopContainer(name);
|
||||
}
|
||||
|
||||
async function stopNetwork() {
|
||||
Logger.header('Stop Sonr Testnet Network');
|
||||
|
||||
const dockerAvailable = await DockerManager.isAvailable();
|
||||
if (!dockerAvailable) {
|
||||
Logger.error('Docker is not available');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Find docker-compose file
|
||||
const cwd = process.cwd();
|
||||
const composePath = join(cwd, 'networks', 'testnet', 'docker-compose.yml');
|
||||
|
||||
const composeExists = await Bun.file(composePath).exists();
|
||||
if (!composeExists) {
|
||||
Logger.error(`docker-compose.yml not found at: ${composePath}`);
|
||||
Logger.info('Make sure you are in the sonr repository root directory');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await DockerManager.composeDown(composePath);
|
||||
}
|
||||
|
||||
async function stopAll() {
|
||||
Logger.header('Stop All Containers');
|
||||
|
||||
const dockerAvailable = await DockerManager.isAvailable();
|
||||
if (!dockerAvailable) {
|
||||
Logger.error('Docker is not available');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get all containers
|
||||
const containers = await DockerManager.listContainers();
|
||||
|
||||
if (containers.length === 0) {
|
||||
Logger.info('No running containers found');
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info(`Found ${containers.length} containers`);
|
||||
|
||||
for (const container of containers) {
|
||||
if (container.status.startsWith('Up')) {
|
||||
Logger.info(`Stopping ${container.name}...`);
|
||||
await DockerManager.stopContainer(container.name);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.success('All containers stopped');
|
||||
}
|
||||
Executable
+161
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
// Sonrctl - Sonr Network Control CLI
|
||||
import { Logger } from './lib/logger';
|
||||
import { install } from './commands/install';
|
||||
import { init } from './commands/init';
|
||||
import { start } from './commands/start';
|
||||
import { stop } from './commands/stop';
|
||||
import { status } from './commands/status';
|
||||
import { config } from './commands/config';
|
||||
|
||||
const VERSION = '0.1.0';
|
||||
|
||||
// Main CLI router
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length === 0 || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
|
||||
showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (args[0] === 'version' || args[0] === '--version' || args[0] === '-v') {
|
||||
console.log(VERSION);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const command = args[0];
|
||||
const commandArgs = args.slice(1);
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case 'install':
|
||||
await install(commandArgs);
|
||||
break;
|
||||
|
||||
case 'init':
|
||||
await init(commandArgs);
|
||||
break;
|
||||
|
||||
case 'start':
|
||||
await start(commandArgs);
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
await stop(commandArgs);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
await status(commandArgs);
|
||||
break;
|
||||
|
||||
case 'config':
|
||||
await config(commandArgs);
|
||||
break;
|
||||
|
||||
default:
|
||||
Logger.error(`Unknown command: ${command}`);
|
||||
Logger.info('Run "sonrctl help" for usage information');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`Command failed: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp() {
|
||||
const COLORS = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
cyan: '\x1b[36m',
|
||||
blue: '\x1b[34m',
|
||||
gray: '\x1b[90m',
|
||||
};
|
||||
|
||||
console.log(`
|
||||
${COLORS.bright}${COLORS.cyan}sonrctl${COLORS.reset} - Sonr Network Control CLI
|
||||
|
||||
${COLORS.bright}USAGE${COLORS.reset}
|
||||
${COLORS.cyan}sonrctl${COLORS.reset} <command> [options]
|
||||
|
||||
${COLORS.bright}COMMANDS${COLORS.reset}
|
||||
${COLORS.cyan}install${COLORS.reset} [version]
|
||||
Install or update the snrd binary
|
||||
${COLORS.gray}Example: sonrctl install latest${COLORS.reset}
|
||||
|
||||
${COLORS.cyan}init${COLORS.reset} <validator|sentry|full> <name> [options]
|
||||
Initialize a new node configuration
|
||||
${COLORS.gray}Example: sonrctl init validator val-naruto${COLORS.reset}
|
||||
${COLORS.gray}Options:${COLORS.reset}
|
||||
${COLORS.gray} --chain-id <id> Chain ID (default: sonrtest_1-1)${COLORS.reset}
|
||||
${COLORS.gray} --home <path> Home directory${COLORS.reset}
|
||||
${COLORS.gray} --rpc-port <port> RPC port (default: 26657)${COLORS.reset}
|
||||
${COLORS.gray} --rest-port <port> REST API port (default: 1317)${COLORS.reset}
|
||||
${COLORS.gray} --grpc-port <port> gRPC port (default: 9090)${COLORS.reset}
|
||||
|
||||
${COLORS.cyan}start${COLORS.reset} <node-name|network> [options]
|
||||
Start a node or the entire network
|
||||
${COLORS.gray}Example: sonrctl start val-naruto${COLORS.reset}
|
||||
${COLORS.gray}Example: sonrctl start network${COLORS.reset}
|
||||
${COLORS.gray}Options:${COLORS.reset}
|
||||
${COLORS.gray} --detach, -d Run in background${COLORS.reset}
|
||||
|
||||
${COLORS.cyan}stop${COLORS.reset} <node-name|network|all>
|
||||
Stop a node, network, or all containers
|
||||
${COLORS.gray}Example: sonrctl stop val-naruto${COLORS.reset}
|
||||
${COLORS.gray}Example: sonrctl stop network${COLORS.reset}
|
||||
|
||||
${COLORS.cyan}status${COLORS.reset} [node-name|network]
|
||||
Check status of nodes and network
|
||||
${COLORS.gray}Example: sonrctl status${COLORS.reset}
|
||||
${COLORS.gray}Example: sonrctl status val-naruto${COLORS.reset}
|
||||
|
||||
${COLORS.cyan}config${COLORS.reset} <get|set|list> [key] [value]
|
||||
Manage sonrctl configuration
|
||||
${COLORS.gray}Example: sonrctl config list${COLORS.reset}
|
||||
${COLORS.gray}Example: sonrctl config get chain_id${COLORS.reset}
|
||||
${COLORS.gray}Example: sonrctl config set home ~/.sonr${COLORS.reset}
|
||||
|
||||
${COLORS.cyan}help${COLORS.reset}
|
||||
Show this help message
|
||||
|
||||
${COLORS.cyan}version${COLORS.reset}
|
||||
Show version information
|
||||
|
||||
${COLORS.bright}GLOBAL OPTIONS${COLORS.reset}
|
||||
${COLORS.gray}-h, --help Show help${COLORS.reset}
|
||||
${COLORS.gray}-v, --version Show version${COLORS.reset}
|
||||
|
||||
${COLORS.bright}EXAMPLES${COLORS.reset}
|
||||
${COLORS.gray}# Install the snrd binary${COLORS.reset}
|
||||
${COLORS.blue}sonrctl install${COLORS.reset}
|
||||
|
||||
${COLORS.gray}# Initialize a validator node${COLORS.reset}
|
||||
${COLORS.blue}sonrctl init validator val-naruto --chain-id sonrtest_1-1${COLORS.reset}
|
||||
|
||||
${COLORS.gray}# Start the entire testnet network${COLORS.reset}
|
||||
${COLORS.blue}sonrctl start network --detach${COLORS.reset}
|
||||
|
||||
${COLORS.gray}# Check network status${COLORS.reset}
|
||||
${COLORS.blue}sonrctl status network${COLORS.reset}
|
||||
|
||||
${COLORS.gray}# Stop the network${COLORS.reset}
|
||||
${COLORS.blue}sonrctl stop network${COLORS.reset}
|
||||
|
||||
${COLORS.bright}CONFIGURATION${COLORS.reset}
|
||||
Config directory: ${COLORS.cyan}~/.config/sonr${COLORS.reset}
|
||||
Node home: ${COLORS.cyan}~/.sonr${COLORS.reset}
|
||||
|
||||
${COLORS.bright}MORE INFO${COLORS.reset}
|
||||
Docs: ${COLORS.blue}https://docs.sonr.io${COLORS.reset}
|
||||
Repo: ${COLORS.blue}https://github.com/sonr-io/sonr${COLORS.reset}
|
||||
`);
|
||||
}
|
||||
|
||||
// Run the CLI
|
||||
main().catch((error) => {
|
||||
Logger.error(`Fatal error: ${error}`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
// Configuration management using bun:sqlite
|
||||
import { Database } from 'bun:sqlite';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { DEFAULT_CHAIN_ID, DEFAULT_HOME_DIR, DEFAULT_BINARY } from './constants';
|
||||
|
||||
const CONFIG_DIR = join(homedir(), '.config', 'sonr');
|
||||
const CONFIG_DB = join(CONFIG_DIR, 'config.db');
|
||||
|
||||
export class ConfigManager {
|
||||
private db: Database;
|
||||
|
||||
constructor() {
|
||||
// Ensure config directory exists
|
||||
if (!Bun.file(CONFIG_DIR).size) {
|
||||
Bun.spawnSync(['mkdir', '-p', CONFIG_DIR]);
|
||||
}
|
||||
|
||||
this.db = new Database(CONFIG_DB, { create: true });
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database tables
|
||||
*/
|
||||
private init() {
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
name TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL,
|
||||
chain_id TEXT NOT NULL,
|
||||
home TEXT NOT NULL,
|
||||
moniker TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// Set defaults if not exists
|
||||
const defaults = {
|
||||
chain_id: DEFAULT_CHAIN_ID,
|
||||
home: DEFAULT_HOME_DIR,
|
||||
binary: DEFAULT_BINARY,
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(defaults)) {
|
||||
const existing = this.get(key);
|
||||
if (!existing) {
|
||||
this.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configuration value
|
||||
*/
|
||||
get(key: string): string | null {
|
||||
const result = this.db.query('SELECT value FROM config WHERE key = ?').get(key) as { value: string } | null;
|
||||
return result?.value || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a configuration value
|
||||
*/
|
||||
set(key: string, value: string) {
|
||||
this.db.run('INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)', [key, value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configuration
|
||||
*/
|
||||
getAll(): Record<string, string> {
|
||||
const rows = this.db.query('SELECT key, value FROM config').all() as Array<{ key: string; value: string }>;
|
||||
return Object.fromEntries(rows.map(row => [row.key, row.value]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the registry
|
||||
*/
|
||||
addNode(name: string, type: string, chainId: string, home: string, moniker: string) {
|
||||
this.db.run(
|
||||
'INSERT OR REPLACE INTO nodes (name, type, chain_id, home, moniker, created_at) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[name, type, chainId, home, moniker, Date.now()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a node from the registry
|
||||
*/
|
||||
getNode(name: string): any {
|
||||
return this.db.query('SELECT * FROM nodes WHERE name = ?').get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all nodes
|
||||
*/
|
||||
listNodes(): any[] {
|
||||
return this.db.query('SELECT * FROM nodes ORDER BY created_at DESC').all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a node
|
||||
*/
|
||||
removeNode(name: string) {
|
||||
this.db.run('DELETE FROM nodes WHERE name = ?', [name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close database connection
|
||||
*/
|
||||
close() {
|
||||
this.db.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Constants for sonrctl
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
export const DEFAULT_CHAIN_ID = 'sonrtest_1-1';
|
||||
export const DEFAULT_DENOM = 'usnr';
|
||||
export const DEFAULT_HOME_DIR = join(homedir(), '.sonr');
|
||||
export const DEFAULT_KEYRING_BACKEND = 'test';
|
||||
export const DEFAULT_BINARY = 'snrd';
|
||||
export const DEFAULT_DOCKER_IMAGE = 'onsonr/snrd:latest';
|
||||
|
||||
export const DEFAULT_PORTS = {
|
||||
rpc: 26657,
|
||||
rest: 1317,
|
||||
grpc: 9090,
|
||||
grpcWeb: 9091,
|
||||
jsonRpc: 8545,
|
||||
jsonRpcWs: 8546,
|
||||
p2p: 26656,
|
||||
};
|
||||
|
||||
export const CONSENSUS_TIMEOUTS = {
|
||||
propose: '5s',
|
||||
prevote: '1s',
|
||||
precommit: '1s',
|
||||
commit: '1s',
|
||||
};
|
||||
|
||||
export const TESTNET_VALIDATORS = [
|
||||
{ name: 'val-naruto', network: 'net-naruto' },
|
||||
{ name: 'val-senku', network: 'net-senku' },
|
||||
{ name: 'val-yaeger', network: 'net-yaeger' },
|
||||
];
|
||||
|
||||
export const TESTNET_SENTRIES = [
|
||||
{ name: 'sentry-naruto', network: 'net-naruto', validator: 'val-naruto' },
|
||||
{ name: 'sentry-senku', network: 'net-senku', validator: 'val-senku' },
|
||||
{ name: 'sentry-yaeger', network: 'net-yaeger', validator: 'val-yaeger' },
|
||||
];
|
||||
@@ -0,0 +1,165 @@
|
||||
// Docker utilities using Bun.$
|
||||
import type { DockerNode } from '../types';
|
||||
import { Logger } from './logger';
|
||||
|
||||
export class DockerManager {
|
||||
/**
|
||||
* Check if Docker is installed and running
|
||||
*/
|
||||
static async isAvailable(): Promise<boolean> {
|
||||
try {
|
||||
const result = await Bun.$`docker info`.quiet();
|
||||
return result.exitCode === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if docker-compose is available
|
||||
*/
|
||||
static async isComposeAvailable(): Promise<boolean> {
|
||||
try {
|
||||
const result = await Bun.$`docker compose version`.quiet();
|
||||
return result.exitCode === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull a Docker image
|
||||
*/
|
||||
static async pullImage(image: string): Promise<void> {
|
||||
Logger.info(`Pulling Docker image: ${image}`);
|
||||
const stop = Logger.loading('Pulling image...');
|
||||
|
||||
try {
|
||||
await Bun.$`docker pull ${image}`.quiet();
|
||||
stop();
|
||||
Logger.success(`Image pulled: ${image}`);
|
||||
} catch (error) {
|
||||
stop();
|
||||
throw new Error(`Failed to pull image: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a container is running
|
||||
*/
|
||||
static async isContainerRunning(name: string): Promise<boolean> {
|
||||
try {
|
||||
const result = await Bun.$`docker ps --filter name=${name} --format {{.Names}}`.quiet();
|
||||
const output = await result.text();
|
||||
return output.trim() === name;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a container
|
||||
*/
|
||||
static async startContainer(name: string): Promise<void> {
|
||||
Logger.info(`Starting container: ${name}`);
|
||||
await Bun.$`docker start ${name}`;
|
||||
Logger.success(`Container started: ${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a container
|
||||
*/
|
||||
static async stopContainer(name: string): Promise<void> {
|
||||
Logger.info(`Stopping container: ${name}`);
|
||||
await Bun.$`docker stop ${name}`;
|
||||
Logger.success(`Container stopped: ${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a container
|
||||
*/
|
||||
static async removeContainer(name: string, force: boolean = false): Promise<void> {
|
||||
Logger.info(`Removing container: ${name}`);
|
||||
const forceFlag = force ? '-f' : '';
|
||||
await Bun.$`docker rm ${forceFlag} ${name}`;
|
||||
Logger.success(`Container removed: ${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command in a container
|
||||
*/
|
||||
static async exec(container: string, command: string): Promise<string> {
|
||||
const result = await Bun.$`docker exec ${container} sh -c ${command}`.quiet();
|
||||
return await result.text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get container logs
|
||||
*/
|
||||
static async logs(container: string, tail: number = 50): Promise<string> {
|
||||
const result = await Bun.$`docker logs ${container} --tail ${tail}`.quiet();
|
||||
return await result.text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run docker-compose command
|
||||
*/
|
||||
static async composeUp(composePath: string, detached: boolean = true): Promise<void> {
|
||||
const cwd = Bun.file(composePath).name ? composePath.split('/').slice(0, -1).join('/') : composePath;
|
||||
const flags = detached ? '-d' : '';
|
||||
|
||||
Logger.info('Starting network with docker-compose...');
|
||||
await Bun.$`cd ${cwd} && docker compose up ${flags}`;
|
||||
Logger.success('Network started successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop docker-compose services
|
||||
*/
|
||||
static async composeDown(composePath: string): Promise<void> {
|
||||
const cwd = Bun.file(composePath).name ? composePath.split('/').slice(0, -1).join('/') : composePath;
|
||||
|
||||
Logger.info('Stopping network...');
|
||||
await Bun.$`cd ${cwd} && docker compose down`;
|
||||
Logger.success('Network stopped successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get container status
|
||||
*/
|
||||
static async getContainerStatus(name: string): Promise<{ running: boolean; status: string }> {
|
||||
try {
|
||||
const result = await Bun.$`docker ps -a --filter name=${name} --format {{.Status}}`.quiet();
|
||||
const status = (await result.text()).trim();
|
||||
const running = status.startsWith('Up');
|
||||
return { running, status };
|
||||
} catch {
|
||||
return { running: false, status: 'Not found' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all containers with a prefix
|
||||
*/
|
||||
static async listContainers(prefix?: string): Promise<Array<{ name: string; status: string; image: string }>> {
|
||||
try {
|
||||
const filter = prefix ? `--filter name=${prefix}` : '';
|
||||
const result = await Bun.$`docker ps -a ${filter} --format {{.Names}}|||{{.Status}}|||{{.Image}}`.quiet();
|
||||
const output = await result.text();
|
||||
|
||||
if (!output.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return output
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const [name, status, image] = line.split('|||');
|
||||
return { name, status, image };
|
||||
});
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Beautiful logging utilities for sonrctl
|
||||
|
||||
const COLORS = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
|
||||
// Foreground colors
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
cyan: '\x1b[36m',
|
||||
white: '\x1b[37m',
|
||||
gray: '\x1b[90m',
|
||||
} as const;
|
||||
|
||||
const ICONS = {
|
||||
info: 'ℹ',
|
||||
success: '✓',
|
||||
warning: '⚠',
|
||||
error: '✗',
|
||||
rocket: '🚀',
|
||||
gear: '⚙',
|
||||
chain: '⛓',
|
||||
node: '🔗',
|
||||
docker: '🐳',
|
||||
};
|
||||
|
||||
export class Logger {
|
||||
static info(message: string, ...args: any[]) {
|
||||
console.log(`${COLORS.blue}${ICONS.info}${COLORS.reset} ${message}`, ...args);
|
||||
}
|
||||
|
||||
static success(message: string, ...args: any[]) {
|
||||
console.log(`${COLORS.green}${ICONS.success}${COLORS.reset} ${message}`, ...args);
|
||||
}
|
||||
|
||||
static warn(message: string, ...args: any[]) {
|
||||
console.log(`${COLORS.yellow}${ICONS.warning}${COLORS.reset} ${message}`, ...args);
|
||||
}
|
||||
|
||||
static error(message: string, ...args: any[]) {
|
||||
console.error(`${COLORS.red}${ICONS.error}${COLORS.reset} ${message}`, ...args);
|
||||
}
|
||||
|
||||
static debug(message: string, ...args: any[]) {
|
||||
if (process.env.DEBUG) {
|
||||
console.log(`${COLORS.gray}[DEBUG]${COLORS.reset} ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
static header(message: string) {
|
||||
const line = '═'.repeat(message.length + 4);
|
||||
console.log(`\n${COLORS.cyan}${line}${COLORS.reset}`);
|
||||
console.log(`${COLORS.cyan} ${message}${COLORS.reset}`);
|
||||
console.log(`${COLORS.cyan}${line}${COLORS.reset}\n`);
|
||||
}
|
||||
|
||||
static section(message: string) {
|
||||
console.log(`\n${COLORS.bright}${COLORS.cyan}▸ ${message}${COLORS.reset}`);
|
||||
}
|
||||
|
||||
static step(step: number, total: number, message: string) {
|
||||
console.log(`${COLORS.dim}[${step}/${total}]${COLORS.reset} ${message}`);
|
||||
}
|
||||
|
||||
static command(cmd: string) {
|
||||
console.log(`${COLORS.dim}$ ${cmd}${COLORS.reset}`);
|
||||
}
|
||||
|
||||
static json(obj: any) {
|
||||
console.log(JSON.stringify(obj, null, 2));
|
||||
}
|
||||
|
||||
static table(data: Record<string, any>) {
|
||||
const maxKeyLength = Math.max(...Object.keys(data).map(k => k.length));
|
||||
|
||||
console.log();
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
const paddedKey = key.padEnd(maxKeyLength);
|
||||
console.log(` ${COLORS.cyan}${paddedKey}${COLORS.reset} : ${value}`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
static loading(message: string): () => void {
|
||||
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||
let i = 0;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
process.stdout.write(`\r${COLORS.blue}${frames[i]}${COLORS.reset} ${message}`);
|
||||
i = (i + 1) % frames.length;
|
||||
}, 80);
|
||||
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
process.stdout.write('\r\x1b[K'); // Clear line
|
||||
};
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
// Node operations utilities
|
||||
import { join } from 'path';
|
||||
import { Logger } from './logger';
|
||||
import { TomlManager } from './toml';
|
||||
import { DEFAULT_PORTS, CONSENSUS_TIMEOUTS } from './constants';
|
||||
import type { NodeConfig } from '../types';
|
||||
|
||||
export class NodeManager {
|
||||
/**
|
||||
* Check if snrd binary is available
|
||||
*/
|
||||
static async isBinaryAvailable(binary: string = 'snrd'): Promise<boolean> {
|
||||
try {
|
||||
const result = await Bun.$`which ${binary}`.quiet();
|
||||
return result.exitCode === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get snrd version
|
||||
*/
|
||||
static async getVersion(binary: string = 'snrd'): Promise<string> {
|
||||
try {
|
||||
const result = await Bun.$`${binary} version`.quiet();
|
||||
return (await result.text()).trim();
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get version: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a node
|
||||
*/
|
||||
static async init(config: NodeConfig, binary: string = 'snrd'): Promise<void> {
|
||||
Logger.section(`Initializing ${config.nodeType} node: ${config.moniker}`);
|
||||
|
||||
// Initialize chain
|
||||
Logger.step(1, 3, 'Initializing chain directory');
|
||||
await Bun.$`${binary} init ${config.moniker} --chain-id ${config.chainId} --home ${config.home}`;
|
||||
|
||||
// Configure node
|
||||
Logger.step(2, 3, 'Configuring node settings');
|
||||
await this.configureNode(config);
|
||||
|
||||
// Set VRF keys if needed
|
||||
Logger.step(3, 3, 'Generating VRF keypair');
|
||||
try {
|
||||
await this.generateVRFKey(config.home, binary);
|
||||
} catch (error) {
|
||||
Logger.warn('VRF key generation failed, but continuing...');
|
||||
}
|
||||
|
||||
Logger.success(`Node initialized: ${config.moniker}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure node settings
|
||||
*/
|
||||
static async configureNode(config: NodeConfig): Promise<void> {
|
||||
const configToml = join(config.home, 'config', 'config.toml');
|
||||
const appToml = join(config.home, 'config', 'app.toml');
|
||||
|
||||
// Configure RPC
|
||||
if (config.ports?.rpc) {
|
||||
await TomlManager.setValue(configToml, 'rpc.laddr', `tcp://0.0.0.0:${config.ports.rpc}`);
|
||||
await TomlManager.setValue(configToml, 'rpc.cors_allowed_origins', ['*']);
|
||||
}
|
||||
|
||||
// Configure P2P
|
||||
await TomlManager.setValue(configToml, 'p2p.laddr', `tcp://0.0.0.0:${DEFAULT_PORTS.p2p}`);
|
||||
|
||||
// Set consensus timeouts
|
||||
await TomlManager.setValue(configToml, 'consensus.timeout_propose', CONSENSUS_TIMEOUTS.propose);
|
||||
await TomlManager.setValue(configToml, 'consensus.timeout_prevote', CONSENSUS_TIMEOUTS.prevote);
|
||||
await TomlManager.setValue(configToml, 'consensus.timeout_precommit', CONSENSUS_TIMEOUTS.precommit);
|
||||
await TomlManager.setValue(configToml, 'consensus.timeout_commit', CONSENSUS_TIMEOUTS.commit);
|
||||
|
||||
// Configure API
|
||||
if (config.ports?.rest) {
|
||||
await TomlManager.setValue(appToml, 'api.enable', true);
|
||||
await TomlManager.setValue(appToml, 'api.address', `tcp://0.0.0.0:${config.ports.rest}`);
|
||||
await TomlManager.setValue(appToml, 'api.enabled-unsafe-cors', true);
|
||||
}
|
||||
|
||||
// Configure gRPC
|
||||
if (config.ports?.grpc) {
|
||||
await TomlManager.setValue(appToml, 'grpc.address', `0.0.0.0:${config.ports.grpc}`);
|
||||
}
|
||||
|
||||
// Configure gRPC-Web
|
||||
if (config.ports?.grpcWeb) {
|
||||
await TomlManager.setValue(appToml, 'grpc-web.address', `0.0.0.0:${config.ports.grpcWeb}`);
|
||||
}
|
||||
|
||||
// Configure JSON-RPC
|
||||
if (config.ports?.jsonRpc && config.ports?.jsonRpcWs) {
|
||||
await TomlManager.setValue(appToml, 'json-rpc.enable', true);
|
||||
await TomlManager.setValue(appToml, 'json-rpc.address', `0.0.0.0:${config.ports.jsonRpc}`);
|
||||
await TomlManager.setValue(appToml, 'json-rpc.ws-address', `0.0.0.0:${config.ports.jsonRpcWs}`);
|
||||
await TomlManager.setValue(appToml, 'json-rpc.api', 'eth,txpool,personal,net,debug,web3');
|
||||
}
|
||||
|
||||
// Set pruning
|
||||
await TomlManager.setValue(appToml, 'pruning', 'nothing');
|
||||
|
||||
// Set minimum gas prices
|
||||
await TomlManager.setValue(appToml, 'minimum-gas-prices', '0usnr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate VRF keypair
|
||||
*/
|
||||
static async generateVRFKey(home: string, binary: string = 'snrd'): Promise<void> {
|
||||
try {
|
||||
await Bun.$`${binary} keys vrf generate --home ${home}`.quiet();
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to generate VRF key: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a node
|
||||
*/
|
||||
static async start(home: string, chainId: string, binary: string = 'snrd'): Promise<void> {
|
||||
Logger.info(`Starting node from: ${home}`);
|
||||
await Bun.$`${binary} start --home ${home} --chain-id ${chainId} --pruning nothing --minimum-gas-prices 0usnr`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get node status
|
||||
*/
|
||||
static async getStatus(rpcUrl: string = 'http://localhost:26657'): Promise<any> {
|
||||
try {
|
||||
const response = await fetch(`${rpcUrl}/status`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get node status: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for node to be ready
|
||||
*/
|
||||
static async waitForReady(rpcUrl: string = 'http://localhost:26657', maxAttempts: number = 30): Promise<void> {
|
||||
Logger.info('Waiting for node to be ready...');
|
||||
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
const response = await fetch(`${rpcUrl}/health`);
|
||||
if (response.ok) {
|
||||
Logger.success('Node is ready!');
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors and retry
|
||||
}
|
||||
|
||||
await Bun.sleep(1000);
|
||||
}
|
||||
|
||||
throw new Error('Node failed to become ready');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if node is syncing
|
||||
*/
|
||||
static async isSyncing(rpcUrl: string = 'http://localhost:26657'): Promise<boolean> {
|
||||
const status = await this.getStatus(rpcUrl);
|
||||
return status.result.sync_info.catching_up;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// TOML utilities for configuration management
|
||||
import TOML from '@iarna/toml';
|
||||
|
||||
export class TomlManager {
|
||||
/**
|
||||
* Read and parse a TOML file
|
||||
*/
|
||||
static async read(filePath: string): Promise<any> {
|
||||
const file = Bun.file(filePath);
|
||||
const content = await file.text();
|
||||
return TOML.parse(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an object to a TOML file
|
||||
*/
|
||||
static async write(filePath: string, data: any): Promise<void> {
|
||||
const content = TOML.stringify(data);
|
||||
await Bun.write(filePath, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value in a TOML file (supports nested paths with dot notation)
|
||||
*/
|
||||
static async setValue(filePath: string, path: string, value: any): Promise<void> {
|
||||
const data = await this.read(filePath);
|
||||
const keys = path.split('.');
|
||||
|
||||
let current = data;
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!current[key]) {
|
||||
current[key] = {};
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
current[keys[keys.length - 1]] = value;
|
||||
|
||||
await this.write(filePath, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from a TOML file
|
||||
*/
|
||||
static async getValue(filePath: string, path: string): Promise<any> {
|
||||
const data = await this.read(filePath);
|
||||
const keys = path.split('.');
|
||||
|
||||
let current = data;
|
||||
for (const key of keys) {
|
||||
if (current[key] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge configurations
|
||||
*/
|
||||
static merge(base: any, override: any): any {
|
||||
const result = { ...base };
|
||||
|
||||
for (const [key, value] of Object.entries(override)) {
|
||||
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
|
||||
result[key] = this.merge(result[key] || {}, value);
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Type definitions for sonrctl
|
||||
|
||||
export interface NodeConfig {
|
||||
moniker: string;
|
||||
chainId: string;
|
||||
home: string;
|
||||
nodeType: 'validator' | 'sentry' | 'full';
|
||||
ports?: {
|
||||
rpc?: number;
|
||||
rest?: number;
|
||||
grpc?: number;
|
||||
grpcWeb?: number;
|
||||
jsonRpc?: number;
|
||||
jsonRpcWs?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface NetworkConfig {
|
||||
name: string;
|
||||
chainId: string;
|
||||
validators: ValidatorConfig[];
|
||||
sentries: SentryConfig[];
|
||||
}
|
||||
|
||||
export interface ValidatorConfig {
|
||||
name: string;
|
||||
moniker: string;
|
||||
network: string;
|
||||
home: string;
|
||||
}
|
||||
|
||||
export interface SentryConfig {
|
||||
name: string;
|
||||
moniker: string;
|
||||
network: string;
|
||||
home: string;
|
||||
exposedPorts: boolean;
|
||||
}
|
||||
|
||||
export interface DockerNode {
|
||||
name: string;
|
||||
image: string;
|
||||
command: string;
|
||||
environment: Record<string, string>;
|
||||
volumes: string[];
|
||||
networks: string[];
|
||||
ports?: string[];
|
||||
}
|
||||
|
||||
export interface CommandContext {
|
||||
args: string[];
|
||||
options: Record<string, any>;
|
||||
}
|
||||
@@ -349,50 +349,3 @@ func GetEventAttribute(event struct {
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// DoRequest performs a public HTTP GET request (wrapper around private doRequest)
|
||||
// This is used by test helpers to query custom endpoints
|
||||
func (c *StarshipClient) DoRequest(ctx context.Context, url string, target any) error {
|
||||
return c.doRequest(ctx, url, target)
|
||||
}
|
||||
|
||||
// TxResponse represents a transaction response
|
||||
type TxResponse struct {
|
||||
Height string `json:"height"`
|
||||
TxHash string `json:"txhash"`
|
||||
Codespace string `json:"codespace"`
|
||||
Code uint32 `json:"code"`
|
||||
Data string `json:"data"`
|
||||
RawLog string `json:"raw_log"`
|
||||
Logs []struct {
|
||||
MsgIndex int `json:"msg_index"`
|
||||
Log string `json:"log"`
|
||||
Events []struct {
|
||||
Type string `json:"type"`
|
||||
Attributes []struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
} `json:"attributes"`
|
||||
} `json:"events"`
|
||||
} `json:"logs"`
|
||||
Info string `json:"info"`
|
||||
GasWanted string `json:"gas_wanted"`
|
||||
GasUsed string `json:"gas_used"`
|
||||
Tx any `json:"tx"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
// SignAndBroadcastTx signs and broadcasts a transaction message
|
||||
// Note: This is a simplified implementation for E2E tests
|
||||
// In production, use proper keyring and transaction signing
|
||||
func (c *StarshipClient) SignAndBroadcastTx(ctx context.Context, from string, msgs ...sdk.Msg) (*TxResponse, error) {
|
||||
// For E2E tests, we'll use the REST API to broadcast transactions
|
||||
// In a real implementation, you would:
|
||||
// 1. Sign the transaction with the account's private key
|
||||
// 2. Encode the transaction
|
||||
// 3. Broadcast via REST or gRPC
|
||||
|
||||
// This is a placeholder that needs proper implementation based on your keyring setup
|
||||
// For now, return an error indicating this needs to be implemented
|
||||
return nil, fmt.Errorf("SignAndBroadcastTx not yet implemented - use CLI or proper SDK client for transaction signing")
|
||||
}
|
||||
|
||||
+2
-4
@@ -18,8 +18,8 @@ replace (
|
||||
github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0
|
||||
github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.8.1
|
||||
github.com/sonr-io/crypto => ../../crypto
|
||||
github.com/sonr-io/sonr => ../../
|
||||
github.com/sonr-io/crypto => ../../crypto
|
||||
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||
)
|
||||
|
||||
@@ -333,8 +333,8 @@ require (
|
||||
github.com/samber/lo v1.47.0 // indirect
|
||||
github.com/sasha-s/go-deadlock v0.3.5 // indirect
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
|
||||
github.com/sonr-io/crypto v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/sonr-io/sonr v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/sonr-io/crypto v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
@@ -421,5 +421,3 @@ require (
|
||||
rsc.io/tmplfunc v0.0.3 // indirect
|
||||
sigs.k8s.io/yaml v1.5.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/lyft/protoc-gen-validate => github.com/envoyproxy/protoc-gen-validate v1.1.0
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
.PHONY: test test-verbose test-quick test-single clean help
|
||||
|
||||
# Default target
|
||||
all: test
|
||||
|
||||
# Run all E2E tests
|
||||
test:
|
||||
@echo "Running USDC Swap E2E tests..."
|
||||
go test -v -timeout 10m
|
||||
|
||||
# Run tests with verbose output and detailed logs
|
||||
test-verbose:
|
||||
@echo "Running tests with verbose output..."
|
||||
go test -v -timeout 10m 2>&1 | tee test.log
|
||||
@echo "Test log saved to test.log"
|
||||
|
||||
# Run quick tests (skip slow integration tests)
|
||||
test-quick:
|
||||
@echo "Running quick tests..."
|
||||
go test -v -short -timeout 5m
|
||||
|
||||
# Run a single test
|
||||
# Usage: make test-single TEST=Test04_ExecuteUSDCSwap_SNRToUSDC
|
||||
test-single:
|
||||
@echo "Running single test: $(TEST)"
|
||||
go test -v -run "TestUSDCSwapE2E/$(TEST)" -timeout 5m
|
||||
|
||||
# Run tests with coverage
|
||||
test-coverage:
|
||||
@echo "Running tests with coverage..."
|
||||
go test -v -coverprofile=coverage.out -timeout 10m
|
||||
go tool cover -html=coverage.out -o coverage.html
|
||||
@echo "Coverage report generated: coverage.html"
|
||||
|
||||
# Run specific test categories
|
||||
test-config:
|
||||
@echo "Running configuration tests..."
|
||||
go test -v -run "Test01" -timeout 2m
|
||||
|
||||
test-did:
|
||||
@echo "Running DID tests..."
|
||||
go test -v -run "Test02" -timeout 3m
|
||||
|
||||
test-dex:
|
||||
@echo "Running DEX account tests..."
|
||||
go test -v -run "Test03" -timeout 3m
|
||||
|
||||
test-swaps:
|
||||
@echo "Running swap execution tests..."
|
||||
go test -v -run "Test04|Test05|Test09" -timeout 10m
|
||||
|
||||
test-validation:
|
||||
@echo "Running validation tests..."
|
||||
go test -v -run "Test06" -timeout 3m
|
||||
|
||||
test-query:
|
||||
@echo "Running query tests..."
|
||||
go test -v -run "Test08" -timeout 2m
|
||||
|
||||
# Verify prerequisites
|
||||
check-prereqs:
|
||||
@echo "Checking prerequisites..."
|
||||
@command -v go >/dev/null 2>&1 || { echo "Go is not installed"; exit 1; }
|
||||
@echo "✓ Go installed: $$(go version)"
|
||||
@command -v snrd >/dev/null 2>&1 || { echo "snrd is not installed"; exit 1; }
|
||||
@echo "✓ snrd installed"
|
||||
@curl -s http://localhost:1317/cosmos/base/tendermint/v1beta1/node_info >/dev/null 2>&1 || \
|
||||
{ echo "✗ Sonr testnet not running at localhost:1317"; exit 1; }
|
||||
@echo "✓ Sonr testnet running"
|
||||
@echo "All prerequisites met!"
|
||||
|
||||
# Setup test environment
|
||||
setup:
|
||||
@echo "Setting up test environment..."
|
||||
@echo "1. Checking network status..."
|
||||
@curl -s http://localhost:1317/cosmos/base/tendermint/v1beta1/node_info | grep -q network && \
|
||||
echo "✓ Network is running" || { echo "✗ Network not accessible"; exit 1; }
|
||||
@echo "2. Checking faucet..."
|
||||
@curl -s http://localhost:8000/health >/dev/null 2>&1 && \
|
||||
echo "✓ Faucet is running" || echo "⚠ Faucet not accessible (tests may fail)"
|
||||
@echo "Setup complete!"
|
||||
|
||||
# Verify IBC connections
|
||||
check-ibc:
|
||||
@echo "Checking IBC connections..."
|
||||
@snrd query ibc connection connections --node tcp://localhost:26657 --output json | \
|
||||
jq -r '.connections[] | "\(.id): \(.state)"'
|
||||
@echo ""
|
||||
@echo "Checking IBC channels..."
|
||||
@snrd query ibc channel channels --node tcp://localhost:26657 --output json | \
|
||||
jq -r '.channels[] | "\(.port_id)/\(.channel_id): \(.state)"'
|
||||
|
||||
# Query DEX module status
|
||||
check-dex:
|
||||
@echo "Checking DEX module..."
|
||||
@snrd query dex params --node tcp://localhost:26657 --output json | \
|
||||
jq -r '.params | "Enabled: \(.enabled), Max Accounts: \(.max_accounts_per_did)"'
|
||||
|
||||
# Clean test artifacts
|
||||
clean:
|
||||
@echo "Cleaning test artifacts..."
|
||||
@rm -f test.log coverage.out coverage.html
|
||||
@echo "✓ Cleaned"
|
||||
|
||||
# Watch mode - run tests on file changes
|
||||
# Requires: entr (brew install entr or apt-get install entr)
|
||||
watch:
|
||||
@echo "Watching for file changes..."
|
||||
@ls *.go | entr -c make test-verbose
|
||||
|
||||
# Benchmark tests
|
||||
bench:
|
||||
@echo "Running benchmarks..."
|
||||
go test -bench=. -benchmem -timeout 10m
|
||||
|
||||
# Generate test data
|
||||
generate-test-data:
|
||||
@echo "Generating test DIDs and accounts..."
|
||||
@for i in {1..5}; do \
|
||||
did="did:snr:test_preset_$$i"; \
|
||||
echo "$$did"; \
|
||||
done
|
||||
|
||||
# Help target
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@echo " make test - Run all E2E tests"
|
||||
@echo " make test-verbose - Run with verbose output"
|
||||
@echo " make test-quick - Run quick tests only"
|
||||
@echo " make test-single TEST=<name> - Run a single test"
|
||||
@echo " make test-coverage - Run with coverage report"
|
||||
@echo " make test-config - Run configuration tests"
|
||||
@echo " make test-did - Run DID tests"
|
||||
@echo " make test-dex - Run DEX account tests"
|
||||
@echo " make test-swaps - Run swap execution tests"
|
||||
@echo " make test-validation - Run validation tests"
|
||||
@echo " make test-query - Run query tests"
|
||||
@echo " make check-prereqs - Verify prerequisites"
|
||||
@echo " make setup - Setup test environment"
|
||||
@echo " make check-ibc - Check IBC connections"
|
||||
@echo " make check-dex - Check DEX module status"
|
||||
@echo " make clean - Clean test artifacts"
|
||||
@echo " make watch - Watch mode (requires entr)"
|
||||
@echo " make bench - Run benchmarks"
|
||||
@echo " make help - Show this help"
|
||||
@@ -1,420 +0,0 @@
|
||||
# USDC Swap with DID E2E Tests
|
||||
|
||||
Comprehensive end-to-end tests for USDC swap functionality with DID-based Interchain Accounts (ICA) on Noble testnet.
|
||||
|
||||
## Overview
|
||||
|
||||
This test suite validates the complete USDC swap flow including:
|
||||
- DID creation and management
|
||||
- DEX account registration via ICA
|
||||
- Cross-chain USDC swaps (SNR ↔ USDC)
|
||||
- Noble testnet IBC integration
|
||||
- Swap routing and slippage protection
|
||||
- Event verification and transaction history
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Network Setup
|
||||
|
||||
1. **Sonr Testnet**: Running local testnet with DEX module enabled
|
||||
2. **Noble Testnet Connection**: IBC connection established to Noble testnet (grand-1)
|
||||
3. **IBC Relayer**: Active relayer between Sonr and Noble chains
|
||||
4. **Test Tokens**:
|
||||
- SNR tokens for gas and swaps
|
||||
- USDC tokens on Noble testnet (optional for full E2E)
|
||||
|
||||
### Configuration
|
||||
|
||||
The tests use these default configurations:
|
||||
|
||||
```go
|
||||
// Noble Testnet
|
||||
NobleChainID = "noble-grand-1"
|
||||
NobleConnectionID = "connection-noble" // Update with actual connection
|
||||
NobleUSDCDenom = "uusdc"
|
||||
|
||||
// Test Amounts
|
||||
USDCTestAmount = 1_000_000 // 1 USDC (6 decimals)
|
||||
SNRSwapAmount = 1_000_000 // 1 SNR (6 decimals)
|
||||
SlippageTolerance = 0.05 // 5% slippage
|
||||
```
|
||||
|
||||
## Test Suite Structure
|
||||
|
||||
### Test Cases
|
||||
|
||||
#### 01. Verify Noble Configuration
|
||||
**Purpose**: Validates Noble chain configuration and constants
|
||||
**Validates**:
|
||||
- Noble chain ID recognition
|
||||
- USDC denomination configuration
|
||||
- Decimal places (6 for USDC)
|
||||
- Chain type detection
|
||||
|
||||
#### 02. Create DID Document
|
||||
**Purpose**: Creates a test DID for swap operations
|
||||
**Operations**:
|
||||
- Generate unique DID for test
|
||||
- Create DID document on-chain
|
||||
- Verify DID can be queried
|
||||
- Validate DID structure
|
||||
|
||||
#### 03. Register DEX Account
|
||||
**Purpose**: Registers an ICA account on Noble connection
|
||||
**Operations**:
|
||||
- Register DEX account with Noble connection
|
||||
- Enable swap and liquidity features
|
||||
- Wait for ICA channel handshake
|
||||
- Verify account status and ICA address
|
||||
|
||||
**Expected States**:
|
||||
- `ACCOUNT_STATUS_PENDING`: Initial state during handshake
|
||||
- `ACCOUNT_STATUS_ACTIVE`: Account ready for operations
|
||||
- `ACCOUNT_STATUS_FAILED`: Handshake failed
|
||||
|
||||
#### 04. Execute USDC Swap (SNR → USDC)
|
||||
**Purpose**: Tests swapping SNR to USDC on Noble
|
||||
**Flow**:
|
||||
1. Setup DID and DEX account
|
||||
2. Calculate minimum output with slippage
|
||||
3. Execute swap via ICA
|
||||
4. Verify transaction success
|
||||
5. Check swap event emission
|
||||
|
||||
**Parameters**:
|
||||
```go
|
||||
Amount: 1_000_000 usnr (1 SNR)
|
||||
MinAmountOut: 950_000 uusdc (0.95 USDC - 5% slippage)
|
||||
Timeout: 60 seconds
|
||||
```
|
||||
|
||||
#### 05. Execute USDC Swap (USDC → SNR)
|
||||
**Purpose**: Tests reverse swap (USDC to SNR)
|
||||
**Flow**: Same as Test 04 but with reversed denoms
|
||||
|
||||
#### 06. Swap With Invalid Parameters
|
||||
**Purpose**: Tests error handling and validation
|
||||
**Test Cases**:
|
||||
- Zero amount swap
|
||||
- Same source/target denom
|
||||
- Negative minimum output
|
||||
- Invalid connection ID
|
||||
|
||||
**Expected**: All cases should fail gracefully
|
||||
|
||||
#### 07. Swap With UCAN Permission
|
||||
**Purpose**: Tests UCAN-authorized delegated swaps
|
||||
**Status**: Currently skipped (requires full UCAN setup)
|
||||
**Features**:
|
||||
- UCAN token generation
|
||||
- Permission validation
|
||||
- Delegated execution
|
||||
|
||||
#### 08. Query DEX History
|
||||
**Purpose**: Verifies transaction history tracking
|
||||
**Validates**:
|
||||
- History query endpoint
|
||||
- Activity records
|
||||
- Transaction metadata
|
||||
- Status tracking
|
||||
|
||||
#### 09. Multiple Sequential Swaps
|
||||
**Purpose**: Tests executing multiple swaps in sequence
|
||||
**Operations**:
|
||||
- Execute 3 swaps sequentially
|
||||
- Verify each transaction succeeds
|
||||
- Check for nonce/sequence handling
|
||||
- Validate no race conditions
|
||||
|
||||
## Running the Tests
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Navigate to test directory
|
||||
cd test/e2e/usdc-swap-did
|
||||
|
||||
# Run all tests
|
||||
go test -v
|
||||
|
||||
# Run specific test
|
||||
go test -v -run TestUSDCSwapE2E/Test04_ExecuteUSDCSwap_SNRToUSDC
|
||||
|
||||
# Run with timeout
|
||||
go test -v -timeout 10m
|
||||
```
|
||||
|
||||
### Prerequisites Check
|
||||
|
||||
Before running tests, ensure:
|
||||
|
||||
```bash
|
||||
# 1. Verify Noble connection exists
|
||||
snrd query ibc connection end connection-noble
|
||||
|
||||
# 2. Check connection is open
|
||||
# Expected output: state: STATE_OPEN
|
||||
|
||||
# 3. Verify ICA channel
|
||||
snrd query ibc channel channels --node tcp://localhost:26657
|
||||
|
||||
# 4. Check DEX module is enabled
|
||||
snrd query dex params
|
||||
```
|
||||
|
||||
### Full E2E Setup
|
||||
|
||||
```bash
|
||||
# 1. Start Sonr testnet
|
||||
make testnet
|
||||
|
||||
# 2. Establish Noble connection (one-time setup)
|
||||
# This requires:
|
||||
# - Noble testnet access
|
||||
# - IBC relayer configuration
|
||||
# - Channel creation
|
||||
|
||||
# 3. Fund test accounts
|
||||
# The tests will auto-fund via faucet, but you can pre-fund:
|
||||
snrd tx bank send validator <test-address> 100000000usnr --yes
|
||||
|
||||
# 4. Run tests
|
||||
cd test/e2e/usdc-swap-did && go test -v
|
||||
```
|
||||
|
||||
## Test Utilities
|
||||
|
||||
### Helper Functions
|
||||
|
||||
#### DID Operations
|
||||
```go
|
||||
// Create test DID
|
||||
did, err := CreateTestDID(ctx, cfg, account, didID)
|
||||
|
||||
// Query DID
|
||||
did, err := QueryDID(ctx, cfg, didID)
|
||||
```
|
||||
|
||||
#### DEX Operations
|
||||
```go
|
||||
// Query DEX account
|
||||
account, err := QueryDEXAccount(ctx, cfg, didID, connectionID)
|
||||
|
||||
// Query transaction history
|
||||
history, err := QueryDEXHistory(ctx, cfg, didID)
|
||||
|
||||
// Wait for account activation
|
||||
active, err := WaitForDEXAccountActivation(ctx, cfg, didID, connectionID, timeout)
|
||||
```
|
||||
|
||||
#### Swap Utilities
|
||||
```go
|
||||
// Validate parameters
|
||||
err := ValidateSwapParameters(sourceDenom, targetDenom, amount, minOut)
|
||||
|
||||
// Calculate slippage
|
||||
minOut := CalculateMinimumOutput(amount, slippagePct)
|
||||
|
||||
// Format amounts for display
|
||||
formatted := FormatUSDCAmount(1000000) // "1.000000 USDC"
|
||||
formatted = FormatSNRAmount(1000000) // "1.000000 SNR"
|
||||
```
|
||||
|
||||
### Assertions
|
||||
|
||||
The test suite uses `testify/suite` and `testify/require` for assertions:
|
||||
|
||||
```go
|
||||
// Transaction success
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint32(0), txResp.Code)
|
||||
|
||||
// Balance checks
|
||||
balance := s.getBalance(address, denom)
|
||||
require.True(t, balance.GT(minAmount))
|
||||
|
||||
// Event verification
|
||||
s.verifySwapEvent(txHash, did, connectionID)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Update Connection ID
|
||||
|
||||
Before running tests, update the Noble connection ID in `usdc_swap_test.go`:
|
||||
|
||||
```go
|
||||
const (
|
||||
NobleConnectionID = "connection-0" // Replace with actual connection ID
|
||||
)
|
||||
```
|
||||
|
||||
To find the connection ID:
|
||||
|
||||
```bash
|
||||
# List all connections
|
||||
snrd query ibc connection connections
|
||||
|
||||
# Look for connection to Noble (grand-1)
|
||||
# Update NobleConnectionID constant with the correct value
|
||||
```
|
||||
|
||||
### Custom Test Configuration
|
||||
|
||||
Create a custom config for your environment:
|
||||
|
||||
```go
|
||||
cfg := &utils.TestConfig{
|
||||
ChainID: "sonrtest_1-1",
|
||||
BaseURL: "http://localhost:1317",
|
||||
NobleConnection: "connection-0", // Your Noble connection
|
||||
// ... other settings
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### 1. Connection Not Found
|
||||
```
|
||||
Error: connection connection-noble not found
|
||||
```
|
||||
**Solution**: Update `NobleConnectionID` constant with actual connection ID
|
||||
|
||||
#### 2. ICA Account Pending
|
||||
```
|
||||
⚠ ICA address pending (channel handshake may be in progress)
|
||||
```
|
||||
**Solution**: Wait for ICA channel handshake to complete (can take 1-2 minutes)
|
||||
|
||||
#### 3. Swap Transaction Fails
|
||||
```
|
||||
Error: DEX account not active
|
||||
```
|
||||
**Solution**: Ensure ICA account is in `ACCOUNT_STATUS_ACTIVE` state
|
||||
|
||||
#### 4. Insufficient Funds
|
||||
```
|
||||
Error: insufficient funds
|
||||
```
|
||||
**Solution**: Fund test account with SNR tokens
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable verbose logging:
|
||||
|
||||
```bash
|
||||
# Run with verbose output
|
||||
go test -v -run TestUSDCSwapE2E 2>&1 | tee test.log
|
||||
|
||||
# Check specific test output
|
||||
grep "Test 04" test.log
|
||||
|
||||
# View all swap events
|
||||
grep "Swap" test.log
|
||||
```
|
||||
|
||||
### Verify IBC Setup
|
||||
|
||||
```bash
|
||||
# Check IBC clients
|
||||
snrd query ibc client states
|
||||
|
||||
# Check channels
|
||||
snrd query ibc channel channels
|
||||
|
||||
# Check connections
|
||||
snrd query ibc connection connections
|
||||
|
||||
# Monitor relayer
|
||||
# (depends on your relayer setup)
|
||||
```
|
||||
|
||||
## Integration with CI/CD
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: USDC Swap E2E Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
e2e-usdc-swap:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Start Testnet
|
||||
run: make testnet
|
||||
|
||||
- name: Wait for Network
|
||||
run: sleep 30
|
||||
|
||||
- name: Run E2E Tests
|
||||
run: |
|
||||
cd test/e2e/usdc-swap-did
|
||||
go test -v -timeout 10m
|
||||
```
|
||||
|
||||
## Expected Output
|
||||
|
||||
### Successful Test Run
|
||||
|
||||
```
|
||||
=== RUN TestUSDCSwapE2E
|
||||
=== RUN TestUSDCSwapE2E/Test01_VerifyNobleConfiguration
|
||||
=== Test 01: Verify Noble Configuration ===
|
||||
✓ Noble configuration verified successfully
|
||||
=== RUN TestUSDCSwapE2E/Test02_CreateDIDDocument
|
||||
=== Test 02: Create DID Document ===
|
||||
✓ DID document created: did:snr:test_1234567890
|
||||
✓ DID query verification successful
|
||||
=== RUN TestUSDCSwapE2E/Test03_RegisterDEXAccount
|
||||
=== Test 03: Register DEX Account ===
|
||||
✓ DEX account registered, tx hash: ABC123...
|
||||
✓ DEX account status: ACCOUNT_STATUS_ACTIVE
|
||||
✓ ICA port ID: icacontroller-did:snr:test_1234567890-connection-0
|
||||
✓ ICA address: noble1abc...xyz
|
||||
=== RUN TestUSDCSwapE2E/Test04_ExecuteUSDCSwap_SNRToUSDC
|
||||
=== Test 04: Execute USDC Swap (SNR → USDC) ===
|
||||
Initial SNR balance: 10000000
|
||||
✓ Swap transaction broadcasted, tx hash: DEF456...
|
||||
✓ Found swap event for DID: did:snr:test_1234567890
|
||||
✓ Swap execution test completed
|
||||
...
|
||||
--- PASS: TestUSDCSwapE2E (45.23s)
|
||||
PASS
|
||||
ok github.com/sonr-io/sonr/test/e2e/usdc-swap-did 45.234s
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
1. **Multi-hop Swaps**: Route through multiple pools/chains
|
||||
2. **Liquidity Provision**: Add/remove liquidity tests
|
||||
3. **Limit Orders**: Create and execute limit orders
|
||||
4. **UCAN Integration**: Full delegation testing
|
||||
5. **Performance Tests**: Load testing and benchmarks
|
||||
6. **Failure Recovery**: Test ICA timeout handling
|
||||
|
||||
### Contributing
|
||||
|
||||
When adding new tests:
|
||||
1. Follow existing naming convention (Test##_Description)
|
||||
2. Add helper functions to `helpers.go`
|
||||
3. Update this README with new test documentation
|
||||
4. Ensure tests are idempotent
|
||||
5. Add appropriate logging for debugging
|
||||
|
||||
## References
|
||||
|
||||
- [Noble Documentation](https://docs.noble.xyz)
|
||||
- [IBC Protocol](https://ibc.cosmos.network)
|
||||
- [Sonr DEX Module](../../../../x/dex/README.md)
|
||||
- [DID Module](../../../../x/did/README.md)
|
||||
- [E2E Testing Framework](../README.md)
|
||||
@@ -1,316 +0,0 @@
|
||||
# USDC Swap E2E Test Configuration
|
||||
# Copy this file to config.yaml and customize for your environment
|
||||
|
||||
# Sonr Testnet Configuration
|
||||
sonr:
|
||||
chain_id: "sonrtest_1-1"
|
||||
rpc_endpoint: "http://localhost:26657"
|
||||
rest_endpoint: "http://localhost:1317"
|
||||
grpc_endpoint: "localhost:9090"
|
||||
faucet_endpoint: "http://localhost:8000"
|
||||
|
||||
# Native token configuration
|
||||
staking_denom: "usnr"
|
||||
display_denom: "snr"
|
||||
decimals: 6
|
||||
|
||||
# Test account configuration
|
||||
test_accounts:
|
||||
- name: "test_user_1"
|
||||
mnemonic: "" # Optional: Provide existing mnemonic
|
||||
initial_balance: "100000000usnr" # 100 SNR
|
||||
|
||||
- name: "test_user_2"
|
||||
mnemonic: ""
|
||||
initial_balance: "50000000usnr" # 50 SNR
|
||||
|
||||
# Noble Testnet Configuration
|
||||
noble:
|
||||
chain_id: "noble-grand-1"
|
||||
rpc_endpoint: "https://noble-testnet-rpc.polkachu.com:443"
|
||||
rest_endpoint: "https://noble-testnet-api.polkachu.com"
|
||||
grpc_endpoint: "noble-testnet-grpc.polkachu.com:21590"
|
||||
|
||||
# USDC configuration
|
||||
usdc_denom: "uusdc"
|
||||
usdc_decimals: 6
|
||||
|
||||
# Circle attestation (for USDC)
|
||||
attestation:
|
||||
enabled: true
|
||||
domain: 4 # Noble testnet domain
|
||||
|
||||
# IBC Configuration
|
||||
ibc:
|
||||
# Connection from Sonr to Noble
|
||||
# Update this with your actual connection ID
|
||||
noble_connection_id: "connection-0"
|
||||
|
||||
# IBC transfer channel
|
||||
# Update with actual channel after connection
|
||||
transfer_channel_id: "channel-0"
|
||||
|
||||
# Relayer configuration (optional)
|
||||
relayer:
|
||||
enabled: false
|
||||
type: "hermes" # or "rly"
|
||||
path: "sonr-noble"
|
||||
|
||||
# DEX Module Configuration
|
||||
dex:
|
||||
# Features to enable for test accounts
|
||||
default_features:
|
||||
- "swap"
|
||||
- "liquidity"
|
||||
- "orders"
|
||||
|
||||
# ICA configuration
|
||||
ica:
|
||||
# Timeout for ICA operations
|
||||
timeout: "300s"
|
||||
|
||||
# Retry configuration
|
||||
max_retries: 3
|
||||
retry_delay: "5s"
|
||||
|
||||
# Account creation
|
||||
auto_register: true
|
||||
|
||||
# Swap Test Configuration
|
||||
swap:
|
||||
# Test amounts
|
||||
amounts:
|
||||
snr_to_usdc: "1000000" # 1 SNR
|
||||
usdc_to_snr: "5000000" # 5 USDC
|
||||
small_swap: "100000" # 0.1 tokens
|
||||
large_swap: "50000000" # 50 tokens
|
||||
|
||||
# Slippage configuration
|
||||
slippage:
|
||||
default_percent: 5.0 # 5% default slippage
|
||||
max_percent: 10.0 # 10% maximum allowed
|
||||
min_percent: 0.1 # 0.1% minimum
|
||||
|
||||
# Timeout configuration
|
||||
timeouts:
|
||||
swap_tx: "60s" # Individual swap timeout
|
||||
multi_swap: "300s" # Multiple swaps timeout
|
||||
history_query: "30s" # History query timeout
|
||||
|
||||
# Routing configuration
|
||||
routing:
|
||||
prefer_direct: true # Prefer direct swaps when available
|
||||
max_hops: 3 # Maximum routing hops
|
||||
usdc_intermediary: true # Use USDC as intermediary
|
||||
|
||||
# Test Configuration
|
||||
test:
|
||||
# Parallel execution
|
||||
parallel: false
|
||||
|
||||
# Timeout configuration
|
||||
timeout:
|
||||
default: "10m"
|
||||
extended: "30m"
|
||||
quick: "5m"
|
||||
|
||||
# Retry configuration
|
||||
retry:
|
||||
enabled: true
|
||||
max_attempts: 3
|
||||
delay: "5s"
|
||||
|
||||
# Logging
|
||||
logging:
|
||||
level: "info" # debug, info, warn, error
|
||||
verbose: true
|
||||
save_to_file: true
|
||||
file_path: "test.log"
|
||||
|
||||
# Cleanup
|
||||
cleanup:
|
||||
# Clean up test accounts after tests
|
||||
accounts: true
|
||||
|
||||
# Clean up test DIDs after tests
|
||||
dids: false # Keep for debugging
|
||||
|
||||
# Clean up test data
|
||||
artifacts: true
|
||||
|
||||
# DID Configuration
|
||||
did:
|
||||
# DID prefix
|
||||
prefix: "did:snr:test"
|
||||
|
||||
# Generate unique DIDs per test
|
||||
unique_per_test: true
|
||||
|
||||
# DID document configuration
|
||||
document:
|
||||
verification_methods:
|
||||
- type: "EcdsaSecp256k1VerificationKey2019"
|
||||
purpose: ["authentication", "assertionMethod"]
|
||||
|
||||
services:
|
||||
- type: "DEXSwapService"
|
||||
endpoint: "https://dex.sonr.io"
|
||||
|
||||
# Monitoring and Alerts
|
||||
monitoring:
|
||||
# Enable transaction monitoring
|
||||
enabled: true
|
||||
|
||||
# Check intervals
|
||||
intervals:
|
||||
balance: "5s"
|
||||
status: "10s"
|
||||
events: "3s"
|
||||
|
||||
# Alerts
|
||||
alerts:
|
||||
# Alert on test failure
|
||||
on_failure: true
|
||||
|
||||
# Alert on slow tests
|
||||
on_slow_test:
|
||||
enabled: true
|
||||
threshold: "5m"
|
||||
|
||||
# Alert on IBC timeout
|
||||
on_ibc_timeout: true
|
||||
|
||||
# Performance Configuration
|
||||
performance:
|
||||
# Concurrent swaps limit
|
||||
max_concurrent_swaps: 10
|
||||
|
||||
# Rate limiting
|
||||
rate_limit:
|
||||
enabled: true
|
||||
requests_per_second: 5
|
||||
|
||||
# Resource limits
|
||||
resources:
|
||||
max_memory: "1GB"
|
||||
max_goroutines: 100
|
||||
|
||||
# Network Simulation
|
||||
simulation:
|
||||
# Simulate network conditions
|
||||
enabled: false
|
||||
|
||||
# Latency simulation
|
||||
latency:
|
||||
min: "10ms"
|
||||
max: "500ms"
|
||||
|
||||
# Packet loss simulation
|
||||
packet_loss:
|
||||
rate: 0.01 # 1% packet loss
|
||||
|
||||
# Bandwidth limits
|
||||
bandwidth:
|
||||
upload: "100Mbps"
|
||||
download: "100Mbps"
|
||||
|
||||
# Debug Configuration
|
||||
debug:
|
||||
# Enable debug mode
|
||||
enabled: false
|
||||
|
||||
# Save transaction details
|
||||
save_transactions: true
|
||||
transactions_dir: "./transactions"
|
||||
|
||||
# Save state snapshots
|
||||
save_snapshots: true
|
||||
snapshots_dir: "./snapshots"
|
||||
|
||||
# Breakpoints
|
||||
breakpoints:
|
||||
before_swap: false
|
||||
after_swap: false
|
||||
on_error: true
|
||||
|
||||
# Profiling
|
||||
profiling:
|
||||
enabled: false
|
||||
cpu: true
|
||||
memory: true
|
||||
output_dir: "./profiles"
|
||||
|
||||
# CI/CD Configuration
|
||||
ci:
|
||||
# GitHub Actions
|
||||
github_actions:
|
||||
enabled: true
|
||||
secrets:
|
||||
noble_rpc: "NOBLE_RPC_ENDPOINT"
|
||||
test_mnemonic: "TEST_ACCOUNT_MNEMONIC"
|
||||
|
||||
# Report generation
|
||||
reports:
|
||||
format: "junit" # junit, json, html
|
||||
output_dir: "./test-reports"
|
||||
|
||||
# Artifacts
|
||||
artifacts:
|
||||
upload: true
|
||||
retention_days: 30
|
||||
|
||||
# Environment-specific overrides
|
||||
environments:
|
||||
# Local development
|
||||
local:
|
||||
sonr:
|
||||
rest_endpoint: "http://localhost:1317"
|
||||
noble:
|
||||
# Use local Noble node if available
|
||||
rest_endpoint: "http://localhost:1318"
|
||||
test:
|
||||
timeout:
|
||||
default: "5m"
|
||||
|
||||
# Staging environment
|
||||
staging:
|
||||
sonr:
|
||||
rest_endpoint: "https://staging-api.sonr.io"
|
||||
noble:
|
||||
rest_endpoint: "https://noble-testnet-api.polkachu.com"
|
||||
test:
|
||||
timeout:
|
||||
default: "15m"
|
||||
|
||||
# Production testing
|
||||
production:
|
||||
sonr:
|
||||
rest_endpoint: "https://api.sonr.io"
|
||||
noble:
|
||||
rest_endpoint: "https://noble-api.polkachu.com"
|
||||
test:
|
||||
timeout:
|
||||
default: "30m"
|
||||
retry:
|
||||
max_attempts: 5
|
||||
|
||||
# Notes and Documentation Links
|
||||
documentation:
|
||||
# Useful links
|
||||
links:
|
||||
noble_docs: "https://docs.noble.xyz"
|
||||
sonr_docs: "https://docs.sonr.io"
|
||||
ibc_docs: "https://ibc.cosmos.network"
|
||||
dex_module: "https://github.com/sonr-io/sonr/tree/master/x/dex"
|
||||
|
||||
# Known issues
|
||||
known_issues:
|
||||
- "ICA channel handshake can take 1-2 minutes"
|
||||
- "First swap may timeout if pools are not initialized"
|
||||
- "USDC attestation required for Noble testnet"
|
||||
|
||||
# Troubleshooting
|
||||
troubleshooting:
|
||||
connection_failed: "Verify IBC connection is established: snrd query ibc connection end <connection-id>"
|
||||
account_pending: "Wait for ICA channel handshake to complete (1-2 minutes)"
|
||||
insufficient_funds: "Fund test account: snrd tx bank send validator <address> 100000000usnr"
|
||||
@@ -1,260 +0,0 @@
|
||||
package usdcswapdid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sonr-io/sonr/test/e2e/utils"
|
||||
dextypes "github.com/sonr-io/sonr/x/dex/types"
|
||||
didtypes "github.com/sonr-io/sonr/x/did/types"
|
||||
)
|
||||
|
||||
// CreateTestDID creates a DID document for testing
|
||||
// Returns the created DID document or error
|
||||
// Note: For E2E tests, DIDs should be created using CLI commands before running tests
|
||||
// This function checks if the DID exists and returns it, or returns instructions to create it
|
||||
func CreateTestDID(ctx context.Context, cfg *utils.TestConfig, account, didID string) (*didtypes.DIDDocument, error) {
|
||||
// Check if DID already exists
|
||||
existing, err := QueryDID(ctx, cfg, didID)
|
||||
if err == nil && existing != nil {
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
// DID doesn't exist - return error with instructions
|
||||
return nil, fmt.Errorf("DID %s not found. Create it using CLI:\n"+
|
||||
" snrd tx did create-did %s --from %s --chain-id %s --yes",
|
||||
didID, didID, account, cfg.ChainID)
|
||||
}
|
||||
|
||||
// QueryDID queries a DID document by ID
|
||||
func QueryDID(ctx context.Context, cfg *utils.TestConfig, didID string) (*didtypes.DIDDocument, error) {
|
||||
// Build query URL
|
||||
url := fmt.Sprintf("%s/sonr/did/v1/did/%s", cfg.BaseURL, didID)
|
||||
|
||||
// Response structure
|
||||
var response struct {
|
||||
DidDocument *didtypes.DIDDocument `json:"did_document"`
|
||||
}
|
||||
|
||||
// Make HTTP request
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to query DID: %w", err)
|
||||
}
|
||||
|
||||
return response.DidDocument, nil
|
||||
}
|
||||
|
||||
// QueryDEXAccount queries a DEX account by DID and connection ID
|
||||
func QueryDEXAccount(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string) (*dextypes.InterchainDEXAccount, error) {
|
||||
// Build query URL
|
||||
url := fmt.Sprintf("%s/sonr/dex/v1/account/%s/%s", cfg.BaseURL, didID, connectionID)
|
||||
|
||||
// Response structure
|
||||
var response struct {
|
||||
Account *dextypes.InterchainDEXAccount `json:"account"`
|
||||
}
|
||||
|
||||
// Make HTTP request
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to query DEX account: %w", err)
|
||||
}
|
||||
|
||||
return response.Account, nil
|
||||
}
|
||||
|
||||
// QueryDEXHistory queries transaction history for a DID
|
||||
func QueryDEXHistory(ctx context.Context, cfg *utils.TestConfig, didID string) ([]*dextypes.DEXActivity, error) {
|
||||
// Build query URL
|
||||
url := fmt.Sprintf("%s/sonr/dex/v1/history/%s", cfg.BaseURL, didID)
|
||||
|
||||
// Response structure
|
||||
var response struct {
|
||||
History []*dextypes.DEXActivity `json:"history"`
|
||||
}
|
||||
|
||||
// Make HTTP request
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to query DEX history: %w", err)
|
||||
}
|
||||
|
||||
return response.History, nil
|
||||
}
|
||||
|
||||
// QueryAllDEXAccounts queries all DEX accounts
|
||||
func QueryAllDEXAccounts(ctx context.Context, cfg *utils.TestConfig) ([]*dextypes.InterchainDEXAccount, error) {
|
||||
// Build query URL
|
||||
url := fmt.Sprintf("%s/sonr/dex/v1/accounts", cfg.BaseURL)
|
||||
|
||||
// Response structure
|
||||
var response struct {
|
||||
Accounts []*dextypes.InterchainDEXAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
// Make HTTP request
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to query all DEX accounts: %w", err)
|
||||
}
|
||||
|
||||
return response.Accounts, nil
|
||||
}
|
||||
|
||||
// WaitForDEXAccountActivation waits for a DEX account to become active
|
||||
// Returns true if account is active, false if timeout
|
||||
func WaitForDEXAccountActivation(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string, timeout time.Duration) (bool, error) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
account, err := QueryDEXAccount(ctx, cfg, didID, connectionID)
|
||||
if err != nil {
|
||||
// Account not found yet, continue waiting
|
||||
time.Sleep(cfg.BlockTime)
|
||||
continue
|
||||
}
|
||||
|
||||
if account.Status == dextypes.ACCOUNT_STATUS_ACTIVE {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check for failed status
|
||||
if account.Status == dextypes.ACCOUNT_STATUS_FAILED {
|
||||
return false, fmt.Errorf("DEX account activation failed")
|
||||
}
|
||||
|
||||
time.Sleep(cfg.BlockTime)
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("timeout waiting for account activation")
|
||||
}
|
||||
|
||||
// VerifyICAAccountAddress verifies that an ICA account has been created
|
||||
func VerifyICAAccountAddress(ctx context.Context, cfg *utils.TestConfig, didID, connectionID string) (string, error) {
|
||||
account, err := QueryDEXAccount(ctx, cfg, didID, connectionID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to query DEX account: %w", err)
|
||||
}
|
||||
|
||||
if account.AccountAddress == "" {
|
||||
return "", fmt.Errorf("ICA account address not yet assigned")
|
||||
}
|
||||
|
||||
return account.AccountAddress, nil
|
||||
}
|
||||
|
||||
// ValidateSwapParameters validates swap parameters before execution
|
||||
func ValidateSwapParameters(sourceDenom, targetDenom string, amount, minOut int64) error {
|
||||
if sourceDenom == "" {
|
||||
return fmt.Errorf("source denom cannot be empty")
|
||||
}
|
||||
|
||||
if targetDenom == "" {
|
||||
return fmt.Errorf("target denom cannot be empty")
|
||||
}
|
||||
|
||||
if sourceDenom == targetDenom {
|
||||
return fmt.Errorf("source and target denoms must be different")
|
||||
}
|
||||
|
||||
if amount <= 0 {
|
||||
return fmt.Errorf("swap amount must be positive")
|
||||
}
|
||||
|
||||
if minOut < 0 {
|
||||
return fmt.Errorf("min output cannot be negative")
|
||||
}
|
||||
|
||||
if minOut > amount {
|
||||
return fmt.Errorf("min output cannot exceed input amount (unless exchange rate > 1)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateMinimumOutput calculates minimum output based on slippage tolerance
|
||||
// slippagePct should be between 0 and 100 (e.g., 5 for 5% slippage)
|
||||
func CalculateMinimumOutput(inputAmount int64, slippagePct float64) int64 {
|
||||
if slippagePct < 0 {
|
||||
slippagePct = 0
|
||||
}
|
||||
if slippagePct > 100 {
|
||||
slippagePct = 100
|
||||
}
|
||||
|
||||
multiplier := (100 - slippagePct) / 100
|
||||
return int64(float64(inputAmount) * multiplier)
|
||||
}
|
||||
|
||||
// FormatUSDCAmount formats a USDC amount for display
|
||||
// USDC has 6 decimals, so 1000000 = 1.000000 USDC
|
||||
func FormatUSDCAmount(amount int64) string {
|
||||
whole := amount / 1_000_000
|
||||
fractional := amount % 1_000_000
|
||||
return fmt.Sprintf("%d.%06d USDC", whole, fractional)
|
||||
}
|
||||
|
||||
// FormatSNRAmount formats a SNR amount for display
|
||||
// SNR has 6 decimals (usnr), so 1000000 = 1.000000 SNR
|
||||
func FormatSNRAmount(amount int64) string {
|
||||
whole := amount / 1_000_000
|
||||
fractional := amount % 1_000_000
|
||||
return fmt.Sprintf("%d.%06d SNR", whole, fractional)
|
||||
}
|
||||
|
||||
// ParseIBCDenom parses an IBC denom to extract the base denom
|
||||
// IBC denoms have format: ibc/HASH
|
||||
func ParseIBCDenom(ibcDenom string) (hash string, isIBC bool) {
|
||||
if len(ibcDenom) > 4 && ibcDenom[:4] == "ibc/" {
|
||||
return ibcDenom[4:], true
|
||||
}
|
||||
return ibcDenom, false
|
||||
}
|
||||
|
||||
// BuildNobleIBCDenom builds the IBC denom for Noble USDC on Sonr chain
|
||||
// This requires knowing the IBC channel from Noble to Sonr
|
||||
func BuildNobleIBCDenom(channelID string) string {
|
||||
// In actual implementation, this would use IBC denom trace hashing
|
||||
// For now, return a placeholder
|
||||
return fmt.Sprintf("ibc/noble_usdc_via_%s", channelID)
|
||||
}
|
||||
|
||||
// ExtractSwapEventData extracts relevant data from a swap event
|
||||
type SwapEventData struct {
|
||||
DID string
|
||||
ConnectionID string
|
||||
SourceDenom string
|
||||
TargetDenom string
|
||||
Amount string
|
||||
MinAmountOut string
|
||||
Sequence string
|
||||
SwapType string
|
||||
ICAAddress string
|
||||
}
|
||||
|
||||
// ParseSwapEvent parses a swap event from transaction logs
|
||||
func ParseSwapEvent(events []interface{}) (*SwapEventData, error) {
|
||||
// This would parse the actual event structure from the transaction
|
||||
// For now, return a placeholder
|
||||
return &SwapEventData{}, fmt.Errorf("event parsing not yet implemented")
|
||||
}
|
||||
|
||||
// VerifyNobleConnection verifies that a Noble IBC connection exists
|
||||
func VerifyNobleConnection(ctx context.Context, cfg *utils.TestConfig, connectionID string) error {
|
||||
// Build query URL for connection
|
||||
url := fmt.Sprintf("%s/ibc/core/connection/v1/connections/%s", cfg.BaseURL, connectionID)
|
||||
|
||||
var response struct {
|
||||
Connection struct {
|
||||
State string `json:"state"`
|
||||
} `json:"connection"`
|
||||
}
|
||||
|
||||
if err := cfg.Client.DoRequest(ctx, url, &response); err != nil {
|
||||
return fmt.Errorf("failed to query connection: %w", err)
|
||||
}
|
||||
|
||||
if response.Connection.State != "STATE_OPEN" {
|
||||
return fmt.Errorf("connection is not open: %s", response.Connection.State)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
#!/bin/bash
|
||||
# USDC Swap E2E Test Setup Script
|
||||
# This script helps set up the environment for USDC swap E2E tests
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
SONR_RPC="${SONR_RPC:-http://localhost:26657}"
|
||||
SONR_REST="${SONR_REST:-http://localhost:1317}"
|
||||
FAUCET_URL="${FAUCET_URL:-http://localhost:8000}"
|
||||
NOBLE_CONNECTION="${NOBLE_CONNECTION:-connection-0}"
|
||||
|
||||
echo -e "${BLUE}=== USDC Swap E2E Test Setup ===${NC}\n"
|
||||
|
||||
# Function to print status
|
||||
print_status() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Step 1: Check prerequisites
|
||||
echo -e "${BLUE}Step 1: Checking prerequisites...${NC}"
|
||||
|
||||
if ! command_exists go; then
|
||||
print_error "Go is not installed. Please install Go 1.21 or later."
|
||||
exit 1
|
||||
fi
|
||||
print_status "Go installed: $(go version | awk '{print $3}')"
|
||||
|
||||
if ! command_exists snrd; then
|
||||
print_error "snrd is not installed. Please install Sonr daemon."
|
||||
exit 1
|
||||
fi
|
||||
print_status "snrd installed"
|
||||
|
||||
if ! command_exists jq; then
|
||||
print_warning "jq is not installed. Some features may not work. Install with: brew install jq"
|
||||
else
|
||||
print_status "jq installed"
|
||||
fi
|
||||
|
||||
if ! command_exists curl; then
|
||||
print_error "curl is not installed."
|
||||
exit 1
|
||||
fi
|
||||
print_status "curl installed"
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 2: Check network status
|
||||
echo -e "${BLUE}Step 2: Checking network status...${NC}"
|
||||
|
||||
if curl -s "${SONR_REST}/cosmos/base/tendermint/v1beta1/node_info" >/dev/null 2>&1; then
|
||||
CHAIN_ID=$(curl -s "${SONR_REST}/cosmos/base/tendermint/v1beta1/node_info" | jq -r '.default_node_info.network')
|
||||
print_status "Sonr testnet is running (Chain ID: $CHAIN_ID)"
|
||||
else
|
||||
print_error "Sonr testnet is not accessible at ${SONR_REST}"
|
||||
echo " Start testnet with: make testnet"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if curl -s "${FAUCET_URL}/health" >/dev/null 2>&1; then
|
||||
print_status "Faucet is running"
|
||||
else
|
||||
print_warning "Faucet is not accessible. Tests may fail without funded accounts."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 3: Check IBC connections
|
||||
echo -e "${BLUE}Step 3: Checking IBC connections...${NC}"
|
||||
|
||||
CONNECTIONS=$(snrd query ibc connection connections --node ${SONR_RPC} --output json 2>/dev/null | jq -r '.connections // [] | length')
|
||||
|
||||
if [ "$CONNECTIONS" -eq 0 ]; then
|
||||
print_warning "No IBC connections found"
|
||||
echo " You need to establish a connection to Noble testnet"
|
||||
echo " This typically requires:"
|
||||
echo " 1. Setting up an IBC relayer"
|
||||
echo " 2. Creating a client on each chain"
|
||||
echo " 3. Creating a connection"
|
||||
echo ""
|
||||
echo " For testing without Noble, some tests will be skipped."
|
||||
else
|
||||
print_status "Found $CONNECTIONS IBC connection(s)"
|
||||
|
||||
# Check for Noble connection
|
||||
if snrd query ibc connection end "$NOBLE_CONNECTION" --node ${SONR_RPC} --output json 2>/dev/null | jq -e '.connection.state == "STATE_OPEN"' >/dev/null; then
|
||||
print_status "Noble connection ($NOBLE_CONNECTION) is OPEN"
|
||||
else
|
||||
print_warning "Noble connection ($NOBLE_CONNECTION) not found or not open"
|
||||
echo " Update NOBLE_CONNECTION in config or create the connection"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 4: Check IBC channels
|
||||
echo -e "${BLUE}Step 4: Checking IBC channels...${NC}"
|
||||
|
||||
CHANNELS=$(snrd query ibc channel channels --node ${SONR_RPC} --output json 2>/dev/null | jq -r '.channels // [] | length')
|
||||
|
||||
if [ "$CHANNELS" -eq 0 ]; then
|
||||
print_warning "No IBC channels found"
|
||||
else
|
||||
print_status "Found $CHANNELS IBC channel(s)"
|
||||
|
||||
# List channels
|
||||
if command_exists jq; then
|
||||
echo " Channels:"
|
||||
snrd query ibc channel channels --node ${SONR_RPC} --output json 2>/dev/null | \
|
||||
jq -r '.channels[] | " \(.port_id)/\(.channel_id): \(.state)"'
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 5: Check DEX module
|
||||
echo -e "${BLUE}Step 5: Checking DEX module...${NC}"
|
||||
|
||||
if snrd query dex params --node ${SONR_RPC} --output json 2>/dev/null | jq -e '.params.enabled == true' >/dev/null; then
|
||||
print_status "DEX module is enabled"
|
||||
|
||||
if command_exists jq; then
|
||||
MAX_ACCOUNTS=$(snrd query dex params --node ${SONR_RPC} --output json 2>/dev/null | jq -r '.params.max_accounts_per_did // "N/A"')
|
||||
echo " Max accounts per DID: $MAX_ACCOUNTS"
|
||||
fi
|
||||
else
|
||||
print_error "DEX module is not enabled"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 6: Check DID module
|
||||
echo -e "${BLUE}Step 6: Checking DID module...${NC}"
|
||||
|
||||
if snrd query did params --node ${SONR_RPC} 2>/dev/null >/dev/null; then
|
||||
print_status "DID module is available"
|
||||
else
|
||||
print_warning "DID module query failed (module may not be enabled)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 7: Create test configuration
|
||||
echo -e "${BLUE}Step 7: Setting up test configuration...${NC}"
|
||||
|
||||
if [ ! -f "config.yaml" ]; then
|
||||
if [ -f "config.example.yaml" ]; then
|
||||
cp config.example.yaml config.yaml
|
||||
|
||||
# Update connection ID if different from default
|
||||
if [ -n "$NOBLE_CONNECTION" ]; then
|
||||
sed -i.bak "s/connection-0/$NOBLE_CONNECTION/g" config.yaml
|
||||
rm -f config.yaml.bak
|
||||
fi
|
||||
|
||||
print_status "Created config.yaml from example"
|
||||
echo " Review and update config.yaml with your connection details"
|
||||
else
|
||||
print_warning "config.example.yaml not found"
|
||||
fi
|
||||
else
|
||||
print_status "config.yaml already exists"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 8: Install test dependencies
|
||||
echo -e "${BLUE}Step 8: Installing test dependencies...${NC}"
|
||||
|
||||
if go mod download 2>/dev/null; then
|
||||
print_status "Downloaded Go dependencies"
|
||||
else
|
||||
print_warning "Failed to download dependencies (may already be cached)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 9: Fund test accounts (optional)
|
||||
echo -e "${BLUE}Step 9: Test account setup...${NC}"
|
||||
|
||||
read -p "Do you want to create and fund a test account? (y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
# Generate test account
|
||||
TEST_ACCOUNT=$(snrd keys add test_e2e_user --keyring-backend test --output json 2>/dev/null | jq -r '.address' || echo "")
|
||||
|
||||
if [ -n "$TEST_ACCOUNT" ]; then
|
||||
print_status "Created test account: $TEST_ACCOUNT"
|
||||
|
||||
# Try to fund via faucet
|
||||
if curl -s "${FAUCET_URL}/health" >/dev/null 2>&1; then
|
||||
FUND_RESPONSE=$(curl -s -X POST "${FAUCET_URL}/credit" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"address\":\"$TEST_ACCOUNT\",\"denom\":\"snr\"}" || echo "")
|
||||
|
||||
if echo "$FUND_RESPONSE" | grep -q "success"; then
|
||||
print_status "Funded test account with SNR tokens"
|
||||
else
|
||||
print_warning "Failed to fund account via faucet"
|
||||
echo " Fund manually with: snrd tx bank send validator $TEST_ACCOUNT 100000000usnr"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
print_warning "Failed to create test account"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 10: Run quick verification
|
||||
echo -e "${BLUE}Step 10: Running quick verification...${NC}"
|
||||
|
||||
if make check-prereqs 2>/dev/null; then
|
||||
print_status "All prerequisites verified"
|
||||
else
|
||||
print_warning "Some verification checks failed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Final summary
|
||||
echo -e "${BLUE}=== Setup Complete ===${NC}\n"
|
||||
echo "Environment Summary:"
|
||||
echo " Sonr RPC: $SONR_RPC"
|
||||
echo " Sonr REST: $SONR_REST"
|
||||
echo " Faucet: $FAUCET_URL"
|
||||
echo " Chain ID: $CHAIN_ID"
|
||||
echo " IBC Connections: $CONNECTIONS"
|
||||
echo " IBC Channels: $CHANNELS"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Review and update config.yaml if needed"
|
||||
echo " 2. Ensure Noble IBC connection is established"
|
||||
echo " 3. Run tests with: make test"
|
||||
echo ""
|
||||
echo "Useful commands:"
|
||||
echo " make test - Run all tests"
|
||||
echo " make test-verbose - Run with detailed output"
|
||||
echo " make check-ibc - Check IBC status"
|
||||
echo " make help - Show all available commands"
|
||||
echo ""
|
||||
|
||||
# Check if ready to run tests
|
||||
if [ "$CONNECTIONS" -gt 0 ] && [ "$CHANNELS" -gt 0 ]; then
|
||||
echo -e "${GREEN}✓ System appears ready for E2E tests!${NC}"
|
||||
echo ""
|
||||
read -p "Run tests now? (y/N) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
make test
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠ IBC setup incomplete. Some tests may be skipped.${NC}"
|
||||
echo " Set up IBC connections to Noble for full test coverage."
|
||||
fi
|
||||
@@ -1,531 +0,0 @@
|
||||
package usdcswapdid
|
||||
|
||||
/*
|
||||
USDC Swap E2E Tests - Implementation Notes
|
||||
|
||||
CURRENT STATUS:
|
||||
These E2E tests are structured and ready but require manual setup steps before execution.
|
||||
|
||||
LIMITATION:
|
||||
The test framework currently lacks full transaction signing/broadcasting capability.
|
||||
The SignAndBroadcastTx method in StarshipClient is a placeholder that requires proper
|
||||
keyring integration to function.
|
||||
|
||||
MANUAL SETUP REQUIRED:
|
||||
Before running these tests, you must manually create the test resources using the CLI:
|
||||
|
||||
1. Create Test DID:
|
||||
snrd tx did create-did did:snr:test_<timestamp> --from <account> --chain-id sonrtest_1-1 --yes
|
||||
|
||||
2. Register DEX Account:
|
||||
snrd tx dex register-account did:snr:test_<timestamp> connection-noble \
|
||||
--features swap,liquidity --from <account> --chain-id sonrtest_1-1 --yes
|
||||
|
||||
3. Wait for ICA channel handshake (1-2 minutes)
|
||||
|
||||
4. Execute Swaps:
|
||||
snrd tx dex execute-swap did:snr:test_<timestamp> connection-noble \
|
||||
usnr uusdc 1000000 950000 --from <account> --chain-id sonrtest_1-1 --yes
|
||||
|
||||
ALTERNATIVE APPROACH:
|
||||
For automated E2E testing, consider:
|
||||
- Using the setup.sh script to prepare the environment
|
||||
- Implementing a test helper that shells out to the CLI
|
||||
- Integrating with cosmos-sdk/testutil for full transaction support
|
||||
- Using a dedicated test keyring with pre-configured keys
|
||||
|
||||
See README.md for detailed setup instructions and troubleshooting.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/sonr-io/sonr/test/e2e/utils"
|
||||
dextypes "github.com/sonr-io/sonr/x/dex/types"
|
||||
didtypes "github.com/sonr-io/sonr/x/did/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// Noble testnet configuration
|
||||
NobleChainID = "noble-grand-1"
|
||||
NobleConnectionID = "connection-noble" // Update with actual connection ID
|
||||
NobleUSDCDenom = "uusdc"
|
||||
|
||||
// Test amounts (USDC has 6 decimals like native USDC)
|
||||
USDCTestAmount = 1_000_000 // 1 USDC
|
||||
USDCSwapAmount = 5_000_000 // 5 USDC
|
||||
SNRTestAmount = 10_000_000 // 10 SNR
|
||||
SNRSwapAmount = 1_000_000 // 1 SNR
|
||||
|
||||
// Slippage tolerance (5%)
|
||||
SlippageTolerance = 0.05
|
||||
)
|
||||
|
||||
// USDCSwapTestSuite is the main test suite for USDC swap functionality
|
||||
type USDCSwapTestSuite struct {
|
||||
suite.Suite
|
||||
cfg *utils.TestConfig
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
testDID string
|
||||
testAccount string
|
||||
}
|
||||
|
||||
// SetupSuite runs once before all tests
|
||||
func (s *USDCSwapTestSuite) SetupSuite() {
|
||||
s.cfg = utils.NewTestConfig()
|
||||
s.ctx, s.cancel = context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
|
||||
// Setup test account
|
||||
users := utils.SetupTestUsers(s.T(), s.cfg, math.NewInt(SNRTestAmount))
|
||||
require.Len(s.T(), users, 1, "should create at least one test user")
|
||||
s.testAccount = users[0].Address
|
||||
|
||||
s.T().Logf("Test suite initialized with account: %s", s.testAccount)
|
||||
}
|
||||
|
||||
// TearDownSuite runs once after all tests
|
||||
func (s *USDCSwapTestSuite) TearDownSuite() {
|
||||
if s.cancel != nil {
|
||||
s.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// SetupTest runs before each test
|
||||
func (s *USDCSwapTestSuite) SetupTest() {
|
||||
// Create unique DID for each test
|
||||
s.testDID = fmt.Sprintf("did:snr:test_%d", time.Now().UnixNano())
|
||||
s.T().Logf("Created test DID: %s", s.testDID)
|
||||
}
|
||||
|
||||
// TestUSDCSwapE2E is the main end-to-end test function
|
||||
func TestUSDCSwapE2E(t *testing.T) {
|
||||
suite.Run(t, new(USDCSwapTestSuite))
|
||||
}
|
||||
|
||||
// Test01_VerifyNobleConfiguration verifies Noble chain configuration
|
||||
func (s *USDCSwapTestSuite) Test01_VerifyNobleConfiguration() {
|
||||
s.T().Log("=== Test 01: Verify Noble Configuration ===")
|
||||
|
||||
// Verify Noble configuration constants
|
||||
require.Equal(s.T(), NobleChainID, dextypes.NobleTestnetChainID,
|
||||
"Noble chain ID should match")
|
||||
require.Equal(s.T(), NobleUSDCDenom, dextypes.NobleUSDCDenom,
|
||||
"USDC denom should match")
|
||||
require.Equal(s.T(), uint8(6), dextypes.NobleUSDCDecimals,
|
||||
"USDC decimals should be 6")
|
||||
|
||||
// Verify Noble is recognized as a Noble chain
|
||||
require.True(s.T(), dextypes.IsNobleChain(NobleChainID),
|
||||
"Noble testnet chain ID should be recognized")
|
||||
|
||||
s.T().Log("✓ Noble configuration verified successfully")
|
||||
}
|
||||
|
||||
// Test02_CreateDIDDocument creates a DID document for testing
|
||||
func (s *USDCSwapTestSuite) Test02_CreateDIDDocument() {
|
||||
s.T().Log("=== Test 02: Create DID Document ===")
|
||||
|
||||
// Create DID document
|
||||
did, err := CreateTestDID(s.ctx, s.cfg, s.testAccount, s.testDID)
|
||||
require.NoError(s.T(), err, "failed to create DID document")
|
||||
require.NotNil(s.T(), did, "DID document should not be nil")
|
||||
require.Equal(s.T(), s.testDID, did.Id, "DID ID should match")
|
||||
|
||||
s.T().Logf("✓ DID document created: %s", did.Id)
|
||||
|
||||
// Verify DID can be queried
|
||||
queriedDID, err := QueryDID(s.ctx, s.cfg, s.testDID)
|
||||
require.NoError(s.T(), err, "failed to query DID")
|
||||
require.NotNil(s.T(), queriedDID, "queried DID should not be nil")
|
||||
require.Equal(s.T(), s.testDID, queriedDID.Id, "queried DID should match")
|
||||
|
||||
s.T().Log("✓ DID query verification successful")
|
||||
}
|
||||
|
||||
// Test03_RegisterDEXAccount registers a DEX account on Noble connection
|
||||
func (s *USDCSwapTestSuite) Test03_RegisterDEXAccount() {
|
||||
s.T().Log("=== Test 03: Register DEX Account ===")
|
||||
|
||||
// First create DID
|
||||
_, err := CreateTestDID(s.ctx, s.cfg, s.testAccount, s.testDID)
|
||||
require.NoError(s.T(), err, "failed to create DID")
|
||||
|
||||
// Register DEX account
|
||||
msg := &dextypes.MsgRegisterDEXAccount{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
Features: []string{"swap", "liquidity"},
|
||||
Metadata: "Noble USDC swap test account",
|
||||
}
|
||||
|
||||
txResp, err := s.cfg.Client.SignAndBroadcastTx(s.ctx, s.testAccount, msg)
|
||||
require.NoError(s.T(), err, "failed to register DEX account")
|
||||
require.Equal(s.T(), uint32(0), txResp.Code, "transaction should succeed")
|
||||
|
||||
s.T().Logf("✓ DEX account registered, tx hash: %s", txResp.TxHash)
|
||||
|
||||
// Wait for account activation
|
||||
time.Sleep(2 * s.cfg.BlockTime)
|
||||
|
||||
// Query the created account
|
||||
account, err := QueryDEXAccount(s.ctx, s.cfg, s.testDID, NobleConnectionID)
|
||||
require.NoError(s.T(), err, "failed to query DEX account")
|
||||
require.NotNil(s.T(), account, "DEX account should exist")
|
||||
require.Equal(s.T(), s.testDID, account.Did, "DID should match")
|
||||
require.Equal(s.T(), NobleConnectionID, account.ConnectionId, "connection should match")
|
||||
|
||||
// Account may be pending if ICA channel is not yet established
|
||||
s.T().Logf("✓ DEX account status: %s", account.Status.String())
|
||||
s.T().Logf("✓ ICA port ID: %s", account.PortId)
|
||||
|
||||
if account.AccountAddress != "" {
|
||||
s.T().Logf("✓ ICA address: %s", account.AccountAddress)
|
||||
} else {
|
||||
s.T().Log("⚠ ICA address pending (channel handshake may be in progress)")
|
||||
}
|
||||
}
|
||||
|
||||
// Test04_ExecuteUSDCSwap_SNRToUSDC tests swapping SNR to USDC
|
||||
func (s *USDCSwapTestSuite) Test04_ExecuteUSDCSwap_SNRToUSDC() {
|
||||
s.T().Log("=== Test 04: Execute USDC Swap (SNR → USDC) ===")
|
||||
|
||||
// Setup: Create DID and register DEX account
|
||||
s.setupDEXAccount()
|
||||
|
||||
// Get initial balances
|
||||
initialSNR := s.getBalance(s.testAccount, s.cfg.StakingDenom)
|
||||
s.T().Logf("Initial SNR balance: %s", initialSNR.String())
|
||||
|
||||
// Calculate expected output (95% of input for 5% slippage)
|
||||
swapAmount := math.NewInt(SNRSwapAmount)
|
||||
minUSDCOut := math.NewInt(SNRSwapAmount).
|
||||
MulRaw(95).QuoRaw(100) // 5% slippage tolerance
|
||||
|
||||
// Execute swap
|
||||
swapMsg := &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
SourceDenom: s.cfg.StakingDenom,
|
||||
TargetDenom: NobleUSDCDenom,
|
||||
Amount: swapAmount,
|
||||
MinAmountOut: minUSDCOut,
|
||||
Route: fmt.Sprintf("noble:%s", NobleConnectionID),
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
}
|
||||
|
||||
txResp, err := s.cfg.Client.SignAndBroadcastTx(s.ctx, s.testAccount, swapMsg)
|
||||
require.NoError(s.T(), err, "failed to execute swap")
|
||||
require.Equal(s.T(), uint32(0), txResp.Code, "swap transaction should succeed")
|
||||
|
||||
s.T().Logf("✓ Swap transaction broadcasted, tx hash: %s", txResp.TxHash)
|
||||
|
||||
// Verify swap event was emitted
|
||||
s.verifySwapEvent(txResp.TxHash, s.testDID, NobleConnectionID)
|
||||
|
||||
// Note: In a real IBC swap, we would need to wait for ICA callback
|
||||
// and verify balances on both chains. For this test, we verify the
|
||||
// transaction was accepted and properly formatted.
|
||||
s.T().Log("✓ Swap execution test completed")
|
||||
}
|
||||
|
||||
// Test05_ExecuteUSDCSwap_USDCToSNR tests swapping USDC to SNR
|
||||
func (s *USDCSwapTestSuite) Test05_ExecuteUSDCSwap_USDCToSNR() {
|
||||
s.T().Log("=== Test 05: Execute USDC Swap (USDC → SNR) ===")
|
||||
|
||||
// Setup: Create DID and register DEX account
|
||||
s.setupDEXAccount()
|
||||
|
||||
// Calculate expected output
|
||||
swapAmount := math.NewInt(USDCSwapAmount)
|
||||
minSNROut := math.NewInt(USDCSwapAmount).
|
||||
MulRaw(95).QuoRaw(100) // 5% slippage tolerance
|
||||
|
||||
// Execute swap
|
||||
swapMsg := &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
SourceDenom: NobleUSDCDenom,
|
||||
TargetDenom: s.cfg.StakingDenom,
|
||||
Amount: swapAmount,
|
||||
MinAmountOut: minSNROut,
|
||||
Route: fmt.Sprintf("noble:%s", NobleConnectionID),
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
}
|
||||
|
||||
txResp, err := s.cfg.Client.SignAndBroadcastTx(s.ctx, s.testAccount, swapMsg)
|
||||
require.NoError(s.T(), err, "failed to execute swap")
|
||||
require.Equal(s.T(), uint32(0), txResp.Code, "swap transaction should succeed")
|
||||
|
||||
s.T().Logf("✓ Swap transaction broadcasted, tx hash: %s", txResp.TxHash)
|
||||
|
||||
// Verify swap event
|
||||
s.verifySwapEvent(txResp.TxHash, s.testDID, NobleConnectionID)
|
||||
|
||||
s.T().Log("✓ Reverse swap execution test completed")
|
||||
}
|
||||
|
||||
// Test06_SwapWithInvalidParameters tests swap with invalid parameters
|
||||
func (s *USDCSwapTestSuite) Test06_SwapWithInvalidParameters() {
|
||||
s.T().Log("=== Test 06: Swap With Invalid Parameters ===")
|
||||
|
||||
// Setup: Create DID and register DEX account
|
||||
s.setupDEXAccount()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
msg *dextypes.MsgExecuteSwap
|
||||
expectedErr bool
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "zero_amount",
|
||||
msg: &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
SourceDenom: s.cfg.StakingDenom,
|
||||
TargetDenom: NobleUSDCDenom,
|
||||
Amount: math.ZeroInt(),
|
||||
MinAmountOut: math.NewInt(1000),
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
},
|
||||
expectedErr: true,
|
||||
description: "swap with zero amount should fail",
|
||||
},
|
||||
{
|
||||
name: "same_denom",
|
||||
msg: &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
SourceDenom: s.cfg.StakingDenom,
|
||||
TargetDenom: s.cfg.StakingDenom,
|
||||
Amount: math.NewInt(1000),
|
||||
MinAmountOut: math.NewInt(900),
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
},
|
||||
expectedErr: true,
|
||||
description: "swapping same denom should fail",
|
||||
},
|
||||
{
|
||||
name: "negative_min_out",
|
||||
msg: &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
SourceDenom: s.cfg.StakingDenom,
|
||||
TargetDenom: NobleUSDCDenom,
|
||||
Amount: math.NewInt(1000),
|
||||
MinAmountOut: math.NewInt(-100),
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
},
|
||||
expectedErr: true,
|
||||
description: "negative min amount out should fail",
|
||||
},
|
||||
{
|
||||
name: "invalid_connection",
|
||||
msg: &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: "connection-invalid-999",
|
||||
SourceDenom: s.cfg.StakingDenom,
|
||||
TargetDenom: NobleUSDCDenom,
|
||||
Amount: math.NewInt(1000),
|
||||
MinAmountOut: math.NewInt(900),
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
},
|
||||
expectedErr: true,
|
||||
description: "invalid connection ID should fail",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.T().Run(tc.name, func(t *testing.T) {
|
||||
t.Logf("Testing: %s", tc.description)
|
||||
|
||||
txResp, err := s.cfg.Client.SignAndBroadcastTx(s.ctx, s.testAccount, tc.msg)
|
||||
|
||||
if tc.expectedErr {
|
||||
// Transaction should either fail to broadcast or have non-zero code
|
||||
if err == nil {
|
||||
require.NotEqual(t, uint32(0), txResp.Code,
|
||||
"transaction should fail with non-zero code")
|
||||
t.Logf("✓ Transaction failed as expected with code: %d, log: %s",
|
||||
txResp.Code, txResp.RawLog)
|
||||
} else {
|
||||
t.Logf("✓ Transaction failed as expected with error: %v", err)
|
||||
}
|
||||
} else {
|
||||
require.NoError(t, err, "transaction should succeed")
|
||||
require.Equal(t, uint32(0), txResp.Code, "transaction should have code 0")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
s.T().Log("✓ Invalid parameter tests completed")
|
||||
}
|
||||
|
||||
// Test07_SwapWithUCANPermission tests swap with UCAN authorization
|
||||
func (s *USDCSwapTestSuite) Test07_SwapWithUCANPermission() {
|
||||
s.T().Skip("UCAN integration requires full UCAN module setup")
|
||||
s.T().Log("=== Test 07: Swap With UCAN Permission ===")
|
||||
|
||||
// Setup: Create DID and register DEX account
|
||||
s.setupDEXAccount()
|
||||
|
||||
// TODO: Generate UCAN token with swap capabilities
|
||||
// ucanToken := generateUCANToken(s.testDID, "swap", NobleConnectionID)
|
||||
|
||||
swapMsg := &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
SourceDenom: s.cfg.StakingDenom,
|
||||
TargetDenom: NobleUSDCDenom,
|
||||
Amount: math.NewInt(SNRSwapAmount),
|
||||
MinAmountOut: math.NewInt(SNRSwapAmount * 95 / 100),
|
||||
UcanToken: "", // Would be populated with actual token
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
}
|
||||
|
||||
txResp, err := s.cfg.Client.SignAndBroadcastTx(s.ctx, s.testAccount, swapMsg)
|
||||
require.NoError(s.T(), err, "failed to execute swap with UCAN")
|
||||
require.Equal(s.T(), uint32(0), txResp.Code, "swap with UCAN should succeed")
|
||||
|
||||
s.T().Log("✓ UCAN-authorized swap test completed")
|
||||
}
|
||||
|
||||
// Test08_QueryDEXHistory queries transaction history
|
||||
func (s *USDCSwapTestSuite) Test08_QueryDEXHistory() {
|
||||
s.T().Log("=== Test 08: Query DEX History ===")
|
||||
|
||||
// Setup and execute a swap
|
||||
s.setupDEXAccount()
|
||||
s.executeTestSwap()
|
||||
|
||||
// Query DEX history
|
||||
history, err := QueryDEXHistory(s.ctx, s.cfg, s.testDID)
|
||||
require.NoError(s.T(), err, "failed to query DEX history")
|
||||
|
||||
if len(history) > 0 {
|
||||
s.T().Logf("✓ Found %d transactions in history", len(history))
|
||||
for i, activity := range history {
|
||||
s.T().Logf(" [%d] Type: %s, Status: %s, Height: %d",
|
||||
i+1, activity.Type, activity.Status, activity.BlockHeight)
|
||||
}
|
||||
} else {
|
||||
s.T().Log("⚠ No history found (ICA may not be fully configured)")
|
||||
}
|
||||
}
|
||||
|
||||
// Test09_MultipleSwaps tests executing multiple swaps in sequence
|
||||
func (s *USDCSwapTestSuite) Test09_MultipleSwaps() {
|
||||
s.T().Log("=== Test 09: Multiple Sequential Swaps ===")
|
||||
|
||||
// Setup
|
||||
s.setupDEXAccount()
|
||||
|
||||
numSwaps := 3
|
||||
for i := 0; i < numSwaps; i++ {
|
||||
s.T().Logf("Executing swap %d/%d", i+1, numSwaps)
|
||||
|
||||
swapMsg := &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
SourceDenom: s.cfg.StakingDenom,
|
||||
TargetDenom: NobleUSDCDenom,
|
||||
Amount: math.NewInt(100_000), // Small amounts for multiple swaps
|
||||
MinAmountOut: math.NewInt(95_000),
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
}
|
||||
|
||||
txResp, err := s.cfg.Client.SignAndBroadcastTx(s.ctx, s.testAccount, swapMsg)
|
||||
require.NoError(s.T(), err, "swap %d should succeed", i+1)
|
||||
require.Equal(s.T(), uint32(0), txResp.Code, "swap %d should have code 0", i+1)
|
||||
|
||||
s.T().Logf("✓ Swap %d completed, tx: %s", i+1, txResp.TxHash)
|
||||
|
||||
// Small delay between swaps
|
||||
time.Sleep(s.cfg.BlockTime)
|
||||
}
|
||||
|
||||
s.T().Logf("✓ Successfully executed %d swaps", numSwaps)
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
|
||||
func (s *USDCSwapTestSuite) setupDEXAccount() {
|
||||
// Create DID if not exists
|
||||
_, err := CreateTestDID(s.ctx, s.cfg, s.testAccount, s.testDID)
|
||||
if err != nil {
|
||||
// DID might already exist, check query
|
||||
_, queryErr := QueryDID(s.ctx, s.cfg, s.testDID)
|
||||
require.NoError(s.T(), queryErr, "DID should exist or be creatable")
|
||||
}
|
||||
|
||||
// Register DEX account
|
||||
msg := &dextypes.MsgRegisterDEXAccount{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
Features: []string{"swap"},
|
||||
}
|
||||
|
||||
_, err = s.cfg.Client.SignAndBroadcastTx(s.ctx, s.testAccount, msg)
|
||||
// Ignore error if account already exists
|
||||
|
||||
// Wait for registration
|
||||
time.Sleep(2 * s.cfg.BlockTime)
|
||||
}
|
||||
|
||||
func (s *USDCSwapTestSuite) executeTestSwap() {
|
||||
swapMsg := &dextypes.MsgExecuteSwap{
|
||||
Did: s.testDID,
|
||||
ConnectionId: NobleConnectionID,
|
||||
SourceDenom: s.cfg.StakingDenom,
|
||||
TargetDenom: NobleUSDCDenom,
|
||||
Amount: math.NewInt(SNRSwapAmount),
|
||||
MinAmountOut: math.NewInt(SNRSwapAmount * 95 / 100),
|
||||
Timeout: time.Now().Add(60 * time.Second),
|
||||
}
|
||||
|
||||
txResp, err := s.cfg.Client.SignAndBroadcastTx(s.ctx, s.testAccount, swapMsg)
|
||||
require.NoError(s.T(), err, "test swap should succeed")
|
||||
require.Equal(s.T(), uint32(0), txResp.Code, "test swap should have code 0")
|
||||
}
|
||||
|
||||
func (s *USDCSwapTestSuite) getBalance(address, denom string) math.Int {
|
||||
balance, err := s.cfg.Client.GetBalance(s.ctx, address, denom)
|
||||
require.NoError(s.T(), err, "should query balance")
|
||||
return balance
|
||||
}
|
||||
|
||||
func (s *USDCSwapTestSuite) verifySwapEvent(txHash, expectedDID, expectedConnection string) {
|
||||
// Query events for the transaction
|
||||
height, err := s.cfg.Client.GetLatestBlockHeight(s.ctx)
|
||||
require.NoError(s.T(), err, "should get block height")
|
||||
|
||||
// Search for swap events
|
||||
events, err := s.cfg.Client.QueryEventsByType(s.ctx,
|
||||
dextypes.EventTypeSwapExecuted, height-10, height)
|
||||
|
||||
if err != nil || len(events.Events) == 0 {
|
||||
s.T().Log("⚠ Swap event not found (may be indexed later)")
|
||||
return
|
||||
}
|
||||
|
||||
// Verify event contains expected attributes
|
||||
for _, event := range events.Events {
|
||||
for _, attr := range event.Attributes {
|
||||
if attr.Key == "did" && attr.Value == expectedDID {
|
||||
s.T().Logf("✓ Found swap event for DID: %s", expectedDID)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.T().Log("⚠ Swap event attributes not yet indexed")
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "Preserve",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
# Noble Testnet Integration for Sonr DEX Module
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the integration of the Noble testnet with Sonr's x/dex module. Noble is a Cosmos appchain purpose-built for native asset issuance, particularly USDC, in the IBC ecosystem.
|
||||
|
||||
## What is Noble?
|
||||
|
||||
Noble is an application-specific blockchain built on the Cosmos SDK that serves as the canonical issuance hub for USDC and other real-world assets (RWAs) in the Cosmos ecosystem. Key features:
|
||||
|
||||
- **Native USDC Issuance**: Circle's official USDC is natively issued on Noble
|
||||
- **IBC Connectivity**: Seamlessly transfers assets across 50+ IBC-enabled chains
|
||||
- **Packet Forwarding**: Enables "1-click" asset transfers between chains
|
||||
- **CCTP Integration**: Circle's Cross-Chain Transfer Protocol for bridging from EVM chains
|
||||
|
||||
## Noble Testnet Configuration
|
||||
|
||||
### Chain Details
|
||||
|
||||
- **Chain ID**: `noble-grand-1`
|
||||
- **RPC Endpoint**: `https://noble-testnet-rpc.polkachu.com:443`
|
||||
- **gRPC Endpoint**: `noble-testnet-grpc.polkachu.com:21590`
|
||||
- **USDC Denomination**: `uusdc` (micro-USDC, 6 decimals)
|
||||
|
||||
### Default Configuration
|
||||
|
||||
The Noble testnet is included in the default DEX module parameters:
|
||||
|
||||
```go
|
||||
AllowedConnections: []string{
|
||||
"noble-grand-1", // Noble testnet - USDC hub
|
||||
"osmo-test-5", // Osmosis testnet
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Features
|
||||
|
||||
### 1. USDC Helper Functions
|
||||
|
||||
The integration includes helper functions for working with USDC:
|
||||
|
||||
```go
|
||||
// Convert base units to USDC
|
||||
usdcAmount := ConvertToUSDC(sdk.NewInt(1500000)) // Returns 1.5 USDC
|
||||
|
||||
// Convert USDC to base units
|
||||
baseUnits := ConvertFromUSDC(sdk.MustNewDecFromStr("1.5")) // Returns 1500000
|
||||
|
||||
// Format for display
|
||||
formatted := FormatUSDCAmount(sdk.NewInt(1500000)) // Returns "1.500000 USDC"
|
||||
|
||||
// Parse from string
|
||||
amount, err := ParseUSDCAmount("1.5") // Returns 1500000 base units
|
||||
```
|
||||
|
||||
### 2. Chain Configuration
|
||||
|
||||
```go
|
||||
// Get Noble testnet configuration
|
||||
config := GetNobleTestnetConfig()
|
||||
// Returns: NobleChainConfig{
|
||||
// ChainID: "noble-grand-1",
|
||||
// RPCEndpoint: "https://noble-testnet-rpc.polkachu.com:443",
|
||||
// GRPCEndpoint: "noble-testnet-grpc.polkachu.com:21590",
|
||||
// USDCDenom: "uusdc",
|
||||
// Decimals: 6,
|
||||
// }
|
||||
|
||||
// Check if a chain is Noble
|
||||
isNoble := IsNobleChain("noble-grand-1") // Returns true
|
||||
```
|
||||
|
||||
### 3. Trading Pairs
|
||||
|
||||
Common USDC trading pairs are predefined:
|
||||
|
||||
```go
|
||||
pairs := GetNobleUSDCPairs()
|
||||
// Returns pairs like:
|
||||
// - ATOM/USDC
|
||||
// - OSMO/USDC
|
||||
// - AKT/USDC
|
||||
// - JUNO/USDC
|
||||
// - STARS/USDC
|
||||
```
|
||||
|
||||
### 4. Swap Parameters
|
||||
|
||||
Structured parameters for Noble swaps:
|
||||
|
||||
```go
|
||||
swapParams := NobleSwapParams{
|
||||
InputDenom: "uatom",
|
||||
OutputDenom: "uusdc",
|
||||
Amount: sdk.NewInt(1000000), // 1 ATOM
|
||||
MinOutput: sdk.NewInt(5000000), // Min 5 USDC
|
||||
Receiver: "sonr1...",
|
||||
}
|
||||
|
||||
if err := swapParams.Validate(); err != nil {
|
||||
// Handle validation error
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Liquidity Parameters
|
||||
|
||||
Parameters for providing liquidity:
|
||||
|
||||
```go
|
||||
liquidityParams := NobleLiquidityParams{
|
||||
PoolID: "1",
|
||||
Token0: "uusdc",
|
||||
Token1: "uatom",
|
||||
Amount0: sdk.NewInt(1000000), // 1 USDC
|
||||
Amount1: sdk.NewInt(100000), // 0.1 ATOM
|
||||
MinShares: sdk.NewInt(1),
|
||||
}
|
||||
|
||||
if err := liquidityParams.Validate(); err != nil {
|
||||
// Handle validation error
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Registering a DEX Account for Noble
|
||||
|
||||
```bash
|
||||
# Register an Interchain Account on Noble testnet
|
||||
sonrd tx dex register-dex-account \
|
||||
did:sonr:user:abc123 \
|
||||
connection-0 \
|
||||
noble-grand-1 \
|
||||
--from mykey \
|
||||
--chain-id sonr_1-1
|
||||
```
|
||||
|
||||
### Executing a Swap via Noble
|
||||
|
||||
```bash
|
||||
# Swap ATOM for USDC on Noble
|
||||
sonrd tx dex execute-swap \
|
||||
did:sonr:user:abc123 \
|
||||
connection-0 \
|
||||
uatom \
|
||||
uusdc \
|
||||
1000000 \
|
||||
5000000 \
|
||||
--from mykey \
|
||||
--chain-id sonr_1-1
|
||||
```
|
||||
|
||||
### Querying USDC Balance
|
||||
|
||||
```bash
|
||||
# Query USDC balance on Noble for a DEX account
|
||||
sonrd query dex balance \
|
||||
did:sonr:user:abc123 \
|
||||
connection-0 \
|
||||
uusdc
|
||||
```
|
||||
|
||||
## Module Parameters
|
||||
|
||||
The DEX module includes the following default parameters for Noble integration:
|
||||
|
||||
```yaml
|
||||
params:
|
||||
enabled: true
|
||||
max_accounts_per_did: 5
|
||||
default_timeout_seconds: 600
|
||||
allowed_connections:
|
||||
- noble-grand-1 # Noble testnet
|
||||
- osmo-test-5 # Osmosis testnet
|
||||
min_swap_amount: "1000"
|
||||
max_daily_volume: "1000000000000"
|
||||
rate_limits:
|
||||
max_ops_per_block: 10
|
||||
max_ops_per_did_per_day: 100
|
||||
cooldown_blocks: 5
|
||||
fees:
|
||||
swap_fee_bps: 30 # 0.3%
|
||||
liquidity_fee_bps: 20 # 0.2%
|
||||
order_fee_bps: 10 # 0.1%
|
||||
fee_collector: ""
|
||||
```
|
||||
|
||||
## IBC Connection Setup
|
||||
|
||||
To establish an IBC connection with Noble testnet:
|
||||
|
||||
### 1. Create IBC Client
|
||||
|
||||
```bash
|
||||
# Create IBC client for Noble on Sonr chain
|
||||
hermes create client \
|
||||
--host-chain sonr_1-1 \
|
||||
--reference-chain noble-grand-1
|
||||
```
|
||||
|
||||
### 2. Create IBC Connection
|
||||
|
||||
```bash
|
||||
# Create IBC connection
|
||||
hermes create connection \
|
||||
--a-chain sonr_1-1 \
|
||||
--b-chain noble-grand-1
|
||||
```
|
||||
|
||||
### 3. Create Transfer Channel
|
||||
|
||||
```bash
|
||||
# Create transfer channel
|
||||
hermes create channel \
|
||||
--a-chain sonr_1-1 \
|
||||
--a-connection connection-0 \
|
||||
--a-port transfer \
|
||||
--b-port transfer
|
||||
```
|
||||
|
||||
### 4. Verify Connection
|
||||
|
||||
```bash
|
||||
# Query IBC connections
|
||||
sonrd query ibc connection connections
|
||||
|
||||
# Query IBC channels
|
||||
sonrd query ibc channel channels
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### 1. Connection Whitelisting
|
||||
|
||||
Only connections listed in `allowed_connections` parameter can be used:
|
||||
|
||||
```go
|
||||
func ValidateNobleConnection(connectionID string, allowedConnections []string) error {
|
||||
for _, allowed := range allowedConnections {
|
||||
if connectionID == allowed {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("connection %s not in allowed connections list", connectionID)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Rate Limiting
|
||||
|
||||
The module implements rate limiting to prevent abuse:
|
||||
|
||||
- **Per Block**: Maximum 10 operations per block
|
||||
- **Per DID**: Maximum 100 operations per day
|
||||
- **Cooldown**: 5 block cooldown between operations
|
||||
|
||||
### 3. Amount Validation
|
||||
|
||||
All amounts are validated before execution:
|
||||
|
||||
- Minimum swap amount: 1000 base units
|
||||
- Maximum daily volume per DID: 1,000,000,000,000 base units
|
||||
- Positive amount checks on all operations
|
||||
|
||||
### 4. UCAN Authorization
|
||||
|
||||
All DEX operations require valid UCAN tokens:
|
||||
|
||||
```go
|
||||
// UCAN capabilities for DEX operations
|
||||
capabilities := []string{
|
||||
"dex/swap",
|
||||
"dex/liquidity/provide",
|
||||
"dex/liquidity/remove",
|
||||
"dex/order/create",
|
||||
"dex/order/cancel",
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```bash
|
||||
# Run DEX module tests
|
||||
cd x/dex
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
### E2E Tests
|
||||
|
||||
```bash
|
||||
# Run end-to-end tests
|
||||
cd test/e2e
|
||||
go test -v -run TestDEXModuleOperations
|
||||
```
|
||||
|
||||
### Manual Testing
|
||||
|
||||
1. **Start local testnet**:
|
||||
```bash
|
||||
cd networks/testnet
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. **Register account**:
|
||||
```bash
|
||||
sonrd tx dex register-dex-account did:sonr:test connection-0 noble-grand-1 --from test
|
||||
```
|
||||
|
||||
3. **Execute swap**:
|
||||
```bash
|
||||
sonrd tx dex execute-swap did:sonr:test connection-0 uatom uusdc 1000000 500000 --from test
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
### Phase 1: Testnet Integration ✅
|
||||
- [x] Add Noble testnet to allowed connections
|
||||
- [x] Create USDC helper functions
|
||||
- [x] Implement chain configuration
|
||||
- [x] Add documentation
|
||||
|
||||
### Phase 2: Enhanced Features 🚧
|
||||
- [ ] Implement actual ICA swap execution
|
||||
- [ ] Add multi-hop routing via USDC
|
||||
- [ ] Integrate with Osmosis pools
|
||||
- [ ] Add limit order support
|
||||
|
||||
### Phase 3: Mainnet Deployment 📋
|
||||
- [ ] Security audit
|
||||
- [ ] Add Noble mainnet configuration
|
||||
- [ ] Implement advanced slippage protection
|
||||
- [ ] Add monitoring and alerting
|
||||
|
||||
## Resources
|
||||
|
||||
### Noble Documentation
|
||||
- **Developer Hub**: https://www.noble.xyz/dev-hub
|
||||
- **Docs**: https://docs.noble.xyz
|
||||
- **GitHub**: https://github.com/noble-assets/noble
|
||||
|
||||
### Cosmos IBC
|
||||
- **IBC Protocol**: https://ibc.cosmos.network
|
||||
- **ICA Controller**: https://github.com/cosmos/ibc-go/tree/main/modules/apps/27-interchain-accounts
|
||||
|
||||
### Circle USDC
|
||||
- **USDC on Noble**: https://www.circle.com/multi-chain-usdc/noble
|
||||
- **CCTP**: https://www.circle.com/en/cross-chain-transfer-protocol
|
||||
|
||||
## Support
|
||||
|
||||
For questions or issues with the Noble integration:
|
||||
|
||||
1. Check the [DEX Module README](README.md)
|
||||
2. Review the [Noble documentation](https://docs.noble.xyz)
|
||||
3. Open an issue on GitHub
|
||||
4. Join the Sonr Discord community
|
||||
|
||||
## License
|
||||
|
||||
This integration is part of the Sonr blockchain and follows the same license terms.
|
||||
+7
-31
@@ -75,18 +75,9 @@ func CmdRegisterDEXAccount() *cobra.Command {
|
||||
// CmdExecuteSwap returns a command to execute a swap
|
||||
func CmdExecuteSwap() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "swap [did] [connection-id] [token-in] [token-out-denom] [min-amount-out]",
|
||||
Short: "Execute a token swap through ICA (supports Noble USDC)",
|
||||
Long: `Execute a token swap on a remote chain via Interchain Accounts.
|
||||
|
||||
Examples:
|
||||
# Swap 1000000 uatom for USDC on Noble testnet
|
||||
snrd tx dex swap did:snr:user1 connection-0 1000000uatom uusdc 950000 --ucan-token="..." --timeout=60s
|
||||
|
||||
# Swap USDC for ATOM on Osmosis
|
||||
snrd tx dex swap did:snr:user1 connection-1 1000000uusdc uatom 950000 --route="pool:1"
|
||||
`,
|
||||
Args: cobra.ExactArgs(5),
|
||||
Use: "swap [did] [connection-id] [token-in] [token-out-denom] [min-amount-out] [pool-id]",
|
||||
Short: "Execute a token swap through ICA",
|
||||
Args: cobra.ExactArgs(6),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientTxContext(cmd)
|
||||
if err != nil {
|
||||
@@ -108,18 +99,9 @@ Examples:
|
||||
return fmt.Errorf("invalid min-amount-out: %s", args[4])
|
||||
}
|
||||
|
||||
// Parse optional flags
|
||||
ucanToken, _ := cmd.Flags().GetString("ucan-token")
|
||||
route, _ := cmd.Flags().GetString("route")
|
||||
timeoutStr, _ := cmd.Flags().GetString("timeout")
|
||||
|
||||
// Parse timeout duration
|
||||
timeoutDuration := 30 * time.Second // default
|
||||
if timeoutStr != "" {
|
||||
timeoutDuration, err = time.ParseDuration(timeoutStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid timeout: %w", err)
|
||||
}
|
||||
poolID, err := strconv.ParseUint(args[5], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid pool-id: %w", err)
|
||||
}
|
||||
|
||||
msg := &types.MsgExecuteSwap{
|
||||
@@ -129,9 +111,7 @@ Examples:
|
||||
TargetDenom: tokenOutDenom,
|
||||
Amount: tokenIn.Amount,
|
||||
MinAmountOut: minAmountOut,
|
||||
Route: route,
|
||||
UcanToken: ucanToken,
|
||||
Timeout: time.Now().Add(timeoutDuration),
|
||||
Route: fmt.Sprintf("pool:%d", poolID),
|
||||
}
|
||||
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
@@ -142,10 +122,6 @@ Examples:
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("ucan-token", "", "UCAN authorization token for permission delegation")
|
||||
cmd.Flags().String("route", "", "Optional specific swap route (e.g., 'pool:1' or 'noble:channel-0')")
|
||||
cmd.Flags().String("timeout", "30s", "Timeout duration for the swap (e.g., '30s', '1m')")
|
||||
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
+21
-145
@@ -2,10 +2,7 @@ package keeper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdkerrors "cosmossdk.io/errors"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/sonr-io/sonr/x/dex/types"
|
||||
)
|
||||
@@ -55,15 +52,23 @@ func (ms msgServer) RegisterDEXAccount(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExecuteSwap implements cross-chain swap execution via ICA
|
||||
// This method handles token swaps on remote chains through Interchain Accounts
|
||||
// with special support for Noble USDC integration
|
||||
// TODO: ExecuteSwap - Implement cross-chain swap execution via ICA
|
||||
// This method should handle token swaps on remote chains through Interchain Accounts
|
||||
// Required implementation steps:
|
||||
// 1. Validate the sender's DID exists and is active using did keeper
|
||||
// 2. Verify UCAN token has proper swap capabilities (resource: swap, action: execute)
|
||||
// 3. Retrieve the ICA account for this DID and connection from state
|
||||
// 4. Build the appropriate swap message for the target chain's DEX protocol
|
||||
// 5. Create ICA packet data with the swap transaction
|
||||
// 6. Send ICA packet through IBC channel and await acknowledgment
|
||||
// 7. Store transaction details in DWN for user history tracking
|
||||
// 8. Emit events for indexing and monitoring
|
||||
// Returns: Sequence number and transaction ID on success
|
||||
// ExecuteSwap implements types.MsgServer.
|
||||
func (ms msgServer) ExecuteSwap(
|
||||
ctx context.Context,
|
||||
msg *types.MsgExecuteSwap,
|
||||
) (*types.MsgExecuteSwapResponse, error) {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
|
||||
// Validate UCAN permission if token provided
|
||||
if msg.UcanToken != "" {
|
||||
// Use connection ID as resource ID for swap operations
|
||||
@@ -72,119 +77,13 @@ func (ms msgServer) ExecuteSwap(
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Validate DID exists and is active
|
||||
_, err := ms.didKeeper.GetDIDDocument(sdkCtx, msg.Did)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrapf(err, "DID %s not found", msg.Did)
|
||||
}
|
||||
// Note: Active status check removed as DIDDocument may not have Active field
|
||||
// If needed, add additional validation based on actual DID structure
|
||||
|
||||
// 2. Validate connection exists and is open
|
||||
if err := ms.ValidateConnection(sdkCtx, msg.ConnectionId); err != nil {
|
||||
return nil, sdkerrors.Wrapf(types.ErrInvalidConnection, "connection validation failed: %v", err)
|
||||
}
|
||||
|
||||
// 3. Get or ensure ICA account exists
|
||||
account, err := ms.GetDEXAccount(sdkCtx, msg.Did, msg.ConnectionId)
|
||||
if err != nil {
|
||||
// Account doesn't exist, return error - user must register first
|
||||
return nil, sdkerrors.Wrapf(types.ErrAccountNotFound, "DEX account not found for DID %s on connection %s. Please register first.", msg.Did, msg.ConnectionId)
|
||||
}
|
||||
|
||||
// Verify account is active
|
||||
if account.Status != types.ACCOUNT_STATUS_ACTIVE {
|
||||
return nil, sdkerrors.Wrapf(types.ErrAccountNotActive, "DEX account is not active (status: %s)", account.Status.String())
|
||||
}
|
||||
|
||||
// 4. Validate swap parameters
|
||||
tokenIn := sdk.NewCoin(msg.SourceDenom, msg.Amount)
|
||||
if err := ms.ValidateSwapParameters(tokenIn, msg.TargetDenom, msg.MinAmountOut); err != nil {
|
||||
return nil, sdkerrors.Wrapf(types.ErrInvalidSwapParams, "swap parameter validation failed: %v", err)
|
||||
}
|
||||
|
||||
// 5. Build swap message for the target chain
|
||||
// For Noble USDC swaps, we use IBC transfer
|
||||
// For other DEX chains (like Osmosis), we build chain-specific swap messages
|
||||
var swapMsgs []sdk.Msg
|
||||
var swapType string
|
||||
|
||||
// Check if this is a Noble USDC swap
|
||||
if types.IsNobleChain(account.HostChainId) || msg.SourceDenom == types.NobleUSDCDenom || msg.TargetDenom == types.NobleUSDCDenom {
|
||||
// Build Noble-specific swap message (IBC transfer for USDC)
|
||||
swapMsg, err := ms.BuildNobleSwapMsg(sdkCtx, account.AccountAddress, tokenIn, msg.TargetDenom, msg.MinAmountOut)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrapf(types.ErrSwapFailed, "failed to build Noble swap message: %v", err)
|
||||
}
|
||||
swapMsgs = []sdk.Msg{swapMsg}
|
||||
swapType = "noble_usdc_swap"
|
||||
} else {
|
||||
// Build generic DEX swap message (e.g., Osmosis)
|
||||
swapMsg := ms.BuildOsmosisSwapMsg(account.AccountAddress, 1, tokenIn, msg.TargetDenom, msg.MinAmountOut)
|
||||
swapMsgs = []sdk.Msg{swapMsg}
|
||||
swapType = "osmosis_swap"
|
||||
}
|
||||
|
||||
// 6. Calculate timeout from message or use default
|
||||
timeoutDuration := msg.Timeout.Sub(sdkCtx.BlockTime())
|
||||
if timeoutDuration <= 0 {
|
||||
timeoutDuration = 30 * time.Second // Default 30 second timeout
|
||||
}
|
||||
|
||||
// 7. Send the swap transaction via ICA
|
||||
sequence, err := ms.SendDEXTransaction(
|
||||
sdkCtx,
|
||||
msg.Did,
|
||||
msg.ConnectionId,
|
||||
swapMsgs,
|
||||
fmt.Sprintf("swap_%s_%s_for_%s", swapType, msg.SourceDenom, msg.TargetDenom),
|
||||
timeoutDuration,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrapf(types.ErrSwapFailed, "failed to send swap transaction via ICA: %v", err)
|
||||
}
|
||||
|
||||
// 8. Track transaction in DWN if available
|
||||
if ms.dwnKeeper != nil {
|
||||
// Create swap activity record
|
||||
activity := types.DEXActivity{
|
||||
Type: "swap",
|
||||
Did: msg.Did,
|
||||
ConnectionId: msg.ConnectionId,
|
||||
BlockHeight: sdkCtx.BlockHeight(),
|
||||
Timestamp: sdkCtx.BlockTime(),
|
||||
Status: "pending",
|
||||
Amount: sdk.NewCoins(tokenIn),
|
||||
}
|
||||
|
||||
// Store in DWN for user history (non-blocking)
|
||||
if err := ms.storeActivityInDWN(sdkCtx, msg.Did, &activity); err != nil {
|
||||
// Log but don't fail the transaction
|
||||
ms.Logger(sdkCtx).Error("failed to store swap activity in DWN", "error", err, "did", msg.Did)
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Emit swap event for indexing
|
||||
sdkCtx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeSwapExecuted,
|
||||
sdk.NewAttribute("did", msg.Did),
|
||||
sdk.NewAttribute("connection_id", msg.ConnectionId),
|
||||
sdk.NewAttribute("source_denom", msg.SourceDenom),
|
||||
sdk.NewAttribute("target_denom", msg.TargetDenom),
|
||||
sdk.NewAttribute("amount", msg.Amount.String()),
|
||||
sdk.NewAttribute("min_amount_out", msg.MinAmountOut.String()),
|
||||
sdk.NewAttribute("sequence", fmt.Sprintf("%d", sequence)),
|
||||
sdk.NewAttribute("swap_type", swapType),
|
||||
sdk.NewAttribute("ica_address", account.AccountAddress),
|
||||
),
|
||||
)
|
||||
|
||||
return &types.MsgExecuteSwapResponse{
|
||||
TxHash: "", // Will be populated by ICA callback
|
||||
AmountReceived: "", // Will be populated by ICA callback
|
||||
Sequence: sequence,
|
||||
}, nil
|
||||
// TODO: Implement swap execution via ICA
|
||||
// 1. Validate DID
|
||||
// 2. Get ICA account for this DID and connection
|
||||
// 3. Construct swap message for remote chain
|
||||
// 4. Send ICA packet with swap instruction
|
||||
// 5. Track transaction in DWN
|
||||
return &types.MsgExecuteSwapResponse{}, nil
|
||||
}
|
||||
|
||||
// validateUCANPermission validates UCAN token for a DEX operation
|
||||
@@ -301,7 +200,7 @@ func (ms msgServer) CreateLimitOrder(
|
||||
// 5. Check order status is still open (not filled or already cancelled)
|
||||
// 6. Build order cancellation message for target chain's order book protocol
|
||||
// 7. Create ICA packet data with the cancellation transaction
|
||||
// 8. Send IBC packet through IBC channel and await acknowledgment
|
||||
// 8. Send ICA packet through IBC channel and await acknowledgment
|
||||
// 9. Update order status in local state to cancelled
|
||||
// 10. Update order record in DWN with cancellation details
|
||||
// Returns: Sequence number on successful cancellation
|
||||
@@ -318,26 +217,3 @@ func (ms msgServer) CancelOrder(
|
||||
// 5. Update order status in DWN
|
||||
return &types.MsgCancelOrderResponse{}, nil
|
||||
}
|
||||
|
||||
// storeActivityInDWN stores a DEX activity record in the DWN module
|
||||
func (ms msgServer) storeActivityInDWN(ctx sdk.Context, did string, activity *types.DEXActivity) error {
|
||||
// For now, this is a stub - actual implementation would interface with DWN keeper
|
||||
// to store activity records in the user's decentralized web node
|
||||
if ms.dwnKeeper == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Implement actual DWN storage logic
|
||||
// This would involve:
|
||||
// 1. Serializing the activity to JSON
|
||||
// 2. Creating a DWN record with proper schema
|
||||
// 3. Storing it in the user's vault
|
||||
|
||||
ms.Logger(ctx).Info("DEX activity tracked",
|
||||
"did", did,
|
||||
"type", activity.Type,
|
||||
"status", activity.Status,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -123,118 +123,3 @@ func (k Keeper) ValidateSwapParameters(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildNobleSwapMsg builds a Noble-specific swap message using IBC transfer
|
||||
// Noble swaps typically involve transferring USDC between chains via IBC
|
||||
func (k Keeper) BuildNobleSwapMsg(
|
||||
ctx sdk.Context,
|
||||
senderAddress string,
|
||||
tokenIn sdk.Coin,
|
||||
tokenOutDenom string,
|
||||
minAmountOut math.Int,
|
||||
) (sdk.Msg, error) {
|
||||
// Validate Noble swap parameters
|
||||
params := types.NobleSwapParams{
|
||||
InputDenom: tokenIn.Denom,
|
||||
OutputDenom: tokenOutDenom,
|
||||
Amount: tokenIn.Amount,
|
||||
MinOutput: minAmountOut,
|
||||
Receiver: senderAddress,
|
||||
}
|
||||
|
||||
if err := params.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid Noble swap params: %w", err)
|
||||
}
|
||||
|
||||
// For Noble USDC, we primarily use IBC transfers
|
||||
// In a full implementation, this would:
|
||||
// 1. Determine the IBC channel to Noble
|
||||
// 2. Build an IBC transfer message to send USDC
|
||||
// 3. Include proper timeout and memo for swap routing
|
||||
|
||||
// For now, return a placeholder bank send message
|
||||
// In production, this should be an IBC transfer message:
|
||||
// transferMsg := &transfertypes.MsgTransfer{
|
||||
// SourcePort: "transfer",
|
||||
// SourceChannel: channelID,
|
||||
// Token: tokenIn,
|
||||
// Sender: senderAddress,
|
||||
// Receiver: senderAddress,
|
||||
// TimeoutHeight: clienttypes.NewHeight(0, 0),
|
||||
// TimeoutTimestamp: uint64(ctx.BlockTime().Add(30 * time.Second).UnixNano()),
|
||||
// Memo: fmt.Sprintf("swap:%s:%s", tokenOutDenom, minAmountOut.String()),
|
||||
// }
|
||||
|
||||
return &banktypes.MsgSend{
|
||||
FromAddress: senderAddress,
|
||||
ToAddress: senderAddress,
|
||||
Amount: sdk.NewCoins(tokenIn),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildSwapRoute determines the optimal swap route, potentially using USDC as intermediary
|
||||
func (k Keeper) BuildSwapRoute(
|
||||
ctx sdk.Context,
|
||||
tokenInDenom string,
|
||||
tokenOutDenom string,
|
||||
connectionID string,
|
||||
) ([]types.TradingPair, error) {
|
||||
// Simple routing logic:
|
||||
// 1. If either token is USDC, direct swap
|
||||
// 2. Otherwise, route through USDC as intermediary
|
||||
|
||||
if tokenInDenom == types.NobleUSDCDenom || tokenOutDenom == types.NobleUSDCDenom {
|
||||
// Direct swap
|
||||
return []types.TradingPair{
|
||||
{
|
||||
Base: tokenInDenom,
|
||||
Quote: tokenOutDenom,
|
||||
Description: fmt.Sprintf("%s/%s direct", tokenInDenom, tokenOutDenom),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Route through USDC
|
||||
return []types.TradingPair{
|
||||
{
|
||||
Base: tokenInDenom,
|
||||
Quote: types.NobleUSDCDenom,
|
||||
Description: fmt.Sprintf("%s/USDC", tokenInDenom),
|
||||
},
|
||||
{
|
||||
Base: types.NobleUSDCDenom,
|
||||
Quote: tokenOutDenom,
|
||||
Description: fmt.Sprintf("USDC/%s", tokenOutDenom),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EstimateNobleSwapOutput estimates output for a Noble USDC swap
|
||||
func (k Keeper) EstimateNobleSwapOutput(
|
||||
ctx sdk.Context,
|
||||
tokenIn sdk.Coin,
|
||||
tokenOutDenom string,
|
||||
) (math.Int, error) {
|
||||
// In a full implementation, this would:
|
||||
// 1. Query Noble chain for current exchange rates
|
||||
// 2. Query any DEX pools for pricing
|
||||
// 3. Calculate expected output accounting for fees
|
||||
|
||||
// For now, use a simple 1% fee model
|
||||
estimatedOutput := tokenIn.Amount.MulRaw(99).QuoRaw(100)
|
||||
|
||||
return estimatedOutput, nil
|
||||
}
|
||||
|
||||
// CalculateSwapSlippage calculates the slippage percentage for a swap
|
||||
func (k Keeper) CalculateSwapSlippage(
|
||||
expectedOutput math.Int,
|
||||
minOutput math.Int,
|
||||
) math.LegacyDec {
|
||||
if expectedOutput.IsZero() {
|
||||
return math.LegacyZeroDec()
|
||||
}
|
||||
|
||||
slippage := math.LegacyNewDecFromInt(expectedOutput.Sub(minOutput)).Quo(math.LegacyNewDecFromInt(expectedOutput))
|
||||
return slippage.Mul(math.LegacyNewDec(100)) // Convert to percentage
|
||||
}
|
||||
|
||||
@@ -14,8 +14,4 @@ var (
|
||||
ErrInvalidLiquidityParams = sdkerrors.Register(ModuleName, 9, "invalid liquidity parameters")
|
||||
ErrInvalidOrderParams = sdkerrors.Register(ModuleName, 10, "invalid order parameters")
|
||||
ErrICAOperationFailed = sdkerrors.Register(ModuleName, 11, "ICA operation failed")
|
||||
ErrInvalidConnection = sdkerrors.Register(ModuleName, 12, "invalid IBC connection")
|
||||
ErrSwapFailed = sdkerrors.Register(ModuleName, 13, "swap operation failed")
|
||||
ErrLiquidityFailed = sdkerrors.Register(ModuleName, 14, "liquidity operation failed")
|
||||
ErrOrderFailed = sdkerrors.Register(ModuleName, 15, "order operation failed")
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
|
||||
// DefaultGenesisState returns the default module GenesisState.
|
||||
func DefaultGenesisState() *GenesisState {
|
||||
return &GenesisState{
|
||||
Params: DefaultParams(),
|
||||
PortId: PortID,
|
||||
}
|
||||
}
|
||||
@@ -13,7 +12,6 @@ func DefaultGenesisState() *GenesisState {
|
||||
// NewGenesisState initializes and returns a new GenesisState.
|
||||
func NewGenesisState() *GenesisState {
|
||||
return &GenesisState{
|
||||
Params: DefaultParams(),
|
||||
PortId: PortID,
|
||||
}
|
||||
}
|
||||
@@ -24,10 +22,5 @@ func (gs *GenesisState) Validate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate params
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Noble testnet chain configuration
|
||||
const (
|
||||
// NobleTestnetChainID is the chain ID for Noble testnet (grand-1)
|
||||
NobleTestnetChainID = "noble-grand-1"
|
||||
|
||||
// NobleMainnetChainID is the chain ID for Noble mainnet
|
||||
NobleMainnetChainID = "noble-1"
|
||||
|
||||
// NobleUSDCDenom is the denomination for USDC on Noble
|
||||
NobleUSDCDenom = "uusdc"
|
||||
|
||||
// NobleUSDCDecimals is the number of decimals for USDC (6 decimals like native USDC)
|
||||
NobleUSDCDecimals = 6
|
||||
)
|
||||
|
||||
// NobleChainConfig holds configuration for Noble blockchain integration
|
||||
type NobleChainConfig struct {
|
||||
ChainID string
|
||||
RPCEndpoint string
|
||||
GRPCEndpoint string
|
||||
USDCDenom string
|
||||
Decimals uint8
|
||||
}
|
||||
|
||||
// GetNobleTestnetConfig returns the configuration for Noble testnet
|
||||
func GetNobleTestnetConfig() NobleChainConfig {
|
||||
return NobleChainConfig{
|
||||
ChainID: NobleTestnetChainID,
|
||||
RPCEndpoint: "https://noble-testnet-rpc.polkachu.com:443",
|
||||
GRPCEndpoint: "noble-testnet-grpc.polkachu.com:21590",
|
||||
USDCDenom: NobleUSDCDenom,
|
||||
Decimals: NobleUSDCDecimals,
|
||||
}
|
||||
}
|
||||
|
||||
// GetNobleMainnetConfig returns the configuration for Noble mainnet
|
||||
func GetNobleMainnetConfig() NobleChainConfig {
|
||||
return NobleChainConfig{
|
||||
ChainID: NobleMainnetChainID,
|
||||
RPCEndpoint: "https://noble-rpc.polkachu.com:443",
|
||||
GRPCEndpoint: "noble-grpc.polkachu.com:21590",
|
||||
USDCDenom: NobleUSDCDenom,
|
||||
Decimals: NobleUSDCDecimals,
|
||||
}
|
||||
}
|
||||
|
||||
// IsNobleChain checks if a chain ID corresponds to Noble (testnet or mainnet)
|
||||
func IsNobleChain(chainID string) bool {
|
||||
return chainID == NobleTestnetChainID || chainID == NobleMainnetChainID
|
||||
}
|
||||
|
||||
// ValidateNobleConnection validates that a connection ID is properly configured for Noble
|
||||
func ValidateNobleConnection(connectionID string, allowedConnections []string) error {
|
||||
for _, allowed := range allowedConnections {
|
||||
if connectionID == allowed {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("connection %s not in allowed connections list", connectionID)
|
||||
}
|
||||
|
||||
// ConvertToUSDC converts an amount from base units to USDC representation
|
||||
// For example: 1000000 base units = 1.000000 USDC
|
||||
func ConvertToUSDC(amount math.Int) math.LegacyDec {
|
||||
// Convert to decimal and divide by 10^6
|
||||
return math.LegacyNewDecFromInt(amount).QuoInt64(1000000)
|
||||
}
|
||||
|
||||
// ConvertFromUSDC converts USDC amount to base units
|
||||
// For example: 1.5 USDC = 1500000 base units
|
||||
func ConvertFromUSDC(usdcAmount math.LegacyDec) math.Int {
|
||||
// Multiply by 10^6 and truncate to integer
|
||||
return usdcAmount.MulInt64(1000000).TruncateInt()
|
||||
}
|
||||
|
||||
// FormatUSDCAmount formats a USDC amount for display
|
||||
// For example: 1500000 -> "1.500000 USDC"
|
||||
func FormatUSDCAmount(amount math.Int) string {
|
||||
usdcDec := ConvertToUSDC(amount)
|
||||
return fmt.Sprintf("%s USDC", usdcDec.String())
|
||||
}
|
||||
|
||||
// ParseUSDCAmount parses a string USDC amount to base units
|
||||
// For example: "1.5" -> 1500000
|
||||
func ParseUSDCAmount(amountStr string) (math.Int, error) {
|
||||
usdcDec, err := math.LegacyNewDecFromStr(amountStr)
|
||||
if err != nil {
|
||||
return math.ZeroInt(), fmt.Errorf("invalid USDC amount: %w", err)
|
||||
}
|
||||
|
||||
if usdcDec.IsNegative() {
|
||||
return math.ZeroInt(), fmt.Errorf("USDC amount cannot be negative")
|
||||
}
|
||||
|
||||
return ConvertFromUSDC(usdcDec), nil
|
||||
}
|
||||
|
||||
// NobleSwapParams defines parameters for a swap on Noble or via Noble USDC
|
||||
type NobleSwapParams struct {
|
||||
// InputDenom is the denomination being swapped from
|
||||
InputDenom string
|
||||
// OutputDenom is the denomination being swapped to
|
||||
OutputDenom string
|
||||
// Amount is the input amount in base units
|
||||
Amount math.Int
|
||||
// MinOutput is the minimum output amount (slippage protection)
|
||||
MinOutput math.Int
|
||||
// Receiver is the address to receive the output tokens
|
||||
Receiver string
|
||||
}
|
||||
|
||||
// Validate performs basic validation on NobleSwapParams
|
||||
func (p NobleSwapParams) Validate() error {
|
||||
if p.InputDenom == "" {
|
||||
return fmt.Errorf("input denom cannot be empty")
|
||||
}
|
||||
if p.OutputDenom == "" {
|
||||
return fmt.Errorf("output denom cannot be empty")
|
||||
}
|
||||
if p.InputDenom == p.OutputDenom {
|
||||
return fmt.Errorf("input and output denoms must be different")
|
||||
}
|
||||
if !p.Amount.IsPositive() {
|
||||
return fmt.Errorf("amount must be positive")
|
||||
}
|
||||
if !p.MinOutput.IsPositive() {
|
||||
return fmt.Errorf("min output must be positive")
|
||||
}
|
||||
if p.Receiver == "" {
|
||||
return fmt.Errorf("receiver cannot be empty")
|
||||
}
|
||||
|
||||
// Validate receiver is a valid bech32 address
|
||||
_, err := sdk.AccAddressFromBech32(p.Receiver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid receiver address: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NobleLiquidityParams defines parameters for providing liquidity via Noble
|
||||
type NobleLiquidityParams struct {
|
||||
// PoolID is the target pool identifier
|
||||
PoolID string
|
||||
// Token0 is the first token denomination
|
||||
Token0 string
|
||||
// Token1 is the second token denomination
|
||||
Token1 string
|
||||
// Amount0 is the amount of first token
|
||||
Amount0 math.Int
|
||||
// Amount1 is the amount of second token
|
||||
Amount1 math.Int
|
||||
// MinShares is the minimum LP shares to receive
|
||||
MinShares math.Int
|
||||
}
|
||||
|
||||
// Validate performs basic validation on NobleLiquidityParams
|
||||
func (p NobleLiquidityParams) Validate() error {
|
||||
if p.PoolID == "" {
|
||||
return fmt.Errorf("pool ID cannot be empty")
|
||||
}
|
||||
if p.Token0 == "" {
|
||||
return fmt.Errorf("token0 cannot be empty")
|
||||
}
|
||||
if p.Token1 == "" {
|
||||
return fmt.Errorf("token1 cannot be empty")
|
||||
}
|
||||
if !p.Amount0.IsPositive() {
|
||||
return fmt.Errorf("amount0 must be positive")
|
||||
}
|
||||
if !p.Amount1.IsPositive() {
|
||||
return fmt.Errorf("amount1 must be positive")
|
||||
}
|
||||
if !p.MinShares.IsPositive() {
|
||||
return fmt.Errorf("min shares must be positive")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNobleUSDCPairs returns common trading pairs that include Noble USDC
|
||||
// This is useful for routing swaps through USDC as an intermediary
|
||||
func GetNobleUSDCPairs() []TradingPair {
|
||||
return []TradingPair{
|
||||
{Base: "uatom", Quote: NobleUSDCDenom, Description: "ATOM/USDC"},
|
||||
{Base: "uosmo", Quote: NobleUSDCDenom, Description: "OSMO/USDC"},
|
||||
{Base: "uakt", Quote: NobleUSDCDenom, Description: "AKT/USDC"},
|
||||
{Base: "ujuno", Quote: NobleUSDCDenom, Description: "JUNO/USDC"},
|
||||
{Base: "ustars", Quote: NobleUSDCDenom, Description: "STARS/USDC"},
|
||||
}
|
||||
}
|
||||
|
||||
// TradingPair represents a trading pair on a DEX
|
||||
type TradingPair struct {
|
||||
Base string
|
||||
Quote string
|
||||
Description string
|
||||
}
|
||||
|
||||
// String returns a string representation of the trading pair
|
||||
func (tp TradingPair) String() string {
|
||||
if tp.Description != "" {
|
||||
return tp.Description
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", tp.Base, tp.Quote)
|
||||
}
|
||||
|
||||
// Reverse returns the reverse trading pair (swaps base and quote)
|
||||
func (tp TradingPair) Reverse() TradingPair {
|
||||
return TradingPair{
|
||||
Base: tp.Quote,
|
||||
Quote: tp.Base,
|
||||
Description: fmt.Sprintf("%s/%s", tp.Quote, tp.Base),
|
||||
}
|
||||
}
|
||||
@@ -1,533 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetNobleTestnetConfig(t *testing.T) {
|
||||
config := GetNobleTestnetConfig()
|
||||
|
||||
require.Equal(t, NobleTestnetChainID, config.ChainID)
|
||||
require.Equal(t, "https://noble-testnet-rpc.polkachu.com:443", config.RPCEndpoint)
|
||||
require.Equal(t, "noble-testnet-grpc.polkachu.com:21590", config.GRPCEndpoint)
|
||||
require.Equal(t, NobleUSDCDenom, config.USDCDenom)
|
||||
require.Equal(t, uint8(6), config.Decimals)
|
||||
}
|
||||
|
||||
func TestGetNobleMainnetConfig(t *testing.T) {
|
||||
config := GetNobleMainnetConfig()
|
||||
|
||||
require.Equal(t, NobleMainnetChainID, config.ChainID)
|
||||
require.Equal(t, "https://noble-rpc.polkachu.com:443", config.RPCEndpoint)
|
||||
require.Equal(t, "noble-grpc.polkachu.com:21590", config.GRPCEndpoint)
|
||||
require.Equal(t, NobleUSDCDenom, config.USDCDenom)
|
||||
require.Equal(t, uint8(6), config.Decimals)
|
||||
}
|
||||
|
||||
func TestIsNobleChain(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
chainID string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "noble testnet",
|
||||
chainID: NobleTestnetChainID,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "noble mainnet",
|
||||
chainID: NobleMainnetChainID,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "osmosis",
|
||||
chainID: "osmosis-1",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "cosmos hub",
|
||||
chainID: "cosmoshub-4",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := IsNobleChain(tc.chainID)
|
||||
require.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateNobleConnection(t *testing.T) {
|
||||
allowedConnections := []string{"noble-grand-1", "osmo-test-5"}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
connectionID string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid noble connection",
|
||||
connectionID: "noble-grand-1",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid osmosis connection",
|
||||
connectionID: "osmo-test-5",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid connection",
|
||||
connectionID: "unknown-chain",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := ValidateNobleConnection(tc.connectionID, allowedConnections)
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToUSDC(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
amount sdk.Int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "1 USDC",
|
||||
amount: sdk.NewInt(1000000),
|
||||
expected: "1.000000000000000000",
|
||||
},
|
||||
{
|
||||
name: "1.5 USDC",
|
||||
amount: sdk.NewInt(1500000),
|
||||
expected: "1.500000000000000000",
|
||||
},
|
||||
{
|
||||
name: "0.1 USDC",
|
||||
amount: sdk.NewInt(100000),
|
||||
expected: "0.100000000000000000",
|
||||
},
|
||||
{
|
||||
name: "0 USDC",
|
||||
amount: sdk.ZeroInt(),
|
||||
expected: "0.000000000000000000",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := ConvertToUSDC(tc.amount)
|
||||
require.Equal(t, tc.expected, result.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertFromUSDC(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
usdc sdk.Dec
|
||||
expected int64
|
||||
}{
|
||||
{
|
||||
name: "1 USDC",
|
||||
usdc: sdk.MustNewDecFromStr("1.0"),
|
||||
expected: 1000000,
|
||||
},
|
||||
{
|
||||
name: "1.5 USDC",
|
||||
usdc: sdk.MustNewDecFromStr("1.5"),
|
||||
expected: 1500000,
|
||||
},
|
||||
{
|
||||
name: "0.1 USDC",
|
||||
usdc: sdk.MustNewDecFromStr("0.1"),
|
||||
expected: 100000,
|
||||
},
|
||||
{
|
||||
name: "0 USDC",
|
||||
usdc: sdk.ZeroDec(),
|
||||
expected: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := ConvertFromUSDC(tc.usdc)
|
||||
require.Equal(t, tc.expected, result.Int64())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatUSDCAmount(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
amount sdk.Int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "1 USDC",
|
||||
amount: sdk.NewInt(1000000),
|
||||
expected: "1.000000000000000000 USDC",
|
||||
},
|
||||
{
|
||||
name: "1.5 USDC",
|
||||
amount: sdk.NewInt(1500000),
|
||||
expected: "1.500000000000000000 USDC",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := FormatUSDCAmount(tc.amount)
|
||||
require.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseUSDCAmount(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected int64
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "1 USDC",
|
||||
input: "1.0",
|
||||
expected: 1000000,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "1.5 USDC",
|
||||
input: "1.5",
|
||||
expected: 1500000,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "0 USDC",
|
||||
input: "0",
|
||||
expected: 0,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid format",
|
||||
input: "abc",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "negative amount",
|
||||
input: "-1.0",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := ParseUSDCAmount(tc.input)
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, result.Int64())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNobleSwapParams_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
params NobleSwapParams
|
||||
expectErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid params",
|
||||
params: NobleSwapParams{
|
||||
InputDenom: "uatom",
|
||||
OutputDenom: "uusdc",
|
||||
Amount: sdk.NewInt(1000000),
|
||||
MinOutput: sdk.NewInt(500000),
|
||||
Receiver: "cosmos1abc123def456ghi789jkl012mno345pqr678st",
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty input denom",
|
||||
params: NobleSwapParams{
|
||||
OutputDenom: "uusdc",
|
||||
Amount: sdk.NewInt(1000000),
|
||||
MinOutput: sdk.NewInt(500000),
|
||||
Receiver: "cosmos1abc123def456ghi789jkl012mno345pqr678st",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "input denom cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "empty output denom",
|
||||
params: NobleSwapParams{
|
||||
InputDenom: "uatom",
|
||||
Amount: sdk.NewInt(1000000),
|
||||
MinOutput: sdk.NewInt(500000),
|
||||
Receiver: "cosmos1abc123def456ghi789jkl012mno345pqr678st",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "output denom cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "same input and output denom",
|
||||
params: NobleSwapParams{
|
||||
InputDenom: "uusdc",
|
||||
OutputDenom: "uusdc",
|
||||
Amount: sdk.NewInt(1000000),
|
||||
MinOutput: sdk.NewInt(500000),
|
||||
Receiver: "cosmos1abc123def456ghi789jkl012mno345pqr678st",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "input and output denoms must be different",
|
||||
},
|
||||
{
|
||||
name: "zero amount",
|
||||
params: NobleSwapParams{
|
||||
InputDenom: "uatom",
|
||||
OutputDenom: "uusdc",
|
||||
Amount: sdk.ZeroInt(),
|
||||
MinOutput: sdk.NewInt(500000),
|
||||
Receiver: "cosmos1abc123def456ghi789jkl012mno345pqr678st",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "amount must be positive",
|
||||
},
|
||||
{
|
||||
name: "zero min output",
|
||||
params: NobleSwapParams{
|
||||
InputDenom: "uatom",
|
||||
OutputDenom: "uusdc",
|
||||
Amount: sdk.NewInt(1000000),
|
||||
MinOutput: sdk.ZeroInt(),
|
||||
Receiver: "cosmos1abc123def456ghi789jkl012mno345pqr678st",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "min output must be positive",
|
||||
},
|
||||
{
|
||||
name: "empty receiver",
|
||||
params: NobleSwapParams{
|
||||
InputDenom: "uatom",
|
||||
OutputDenom: "uusdc",
|
||||
Amount: sdk.NewInt(1000000),
|
||||
MinOutput: sdk.NewInt(500000),
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "receiver cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "invalid receiver address",
|
||||
params: NobleSwapParams{
|
||||
InputDenom: "uatom",
|
||||
OutputDenom: "uusdc",
|
||||
Amount: sdk.NewInt(1000000),
|
||||
MinOutput: sdk.NewInt(500000),
|
||||
Receiver: "invalid",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "invalid receiver address",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.params.Validate()
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.errMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNobleLiquidityParams_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
params NobleLiquidityParams
|
||||
expectErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid params",
|
||||
params: NobleLiquidityParams{
|
||||
PoolID: "1",
|
||||
Token0: "uusdc",
|
||||
Token1: "uatom",
|
||||
Amount0: sdk.NewInt(1000000),
|
||||
Amount1: sdk.NewInt(500000),
|
||||
MinShares: sdk.NewInt(1),
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty pool ID",
|
||||
params: NobleLiquidityParams{
|
||||
Token0: "uusdc",
|
||||
Token1: "uatom",
|
||||
Amount0: sdk.NewInt(1000000),
|
||||
Amount1: sdk.NewInt(500000),
|
||||
MinShares: sdk.NewInt(1),
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "pool ID cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "empty token0",
|
||||
params: NobleLiquidityParams{
|
||||
PoolID: "1",
|
||||
Token1: "uatom",
|
||||
Amount0: sdk.NewInt(1000000),
|
||||
Amount1: sdk.NewInt(500000),
|
||||
MinShares: sdk.NewInt(1),
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "token0 cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "empty token1",
|
||||
params: NobleLiquidityParams{
|
||||
PoolID: "1",
|
||||
Token0: "uusdc",
|
||||
Amount0: sdk.NewInt(1000000),
|
||||
Amount1: sdk.NewInt(500000),
|
||||
MinShares: sdk.NewInt(1),
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "token1 cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "zero amount0",
|
||||
params: NobleLiquidityParams{
|
||||
PoolID: "1",
|
||||
Token0: "uusdc",
|
||||
Token1: "uatom",
|
||||
Amount0: sdk.ZeroInt(),
|
||||
Amount1: sdk.NewInt(500000),
|
||||
MinShares: sdk.NewInt(1),
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "amount0 must be positive",
|
||||
},
|
||||
{
|
||||
name: "zero amount1",
|
||||
params: NobleLiquidityParams{
|
||||
PoolID: "1",
|
||||
Token0: "uusdc",
|
||||
Token1: "uatom",
|
||||
Amount0: sdk.NewInt(1000000),
|
||||
Amount1: sdk.ZeroInt(),
|
||||
MinShares: sdk.NewInt(1),
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "amount1 must be positive",
|
||||
},
|
||||
{
|
||||
name: "zero min shares",
|
||||
params: NobleLiquidityParams{
|
||||
PoolID: "1",
|
||||
Token0: "uusdc",
|
||||
Token1: "uatom",
|
||||
Amount0: sdk.NewInt(1000000),
|
||||
Amount1: sdk.NewInt(500000),
|
||||
MinShares: sdk.ZeroInt(),
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "min shares must be positive",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.params.Validate()
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.errMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNobleUSDCPairs(t *testing.T) {
|
||||
pairs := GetNobleUSDCPairs()
|
||||
|
||||
require.NotEmpty(t, pairs)
|
||||
require.GreaterOrEqual(t, len(pairs), 5)
|
||||
|
||||
// Check that all pairs have USDC as quote
|
||||
for _, pair := range pairs {
|
||||
require.Equal(t, NobleUSDCDenom, pair.Quote)
|
||||
require.NotEmpty(t, pair.Base)
|
||||
require.NotEmpty(t, pair.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTradingPair_String(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
pair TradingPair
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "with description",
|
||||
pair: TradingPair{
|
||||
Base: "uatom",
|
||||
Quote: "uusdc",
|
||||
Description: "ATOM/USDC",
|
||||
},
|
||||
expected: "ATOM/USDC",
|
||||
},
|
||||
{
|
||||
name: "without description",
|
||||
pair: TradingPair{
|
||||
Base: "uatom",
|
||||
Quote: "uusdc",
|
||||
},
|
||||
expected: "uatom/uusdc",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := tc.pair.String()
|
||||
require.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTradingPair_Reverse(t *testing.T) {
|
||||
pair := TradingPair{
|
||||
Base: "uatom",
|
||||
Quote: "uusdc",
|
||||
Description: "ATOM/USDC",
|
||||
}
|
||||
|
||||
reversed := pair.Reverse()
|
||||
|
||||
require.Equal(t, "uusdc", reversed.Base)
|
||||
require.Equal(t, "uatom", reversed.Quote)
|
||||
require.Equal(t, "uusdc/uatom", reversed.Description)
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
)
|
||||
|
||||
// DefaultParams returns default module parameters.
|
||||
func DefaultParams() Params {
|
||||
return Params{
|
||||
Enabled: true,
|
||||
MaxAccountsPerDid: 5, // Maximum 5 ICA accounts per DID
|
||||
DefaultTimeoutSeconds: 600, // 10 minutes timeout for ICA operations
|
||||
AllowedConnections: []string{
|
||||
// Noble testnet connection - USDC hub for Cosmos
|
||||
"noble-grand-1",
|
||||
// Osmosis testnet - Primary DEX
|
||||
"osmo-test-5",
|
||||
// Other testnets can be added here
|
||||
},
|
||||
MinSwapAmount: "1000", // Minimum 1000 base units
|
||||
MaxDailyVolume: "1000000000000", // 1M units daily volume cap
|
||||
RateLimits: RateLimitParams{
|
||||
MaxOpsPerBlock: 10, // Maximum 10 operations per block
|
||||
MaxOpsPerDidPerDay: 100, // Maximum 100 operations per DID per day
|
||||
CooldownBlocks: 5, // 5 block cooldown between operations
|
||||
},
|
||||
Fees: FeeParams{
|
||||
SwapFeeBps: 30, // 0.3% swap fee
|
||||
LiquidityFeeBps: 20, // 0.2% liquidity fee
|
||||
OrderFeeBps: 10, // 0.1% order fee
|
||||
FeeCollector: "", // Empty means use module account
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs basic validation of module parameters.
|
||||
func (p Params) Validate() error {
|
||||
if p.MaxAccountsPerDid == 0 {
|
||||
return fmt.Errorf("max_accounts_per_did must be positive")
|
||||
}
|
||||
|
||||
if p.MaxAccountsPerDid > 100 {
|
||||
return fmt.Errorf("max_accounts_per_did cannot exceed 100")
|
||||
}
|
||||
|
||||
if p.DefaultTimeoutSeconds == 0 {
|
||||
return fmt.Errorf("default_timeout_seconds must be positive")
|
||||
}
|
||||
|
||||
if p.DefaultTimeoutSeconds > 3600 {
|
||||
return fmt.Errorf("default_timeout_seconds cannot exceed 3600 (1 hour)")
|
||||
}
|
||||
|
||||
// Validate swap amounts
|
||||
if p.MinSwapAmount != "" {
|
||||
minSwap, ok := math.NewIntFromString(p.MinSwapAmount)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid min_swap_amount: %s", p.MinSwapAmount)
|
||||
}
|
||||
if minSwap.IsNegative() {
|
||||
return fmt.Errorf("min_swap_amount cannot be negative")
|
||||
}
|
||||
}
|
||||
|
||||
if p.MaxDailyVolume != "" {
|
||||
maxVolume, ok := math.NewIntFromString(p.MaxDailyVolume)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid max_daily_volume: %s", p.MaxDailyVolume)
|
||||
}
|
||||
if maxVolume.IsNegative() {
|
||||
return fmt.Errorf("max_daily_volume cannot be negative")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate rate limits
|
||||
if err := p.RateLimits.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid rate_limits: %w", err)
|
||||
}
|
||||
|
||||
// Validate fees
|
||||
if err := p.Fees.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid fees: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs basic validation of rate limit parameters.
|
||||
func (r RateLimitParams) Validate() error {
|
||||
if r.MaxOpsPerBlock == 0 {
|
||||
return fmt.Errorf("max_ops_per_block must be positive")
|
||||
}
|
||||
|
||||
if r.MaxOpsPerBlock > 1000 {
|
||||
return fmt.Errorf("max_ops_per_block cannot exceed 1000")
|
||||
}
|
||||
|
||||
if r.MaxOpsPerDidPerDay == 0 {
|
||||
return fmt.Errorf("max_ops_per_did_per_day must be positive")
|
||||
}
|
||||
|
||||
// Cooldown blocks can be 0 (no cooldown)
|
||||
if r.CooldownBlocks > 1000 {
|
||||
return fmt.Errorf("cooldown_blocks cannot exceed 1000")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs basic validation of fee parameters.
|
||||
func (f FeeParams) Validate() error {
|
||||
// Basis points validation (1 bps = 0.01%)
|
||||
maxBps := uint32(10000) // 100%
|
||||
|
||||
if f.SwapFeeBps > maxBps {
|
||||
return fmt.Errorf("swap_fee_bps cannot exceed %d (100%%)", maxBps)
|
||||
}
|
||||
|
||||
if f.LiquidityFeeBps > maxBps {
|
||||
return fmt.Errorf("liquidity_fee_bps cannot exceed %d (100%%)", maxBps)
|
||||
}
|
||||
|
||||
if f.OrderFeeBps > maxBps {
|
||||
return fmt.Errorf("order_fee_bps cannot exceed %d (100%%)", maxBps)
|
||||
}
|
||||
|
||||
// Fee collector address validation is done by the SDK when set
|
||||
// Empty string means use module account
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDefaultParams(t *testing.T) {
|
||||
params := DefaultParams()
|
||||
|
||||
// Verify basic fields
|
||||
require.True(t, params.Enabled)
|
||||
require.Equal(t, uint32(5), params.MaxAccountsPerDid)
|
||||
require.Equal(t, uint64(600), params.DefaultTimeoutSeconds)
|
||||
|
||||
// Verify Noble testnet is in allowed connections
|
||||
require.Contains(t, params.AllowedConnections, "noble-grand-1")
|
||||
|
||||
// Verify amounts
|
||||
require.Equal(t, "1000", params.MinSwapAmount)
|
||||
require.Equal(t, "1000000000000", params.MaxDailyVolume)
|
||||
|
||||
// Verify rate limits
|
||||
require.Equal(t, uint32(10), params.RateLimits.MaxOpsPerBlock)
|
||||
require.Equal(t, uint32(100), params.RateLimits.MaxOpsPerDidPerDay)
|
||||
require.Equal(t, uint32(5), params.RateLimits.CooldownBlocks)
|
||||
|
||||
// Verify fees
|
||||
require.Equal(t, uint32(30), params.Fees.SwapFeeBps)
|
||||
require.Equal(t, uint32(20), params.Fees.LiquidityFeeBps)
|
||||
require.Equal(t, uint32(10), params.Fees.OrderFeeBps)
|
||||
|
||||
// Validate params
|
||||
err := params.Validate()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestParams_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
params Params
|
||||
expectErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid default params",
|
||||
params: DefaultParams(),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - zero max accounts",
|
||||
params: Params{
|
||||
MaxAccountsPerDid: 0,
|
||||
DefaultTimeoutSeconds: 600,
|
||||
MinSwapAmount: "1000",
|
||||
MaxDailyVolume: "1000000",
|
||||
RateLimits: DefaultParams().RateLimits,
|
||||
Fees: DefaultParams().Fees,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "max_accounts_per_did must be positive",
|
||||
},
|
||||
{
|
||||
name: "invalid - max accounts too high",
|
||||
params: Params{
|
||||
MaxAccountsPerDid: 101,
|
||||
DefaultTimeoutSeconds: 600,
|
||||
MinSwapAmount: "1000",
|
||||
MaxDailyVolume: "1000000",
|
||||
RateLimits: DefaultParams().RateLimits,
|
||||
Fees: DefaultParams().Fees,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "max_accounts_per_did cannot exceed 100",
|
||||
},
|
||||
{
|
||||
name: "invalid - zero timeout",
|
||||
params: Params{
|
||||
MaxAccountsPerDid: 5,
|
||||
DefaultTimeoutSeconds: 0,
|
||||
MinSwapAmount: "1000",
|
||||
MaxDailyVolume: "1000000",
|
||||
RateLimits: DefaultParams().RateLimits,
|
||||
Fees: DefaultParams().Fees,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "default_timeout_seconds must be positive",
|
||||
},
|
||||
{
|
||||
name: "invalid - timeout too high",
|
||||
params: Params{
|
||||
MaxAccountsPerDid: 5,
|
||||
DefaultTimeoutSeconds: 3601,
|
||||
MinSwapAmount: "1000",
|
||||
MaxDailyVolume: "1000000",
|
||||
RateLimits: DefaultParams().RateLimits,
|
||||
Fees: DefaultParams().Fees,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "default_timeout_seconds cannot exceed 3600",
|
||||
},
|
||||
{
|
||||
name: "invalid - negative min swap amount",
|
||||
params: Params{
|
||||
MaxAccountsPerDid: 5,
|
||||
DefaultTimeoutSeconds: 600,
|
||||
MinSwapAmount: "-1000",
|
||||
MaxDailyVolume: "1000000",
|
||||
RateLimits: DefaultParams().RateLimits,
|
||||
Fees: DefaultParams().Fees,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "min_swap_amount cannot be negative",
|
||||
},
|
||||
{
|
||||
name: "invalid - invalid min swap amount format",
|
||||
params: Params{
|
||||
MaxAccountsPerDid: 5,
|
||||
DefaultTimeoutSeconds: 600,
|
||||
MinSwapAmount: "invalid",
|
||||
MaxDailyVolume: "1000000",
|
||||
RateLimits: DefaultParams().RateLimits,
|
||||
Fees: DefaultParams().Fees,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "invalid min_swap_amount",
|
||||
},
|
||||
{
|
||||
name: "invalid - negative max daily volume",
|
||||
params: Params{
|
||||
MaxAccountsPerDid: 5,
|
||||
DefaultTimeoutSeconds: 600,
|
||||
MinSwapAmount: "1000",
|
||||
MaxDailyVolume: "-1000000",
|
||||
RateLimits: DefaultParams().RateLimits,
|
||||
Fees: DefaultParams().Fees,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "max_daily_volume cannot be negative",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.params.Validate()
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.errMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimitParams_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
params RateLimitParams
|
||||
expectErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid default",
|
||||
params: DefaultParams().RateLimits,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - zero max ops per block",
|
||||
params: RateLimitParams{
|
||||
MaxOpsPerBlock: 0,
|
||||
MaxOpsPerDidPerDay: 100,
|
||||
CooldownBlocks: 5,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "max_ops_per_block must be positive",
|
||||
},
|
||||
{
|
||||
name: "invalid - max ops per block too high",
|
||||
params: RateLimitParams{
|
||||
MaxOpsPerBlock: 1001,
|
||||
MaxOpsPerDidPerDay: 100,
|
||||
CooldownBlocks: 5,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "max_ops_per_block cannot exceed 1000",
|
||||
},
|
||||
{
|
||||
name: "invalid - zero max ops per did per day",
|
||||
params: RateLimitParams{
|
||||
MaxOpsPerBlock: 10,
|
||||
MaxOpsPerDidPerDay: 0,
|
||||
CooldownBlocks: 5,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "max_ops_per_did_per_day must be positive",
|
||||
},
|
||||
{
|
||||
name: "valid - zero cooldown blocks",
|
||||
params: RateLimitParams{
|
||||
MaxOpsPerBlock: 10,
|
||||
MaxOpsPerDidPerDay: 100,
|
||||
CooldownBlocks: 0,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - cooldown blocks too high",
|
||||
params: RateLimitParams{
|
||||
MaxOpsPerBlock: 10,
|
||||
MaxOpsPerDidPerDay: 100,
|
||||
CooldownBlocks: 1001,
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "cooldown_blocks cannot exceed 1000",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.params.Validate()
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.errMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeeParams_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
params FeeParams
|
||||
expectErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid default",
|
||||
params: DefaultParams().Fees,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid - zero fees",
|
||||
params: FeeParams{
|
||||
SwapFeeBps: 0,
|
||||
LiquidityFeeBps: 0,
|
||||
OrderFeeBps: 0,
|
||||
FeeCollector: "",
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "valid - max fees",
|
||||
params: FeeParams{
|
||||
SwapFeeBps: 10000,
|
||||
LiquidityFeeBps: 10000,
|
||||
OrderFeeBps: 10000,
|
||||
FeeCollector: "",
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid - swap fee too high",
|
||||
params: FeeParams{
|
||||
SwapFeeBps: 10001,
|
||||
LiquidityFeeBps: 20,
|
||||
OrderFeeBps: 10,
|
||||
FeeCollector: "",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "swap_fee_bps cannot exceed",
|
||||
},
|
||||
{
|
||||
name: "invalid - liquidity fee too high",
|
||||
params: FeeParams{
|
||||
SwapFeeBps: 30,
|
||||
LiquidityFeeBps: 10001,
|
||||
OrderFeeBps: 10,
|
||||
FeeCollector: "",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "liquidity_fee_bps cannot exceed",
|
||||
},
|
||||
{
|
||||
name: "invalid - order fee too high",
|
||||
params: FeeParams{
|
||||
SwapFeeBps: 30,
|
||||
LiquidityFeeBps: 20,
|
||||
OrderFeeBps: 10001,
|
||||
FeeCollector: "",
|
||||
},
|
||||
expectErr: true,
|
||||
errMsg: "order_fee_bps cannot exceed",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.params.Validate()
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tc.errMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user