diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f2ae8fc8..784ae1715 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2140](https://github.com/NibiruChain/nibiru/pull/2140) - fix(bank): bank keeper extension now charges gas for the bank operations - [#2141](https://github.com/NibiruChain/nibiru/pull/2141) - refactor: simplify account retrieval operation in `nibid q evm account`. - [#2142](https://github.com/NibiruChain/nibiru/pull/2142) - fix(bank): add additional missing methods to the NibiruBankKeeper +- [#2144](https://github.com/NibiruChain/nibiru/pull/2144) - feat(token-registry): Implement strongly typed Nibiru Token Registry and generation command #### Nibiru EVM | Before Audit 2 - 2024-12-06 diff --git a/justfile b/justfile index 77b6e1aaf..175e1c5e9 100644 --- a/justfile +++ b/justfile @@ -41,6 +41,10 @@ gen-embeds: alias gen-proto := proto-gen +# Generate the Nibiru Token Registry files +gen-token-registry: + go run token-registry/main/main.go + # Generate protobuf-based types in Rust gen-proto-rs: bash proto/buf.gen.rs.sh diff --git a/token-registry/README.md b/token-registry/README.md new file mode 100644 index 000000000..68f97316c --- /dev/null +++ b/token-registry/README.md @@ -0,0 +1,5 @@ +# Nibiru/token-registry + +This directory implements the Nibiru Token Registry by providing a means to +register offchain digital token metadata to onchain identifiers for use with +applications like wallets. diff --git a/token-registry/assetlist.go b/token-registry/assetlist.go new file mode 100644 index 000000000..8c35ae847 --- /dev/null +++ b/token-registry/assetlist.go @@ -0,0 +1,199 @@ +package tokenregistry + +func NibiruAssetList() AssetList { + var tokens = TOKENS() + for idx, token := range tokens { + tokens[idx] = token.GitHubify() + } + + return AssetList{ + Schema: "../assetlist.schema.json", + ChainName: "nibiru", + Assets: tokens, + } +} + +func TOKENS() []Token { + return []Token{ + { + Description: "The native token of Nibiru blockchain", + ExtendedDescription: some("Nibiru is a smart contract ecosystem with a high-performance, EVM-equivalent execution layer. Nibiru is engineered to meet the growing demand for versatile, scalable, and easy-to-use Web3 applications."), + Socials: &SocialLinks{ + Website: some("https://nibiru.fi"), + Twitter: some("https://twitter.com/nibiruchain"), + }, + DenomUnits: []DenomUnit{ + {Denom: "unibi", Exponent: 0}, + {Denom: "nibi", Exponent: 6}, + {Denom: "attonibi", Exponent: 18}, + }, + Base: "unibi", + Name: "Nibiru", + Display: "nibi", + Symbol: "NIBI", + LogoURIs: &LogoURIs{ + Png: some("./img/0000_nibiru.png"), + Svg: some("./img/0000_nibiru.svg"), + }, + CoingeckoID: some("nibiru"), + Images: []AssetImage{ + { + Png: some("./img/0000_nibiru.png"), + Svg: some("./img/0000_nibiru.svg"), + Theme: &ImageTheme{ + PrimaryColorHex: some("#14c0ce"), + }, + }, + }, + TypeAsset: TypeAsset_SDKCoin, + }, + { + Description: "Liquid Staked Nibiru (Eris)", + ExtendedDescription: some("Liquid Staked Nibiru, powered by Eris Protocol's amplifier contracts. Nibiru is a smart contract ecosystem with a high-performance, EVM-equivalent execution layer. Nibiru is engineered to meet the growing demand for versatile, scalable, and easy-to-use Web3 applications."), + Socials: &SocialLinks{ + Website: some("https://nibiru.fi/docs/learn/liquid-stake/"), + Twitter: some("https://x.com/eris_protocol"), + }, + DenomUnits: []DenomUnit{ + {Denom: "tf/nibi1udqqx30cw8nwjxtl4l28ym9hhrp933zlq8dqxfjzcdhvl8y24zcqpzmh8m/ampNIBI", Exponent: 0}, + {Denom: "stNIBI", Exponent: 6}, + }, + Base: "tf/nibi1udqqx30cw8nwjxtl4l28ym9hhrp933zlq8dqxfjzcdhvl8y24zcqpzmh8m/ampNIBI", + Name: "Liquid Staked Nibiru (Eris)", + Display: "stNIBI", + Symbol: "stNIBI", + LogoURIs: &LogoURIs{ + Png: some("./img/0001_stnibi-500x500.png"), + Svg: some("./img/0001_stnibi-500x500.svg"), + }, + Images: []AssetImage{ + { + Png: some("./img/0001_stnibi-500x500.png"), + Svg: some("./img/0001_stnibi-500x500.svg"), + Theme: &ImageTheme{ + PrimaryColorHex: some("#14c0ce"), + }, + }, + }, + Traces: []Trace{ + { + Type: "liquid-stake", + Counterparty: Counterparty{ + ChainName: "nibiru", + BaseDenom: "unibi", + }, + Provider: some("Eris Protocol"), + }, + }, + TypeAsset: TypeAsset_SDKCoin, + }, + { + Description: "Noble USDC on Nibiru", + DenomUnits: []DenomUnit{ + {Denom: "ibc/F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349", Exponent: 0}, + {Denom: "usdc", Exponent: 6}, + }, + Base: "ibc/F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349", + Name: "Noble USDC", + Display: "usdc", + Symbol: "USDC", + Traces: []Trace{ + { + Type: TraceType_IBC, + Counterparty: Counterparty{ + ChainName: "noble", + BaseDenom: "uusdc", + ChannelID: some("channel-67"), + }, + Chain: &TraceChainInfo{ + ChannelID: "channel-2", + Path: "transfer/channel-2/uusdc", + }, + }, + }, + Images: []AssetImage{ + { + ImageSync: &ImageSync{ + ChainName: "noble", + BaseDenom: "uusdc", + }, + Png: some("./img/0002_usdc.png"), + Svg: some("./img/0002_usdc.svg"), + Theme: &ImageTheme{ + Circle: some(true), + PrimaryColorHex: some("#2775CA"), + }, + }, + }, + LogoURIs: &LogoURIs{ + Png: some("./img/0002_usdc.png"), + Svg: some("./img/0002_usdc.svg"), + }, + TypeAsset: TypeAsset_ICS20, + }, + { + Description: "AXV", + ExtendedDescription: some("AXV is the Astrovault token."), + Socials: &SocialLinks{ + Website: some("https://astrovault.io/"), + Twitter: some("https://x.com/axvdex"), + }, + DenomUnits: []DenomUnit{ + {Denom: "tf/nibi1vetfuua65frvf6f458xgtjerf0ra7wwjykrdpuyn0jur5x07awxsfka0ga/axv", Exponent: 0}, + {Denom: "AXV", Exponent: 6}, + }, + Base: "tf/nibi1vetfuua65frvf6f458xgtjerf0ra7wwjykrdpuyn0jur5x07awxsfka0ga/axv", + Name: "AXV", + Display: "AXV", + Symbol: "AXV", + LogoURIs: &LogoURIs{ + Png: some("./img/0003_astrovault-axv.png"), + Svg: some("./img/0003_astrovault-axv.svg"), + }, + Images: []AssetImage{ + { + ImageSync: &ImageSync{ + ChainName: "neutron", + BaseDenom: "cw20:neutron10dxyft3nv4vpxh5vrpn0xw8geej8dw3g39g7nqp8mrm307ypssksau29af", + }, + Png: some("./img/0003_astrovault-axv.png"), + Svg: some("./img/0003_astrovault-axv.svg"), + }, + }, + TypeAsset: TypeAsset_SDKCoin, + }, + { + Description: "uoprek", + DenomUnits: []DenomUnit{ + {Denom: "tf/nibi149m52kn7nvsg5nftvv4fh85scsavpdfxp5nr7zasz97dum89dp5qkyhy0t/uoprek", Exponent: 0}, + }, + Base: "tf/nibi149m52kn7nvsg5nftvv4fh85scsavpdfxp5nr7zasz97dum89dp5qkyhy0t/uoprek", + Name: "uoprek", + Display: "tf/nibi149m52kn7nvsg5nftvv4fh85scsavpdfxp5nr7zasz97dum89dp5qkyhy0t/uoprek", + Symbol: "UOPREK", + TypeAsset: TypeAsset_SDKCoin, + }, + { + Description: "utestate", + DenomUnits: []DenomUnit{ + {Denom: "tf/nibi1lp28kx3gz0prsztl024z730ufkg3alahaq3e7a6gae22nk0dqdvsyrrgqw/utestate", Exponent: 0}, + }, + Base: "tf/nibi1lp28kx3gz0prsztl024z730ufkg3alahaq3e7a6gae22nk0dqdvsyrrgqw/utestate", + Name: "utestate", + Display: "tf/nibi1lp28kx3gz0prsztl024z730ufkg3alahaq3e7a6gae22nk0dqdvsyrrgqw/utestate", + Symbol: "UTESTATE", + TypeAsset: TypeAsset_SDKCoin, + }, + { + Description: "npp", + DenomUnits: []DenomUnit{ + {Denom: "tf/nibi1xpp7yn0tce62ffattws3gpd6v0tah0mlevef3ej3r4pnfvsehcgqk3jvxq/NPP", Exponent: 0}, + }, + Base: "tf/nibi1xpp7yn0tce62ffattws3gpd6v0tah0mlevef3ej3r4pnfvsehcgqk3jvxq/NPP", + Name: "npp", + Display: "tf/nibi1xpp7yn0tce62ffattws3gpd6v0tah0mlevef3ej3r4pnfvsehcgqk3jvxq/NPP", + Symbol: "NPP", + TypeAsset: TypeAsset_SDKCoin, + }, + } +} diff --git a/token-registry/assetlist_test.go b/token-registry/assetlist_test.go new file mode 100644 index 000000000..06fcfb1c1 --- /dev/null +++ b/token-registry/assetlist_test.go @@ -0,0 +1,182 @@ +package tokenregistry_test + +import ( + "os" + "strings" + "testing" + + "github.com/stretchr/testify/suite" + + tokenregistry "github.com/NibiruChain/nibiru/v2/token-registry" +) + +type Suite struct { + suite.Suite +} + +func TestSuite(t *testing.T) { + suite.Run(t, new(Suite)) +} + +func (s *Suite) TestImagesPresent() { + assetList := tokenregistry.NibiruAssetList() + for _, token := range assetList.Assets { + s.Run(token.Name, func() { + token = token.GitHubify() + + // Make sure all local images in Token.LogoURIs exist + if token.LogoURIs != nil { + png, svg := token.LogoURIs.Png, token.LogoURIs.Svg + if png != nil && strings.Contains(*png, "token-registry/img/") { + localImgPath := "./img/" + strings.Split(*png, "/img/")[1] + _, err := os.Stat(localImgPath) + s.NoError(err) + } + if svg != nil && strings.Contains(*svg, "token-registry/img/") { + localImgPath := "./img/" + strings.Split(*svg, "/img/")[1] + _, err := os.Stat(localImgPath) + s.NoError(err) + } + } + + // Make sure all local images in Token.Images exist + for _, img := range token.Images { + png, svg := img.Png, img.Svg + if png != nil && strings.Contains(*png, "token-registry/img/") { + localImgPath := "./img/" + strings.Split(*png, "/img/")[1] + _, err := os.Stat(localImgPath) + s.NoError(err) + } + if svg != nil && strings.Contains(*svg, "token-registry/img/") { + localImgPath := "./img/" + strings.Split(*svg, "/img/")[1] + _, err := os.Stat(localImgPath) + s.NoError(err) + } + } + }) + } +} + +func some(s string) *string { + return &s +} + +func (s *Suite) TestLogoURIsGitHubify() { + tests := []struct { + name string + input tokenregistry.LogoURIs + expected tokenregistry.LogoURIs + }{ + { + name: "Png and Svg are local paths", + input: tokenregistry.LogoURIs{ + Png: some("./img/logo.png"), + Svg: some("./img/logo.svg"), + }, + expected: tokenregistry.LogoURIs{ + Png: some("https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/logo.png"), + Svg: some("https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/logo.svg"), + }, + }, + { + name: "Png is local, Svg is external", + input: tokenregistry.LogoURIs{ + Png: some("./img/logo.png"), + Svg: some("https://example.com/logo.svg"), + }, + expected: tokenregistry.LogoURIs{ + Png: some("https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/logo.png"), + Svg: some("https://example.com/logo.svg"), + }, + }, + { + name: "Both Png and Svg are external URLs", + input: tokenregistry.LogoURIs{ + Png: some("https://example.com/logo.png"), + Svg: some("https://example.com/logo.svg"), + }, + expected: tokenregistry.LogoURIs{ + Png: some("https://example.com/logo.png"), + Svg: some("https://example.com/logo.svg"), + }, + }, + { + name: "Both Png and Svg are nil", + input: tokenregistry.LogoURIs{ + Png: nil, + Svg: nil, + }, + expected: tokenregistry.LogoURIs{ + Png: nil, + Svg: nil, + }, + }, + } + + for _, tt := range tests { + s.Run(tt.name, func() { + result := tt.input.GitHubify() + s.Equal(tt.expected, *result) + }) + } +} + +func (s *Suite) TestAssetImageGitHubify() { + tests := []struct { + name string + input tokenregistry.AssetImage + expected tokenregistry.AssetImage + }{ + { + name: "Png and Svg are local paths", + input: tokenregistry.AssetImage{ + Png: some("./img/asset.png"), + Svg: some("./img/asset.svg"), + }, + expected: tokenregistry.AssetImage{ + Png: some("https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/asset.png"), + Svg: some("https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/asset.svg"), + }, + }, + { + name: "Png is local, Svg is external", + input: tokenregistry.AssetImage{ + Png: some("./img/asset.png"), + Svg: some("https://example.com/asset.svg"), + }, + expected: tokenregistry.AssetImage{ + Png: some("https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/asset.png"), + Svg: some("https://example.com/asset.svg"), + }, + }, + { + name: "Both Png and Svg are external URLs", + input: tokenregistry.AssetImage{ + Png: some("https://example.com/asset.png"), + Svg: some("https://example.com/asset.svg"), + }, + expected: tokenregistry.AssetImage{ + Png: some("https://example.com/asset.png"), + Svg: some("https://example.com/asset.svg"), + }, + }, + { + name: "Both Png and Svg are nil", + input: tokenregistry.AssetImage{ + Png: nil, + Svg: nil, + }, + expected: tokenregistry.AssetImage{ + Png: nil, + Svg: nil, + }, + }, + } + + for _, tt := range tests { + s.Run(tt.name, func() { + result := tt.input.GitHubify() + s.Equal(tt.expected, result) + }) + } +} diff --git a/token-registry/githubify.go b/token-registry/githubify.go new file mode 100644 index 000000000..7bedf6a3d --- /dev/null +++ b/token-registry/githubify.go @@ -0,0 +1,63 @@ +package tokenregistry + +import "strings" + +func (t Token) GitHubify() Token { + out := t + + if out.LogoURIs != nil { + out.LogoURIs = out.LogoURIs.GitHubify() + } + + for imgIdx, img := range out.Images { + out.Images[imgIdx] = img.GitHubify() + } + + return out +} + +// localImageToGitHub converts a path to a local image into a GitHub download +// link in the NibiruChain/nibiru repository. +func localImageToGitHub(local string) string { + trimmed := strings.TrimPrefix(local, "./img/") + return "https://raw.githubusercontent.com/NibiruChain/nibiru/main/token-registry/img/" + trimmed +} + +// isLocalImage returns true if an image URI is meant for a local file in +// the "token-registry/img" directory. +func isLocalImage(maybeLocal *string) bool { + if maybeLocal == nil { + return false + } + return strings.HasPrefix(*maybeLocal, "./img") +} + +func (logouris LogoURIs) GitHubify() *LogoURIs { + out := new(LogoURIs) + out.Png, out.Svg = logouris.Png, logouris.Svg + if logouris.Png != nil && isLocalImage(logouris.Png) { + out.Png = some(localImageToGitHub(*logouris.Png)) + } + if logouris.Svg != nil && isLocalImage(logouris.Svg) { + out.Svg = some(localImageToGitHub(*logouris.Svg)) + } + return out +} + +func (ai AssetImage) GitHubify() AssetImage { + out := AssetImage{} + out.Png, out.Svg = ai.Png, ai.Svg + if ai.Png != nil && isLocalImage(ai.Png) { + out.Png = some(localImageToGitHub(*ai.Png)) + } + if ai.Svg != nil && isLocalImage(ai.Svg) { + out.Svg = some(localImageToGitHub(*ai.Svg)) + } + if ai.Theme != nil { + out.Theme = ai.Theme + } + if ai.ImageSync != nil { + out.ImageSync = ai.ImageSync + } + return out +} diff --git a/token-registry/img/0000_nibiru.png b/token-registry/img/0000_nibiru.png new file mode 100644 index 000000000..46dbd6488 Binary files /dev/null and b/token-registry/img/0000_nibiru.png differ diff --git a/token-registry/img/0000_nibiru.svg b/token-registry/img/0000_nibiru.svg new file mode 100644 index 000000000..15eabe343 --- /dev/null +++ b/token-registry/img/0000_nibiru.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/token-registry/img/0001_stnibi-500x500.png b/token-registry/img/0001_stnibi-500x500.png new file mode 100644 index 000000000..0b0131a3e Binary files /dev/null and b/token-registry/img/0001_stnibi-500x500.png differ diff --git a/token-registry/img/0001_stnibi-500x500.svg b/token-registry/img/0001_stnibi-500x500.svg new file mode 100644 index 000000000..f8e2b3b7a --- /dev/null +++ b/token-registry/img/0001_stnibi-500x500.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/token-registry/img/0002_usdc.png b/token-registry/img/0002_usdc.png new file mode 100644 index 000000000..8c0ffa7b8 Binary files /dev/null and b/token-registry/img/0002_usdc.png differ diff --git a/token-registry/img/0002_usdc.svg b/token-registry/img/0002_usdc.svg new file mode 100644 index 000000000..bcec78210 --- /dev/null +++ b/token-registry/img/0002_usdc.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/token-registry/img/0003_astrovault-axv.png b/token-registry/img/0003_astrovault-axv.png new file mode 100644 index 000000000..561bb4d5e Binary files /dev/null and b/token-registry/img/0003_astrovault-axv.png differ diff --git a/token-registry/img/0003_astrovault-axv.svg b/token-registry/img/0003_astrovault-axv.svg new file mode 100644 index 000000000..7039be027 --- /dev/null +++ b/token-registry/img/0003_astrovault-axv.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/token-registry/main/main.go b/token-registry/main/main.go new file mode 100644 index 000000000..385e8e8d5 --- /dev/null +++ b/token-registry/main/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "path" + "strings" + + "github.com/rs/zerolog/log" + + tokenregistry "github.com/NibiruChain/nibiru/v2/token-registry" +) + +// findRootPath returns the absolute path of the repository root +// This is retrievable with: go list -m -f {{.Dir}} +func findRootPath() (string, error) { + // rootPath, _ := exec.Command("go list -m -f {{.Dir}}").Output() + // This returns the path to the root of the project. + rootPathBz, err := exec.Command("go", "list", "-m", "-f", "{{.Dir}}").Output() + if err != nil { + return "", err + } + rootPath := strings.Trim(string(rootPathBz), "\n") + return rootPath, nil +} + +const SAVE_PATH_ASSETLIST = "dist/assetlist.json" + +func main() { + assetList := tokenregistry.NibiruAssetList() + + prettyBz, err := json.MarshalIndent(assetList, "", " ") + if err != nil { + log.Error().Msg(err.Error()) + return + } + + rootPath, err := findRootPath() + if err != nil { + log.Error().Msg(err.Error()) + return + } + savePath := path.Join(rootPath, SAVE_PATH_ASSETLIST) + + // Create the dist directory if it does not exist + distDirPath := path.Join(rootPath, "dist") + if _, err := os.Stat(distDirPath); os.IsNotExist(err) { + if err := os.Mkdir(distDirPath, 0755); err != nil { + log.Error().Msg(err.Error()) + return + } + } + + perm := os.FileMode(0666) // All can read and write + err = os.WriteFile(savePath, prettyBz, perm) + if err != nil { + log.Error().Msg(err.Error()) + return + } + + fmt.Printf("✅ Generation complete! See %v\n", SAVE_PATH_ASSETLIST) +} diff --git a/token-registry/token.go b/token-registry/token.go new file mode 100644 index 000000000..275f66bd0 --- /dev/null +++ b/token-registry/token.go @@ -0,0 +1,141 @@ +package tokenregistry + +import ( + "encoding/json" +) + +// some: Helper to create pointers for literals +func some[T any](v T) *T { + return &v +} + +type Token struct { + // A short description of the asset + Description string `json:"description"` + // An extended, detailed description of the asset (optional) + ExtendedDescription *string `json:"extended_description,omitempty"` + // Links to social platforms and official websites (optional) + Socials *SocialLinks `json:"socials,omitempty"` + // Units of denomination for the asset + DenomUnits []DenomUnit `json:"denom_units"` + // The base denomination for the asset (canonical name) + Base string `json:"base"` + // The human-readable name of the asset + Name string `json:"name"` + // The display name or symbol used in UI for the asset + Display string `json:"display"` + // The ticker or symbol of the asset + Symbol string `json:"symbol"` + // URIs for the asset's logo in different formats (optional) + LogoURIs *LogoURIs `json:"logo_URIs,omitempty"` + // Unique identifier for the asset on Coingecko (optional) + CoingeckoID *string `json:"coingecko_id,omitempty"` + // An array of image representations for the asset (optional) + Images []AssetImage `json:"images,omitempty"` + // Type of the asset (e.g., "sdk.coin", "ics20", "erc20") + TypeAsset TypeAsset `json:"type_asset"` + // Trace data for the asset (optional, for cross-chain or liquid staking assets) + Traces []Trace `json:"traces,omitempty"` +} + +type AssetList struct { + Schema string `json:"$schema"` + ChainName string `json:"chain_name"` + Assets []Token `json:"assets"` +} + +// String returns a "pretty" JSON version of the [AssetList]. +func (a AssetList) String() string { + jsonBz, _ := json.MarshalIndent(a, "", " ") + return string(jsonBz) +} + +type SocialLinks struct { + Website *string `json:"website,omitempty"` + Twitter *string `json:"twitter,omitempty"` +} + +type DenomUnit struct { + Denom string `json:"denom"` + Exponent int `json:"exponent"` + Aliases []string `json:"aliases,omitempty"` +} + +type LogoURIs struct { + Png *string `json:"png,omitempty"` + Svg *string `json:"svg,omitempty"` +} + +type AssetImage struct { + Png *string `json:"png,omitempty"` + Svg *string `json:"svg,omitempty"` + Theme *ImageTheme `json:"theme,omitempty"` + ImageSync *ImageSync `json:"image_sync,omitempty"` +} + +// ImageTheme represents theme customization for an image. +type ImageTheme struct { + // Whether the image should appear in a circular format (optional) + Circle *bool `json:"circle,omitempty"` + // Primary color in hexadecimal format (optional) + PrimaryColorHex *string `json:"primary_color_hex,omitempty"` +} + +// ImageSync represents synchronization details for cross-chain assets. +type ImageSync struct { + // Name of the chain associated with the image + ChainName string `json:"chain_name"` + // Base denomination of the synced asset + BaseDenom string `json:"base_denom"` +} + +// TypeAsset is an enum type for "type_asset". Valid values include "sdk.coin", +// "ics20", and "erc20". +type TypeAsset string + +const ( + TypeAsset_SDKCoin TypeAsset = "sdk.coin" + TypeAsset_ICS20 TypeAsset = "ics20" + TypeAsset_ERC20 TypeAsset = "erc20" +) + +// Trace represents trace data for cross-chain or liquid staking assets. +type Trace struct { + // Type of trace (e.g., "ibc", "liquid-stake", "wrapped", "bridge") + Type TraceType `json:"type"` + // Counterparty information for the trace + Counterparty Counterparty `json:"counterparty"` + // Provider of the asset for liquid staking or cross-chain trace (optional) + Provider *string `json:"provider,omitempty"` + // Additional chain-level details (optional) + Chain *TraceChainInfo `json:"chain,omitempty"` +} + +// TraceType is an enum type for "trace.type" (e.g. "ibc", "liquid-stake", +// "wrapped", "bridge") +type TraceType string + +const ( + TraceType_IBC TraceType = "ibc" + TraceType_LiquidStake TraceType = "liquid-stake" + TraceType_Wrapped TraceType = "wrapped" + TraceType_Bridge TraceType = "bridge" +) + +// Counterparty represents the counterparty information for an asset trace. +type Counterparty struct { + // Name of the counterparty chain + ChainName string `json:"chain_name"` + // Base denomination on the counterparty chain + BaseDenom string `json:"base_denom"` + // Channel ID used for communication (optional) + ChannelID *string `json:"channel_id,omitempty"` +} + +// TraceChainInfo represents additional chain-level details for an asset trace. +type TraceChainInfo struct { + // Channel ID on the chain + ChannelID string `json:"channel_id"` + // Path used for asset transfer on the chain + Path string `json:"path"` +}