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"`
+}