mirror of
https://github.com/luxfi/assets.git
synced 2026-07-26 23:39:08 +00:00
Add Injective assets (#27191)
* Create logo .png * Create logo.png * Create info.json * Create logo.png * Create info.json * Create info.json * Create logo.png * Create info.json * Create logo.png * Create info.json * Update info.json * Update info.json * Update info.json * Update info.json * Update info.json * Create logo.png * Delete logo .png * tokens * tokens * tokens * tokens * tokens * tokens --------- Co-authored-by: Denis <136321897+defisaur@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
Denis
parent
f82bade254
commit
2204745e34
@@ -1,25 +1,27 @@
|
||||
name: Fix (Dry run)
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
#Disabled, we have 'check' job, 'fix' job do invisible changes which is not convenient to debug
|
||||
|
||||
jobs:
|
||||
fix-dryrun:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run fix
|
||||
run: make fix
|
||||
|
||||
- name: Show fix result (diff)
|
||||
run: |
|
||||
git config core.ignorecase false
|
||||
git status
|
||||
git diff
|
||||
#name: Fix (Dry run)
|
||||
#on:
|
||||
# pull_request:
|
||||
# branches: [ master ]
|
||||
#
|
||||
#jobs:
|
||||
# fix-dryrun:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Set up Go
|
||||
# uses: actions/setup-go@v4
|
||||
# with:
|
||||
# go-version: 1.18
|
||||
#
|
||||
# - name: Check out code
|
||||
# uses: actions/checkout@v3
|
||||
#
|
||||
# - name: Run fix
|
||||
# run: make fix
|
||||
#
|
||||
# - name: Show fix result (diff)
|
||||
# run: |
|
||||
# git config core.ignorecase false
|
||||
# git status
|
||||
# git diff
|
||||
|
||||
+47
-45
@@ -1,46 +1,48 @@
|
||||
name: Fix All
|
||||
#Disabled, we have 'check' job, 'fix' job do invisible changes which is not convenient to debug
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
fix-all:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: github.repository_owner == 'trustwallet' && github.event.repository.fork == false
|
||||
|
||||
steps:
|
||||
- name: Checkout (trustwallet repo, secret token)
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Use trust-ci fine-grained PAT to checkout and later commit the code
|
||||
# Do not use for forked repos
|
||||
token: ${{ secrets.CI_GITHUB_TOKEN }}
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.18
|
||||
|
||||
- name: Run fix
|
||||
run: make fix
|
||||
|
||||
- name: Show fix result (diff)
|
||||
run: |
|
||||
git status
|
||||
git diff
|
||||
|
||||
- name: Run check
|
||||
run: make check
|
||||
|
||||
- name: Commit changes
|
||||
if: success()
|
||||
uses: stefanzweifel/git-auto-commit-action@v4.16.0
|
||||
with:
|
||||
commit_user_name: trust-ci
|
||||
commit_user_email: "117647528+trust-ci@users.noreply.github.com"
|
||||
commit_message: Auto fix all (sanity and consistency)
|
||||
#name: Fix All
|
||||
#
|
||||
#on:
|
||||
# push:
|
||||
# branches:
|
||||
# - '*'
|
||||
# workflow_dispatch:
|
||||
#
|
||||
#jobs:
|
||||
# fix-all:
|
||||
# runs-on: ubuntu-latest
|
||||
#
|
||||
# if: github.repository_owner == 'trustwallet' && github.event.repository.fork == false
|
||||
#
|
||||
# steps:
|
||||
# - name: Checkout (trustwallet repo, secret token)
|
||||
# uses: actions/checkout@v3
|
||||
# with:
|
||||
# # Use trust-ci fine-grained PAT to checkout and later commit the code
|
||||
# # Do not use for forked repos
|
||||
# token: ${{ secrets.CI_GITHUB_TOKEN }}
|
||||
# ref: ${{ github.ref }}
|
||||
#
|
||||
# - name: Set up Go
|
||||
# uses: actions/setup-go@v4
|
||||
# with:
|
||||
# go-version: 1.18
|
||||
#
|
||||
# - name: Run fix
|
||||
# run: make fix
|
||||
#
|
||||
# - name: Show fix result (diff)
|
||||
# run: |
|
||||
# git status
|
||||
# git diff
|
||||
#
|
||||
# - name: Run check
|
||||
# run: make check
|
||||
#
|
||||
# - name: Commit changes
|
||||
# if: success()
|
||||
# uses: stefanzweifel/git-auto-commit-action@v4.16.0
|
||||
# with:
|
||||
# commit_user_name: trust-ci
|
||||
# commit_user_email: "117647528+trust-ci@users.noreply.github.com"
|
||||
# commit_message: Auto fix all (sanity and consistency)
|
||||
|
||||
@@ -192,7 +192,6 @@ linters:
|
||||
- deadcode
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- exportloopref
|
||||
- exhaustive
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "Arbitrum",
|
||||
"type": "INJECTIVE",
|
||||
"symbol": "ARB",
|
||||
"decimals": 8,
|
||||
"website": "https://arbitrum.foundation/",
|
||||
"description": "$ARB tokens can be used to vote on Arbitrum DAO governance proposals, allowing $ARB holders to collectively shape the future of Arbitrum protocols and chains. Token holders can also delegate their voting power to delegates.",
|
||||
"explorer": "https://explorer.injective.network/asset/?tokenType=tokenFactory&denom=factory/inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk/inj1d5vz0uzwlpfvgwrwulxg6syy82axa58y4fuszd",
|
||||
"status": "active",
|
||||
"id": "inj1d5vz0uzwlpfvgwrwulxg6syy82axa58y4fuszd",
|
||||
"links": [
|
||||
{
|
||||
"name": "twitter",
|
||||
"url": "https://twitter.com/arbitrum"
|
||||
},
|
||||
{
|
||||
"name": "whitepaper",
|
||||
"url": "https://docs.arbitrum.foundation/deployment-addresses"
|
||||
},
|
||||
{
|
||||
"name": "discord",
|
||||
"url": "https://discord.com/arbitrum"
|
||||
},
|
||||
{
|
||||
"name": "coinmarketcap",
|
||||
"url": "https://coinmarketcap.com/ru/currencies/arbitrum-iou/"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Talis",
|
||||
"website": "https://injective.talis.art",
|
||||
"description": "In the dynamic landscape of NFTs and cryptocurrency, platforms that offer innovative solutions and tools play a crucial role in shaping the ecosystem.",
|
||||
"explorer": "https://explorer.injective.network/asset/?tokenType=tokenFactory&denom=factory/inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3/Talis",
|
||||
"type": "INJECTIVE",
|
||||
"symbol": "TALIS",
|
||||
"decimals": 6,
|
||||
"status": "active",
|
||||
"id": "inj1maeyvxfamtn8lfyxpjca8kuvauuf2qeu6gtxm3",
|
||||
"links": [
|
||||
{
|
||||
"name": "twitter",
|
||||
"url": "https://twitter.com/ProtocolTalis"
|
||||
},
|
||||
{
|
||||
"name": "telegram",
|
||||
"url": "https://t.me/talisprotocol"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
+40
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "Chiliz",
|
||||
"website": "https://chiliz.com",
|
||||
"description": "Chiliz, powering Socios.com, aims to give sports and esports fans the ability to crowd-manage their favorite teams, games, leagues, and events.",
|
||||
"explorer": "https://explorer.injective.network/asset/?tokenType=tokenFactory&denom=factory/inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk/inj1q6kpxy6ar5lkxqudjvryarrrttmakwsvzkvcyh",
|
||||
"type": "INJECTIVE",
|
||||
"symbol": "CHZ",
|
||||
"decimals": 8,
|
||||
"status": "active",
|
||||
"id": "inj1q6kpxy6ar5lkxqudjvryarrrttmakwsvzkvcyh",
|
||||
"tags": [
|
||||
"nft"
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"name": "telegram",
|
||||
"url": "https://t.me/chiliz_io"
|
||||
},
|
||||
{
|
||||
"name": "twitter",
|
||||
"url": "https://twitter.com/chiliZ/"
|
||||
},
|
||||
{
|
||||
"name": "facebook",
|
||||
"url": "https://facebook.com/chiliZdotcom/"
|
||||
},
|
||||
{
|
||||
"name": "blog",
|
||||
"url": "https://medium.com/chiliz"
|
||||
},
|
||||
{
|
||||
"name": "whitepaper",
|
||||
"url": "https://chiliz.com/docs/CHZ_whitepaper.pdf"
|
||||
},
|
||||
{
|
||||
"name": "coingecko",
|
||||
"url": "https://coingecko.com/en/coins/chiliz/"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "USD Coin",
|
||||
"website": "https://centre.io/usdc",
|
||||
"description": "USDC is a fully collateralized US dollar stablecoin, an Ethereum powered coin and is the brainchild of CENTRE, an open source project bootstrapped by contributions from Circle and Coinbase.",
|
||||
"explorer": "https://explorer.injective.network/asset/?tokenType=tokenFactory&denom=factory/inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk/inj1q6zlut7gtkzknkk773jecujwsdkgq882akqksk",
|
||||
"type": "INJECTIVE",
|
||||
"symbol": "USDCET",
|
||||
"decimals": 6,
|
||||
"status": "active",
|
||||
"id": "inj1q6zlut7gtkzknkk773jecujwsdkgq882akqksk",
|
||||
"tags": [
|
||||
"defi",
|
||||
"stablecoin"
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"name": "github",
|
||||
"url": "https://github.com/centrehq"
|
||||
},
|
||||
{
|
||||
"name": "medium",
|
||||
"url": "https://medium.com/centre-blog"
|
||||
},
|
||||
{
|
||||
"name": "whitepaper",
|
||||
"url": "https://centre.io/pdfs/centre-whitepaper.pdf"
|
||||
},
|
||||
{
|
||||
"name": "coinmarketcap",
|
||||
"url": "https://coinmarketcap.com/currencies/usd-coin/"
|
||||
},
|
||||
{
|
||||
"name": "coingecko",
|
||||
"url": "https://coingecko.com/en/coins/usd-coin/"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "Solana",
|
||||
"website": "https://solana.com/",
|
||||
"description": "Solana is the worlds most performant blockchain in the world at 710k transactions per second. 710k TPS is achieved by encoding the passage of time as data.",
|
||||
"explorer": "https://explorer.injective.network/asset/?tokenType=tokenFactory&denom=factory/inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk/inj1sthrn5ep8ls5vzz8f9gp89khhmedahhdkqa8z3",
|
||||
"symbol": "SOL",
|
||||
"type": "INJECTIVE",
|
||||
"decimals": 8,
|
||||
"status": "active",
|
||||
"id": "inj1sthrn5ep8ls5vzz8f9gp89khhmedahhdkqa8z3",
|
||||
"tags": [
|
||||
"staking-native"
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"name": "github",
|
||||
"url": "https://github.com/solana-labs"
|
||||
},
|
||||
{
|
||||
"name": "twitter",
|
||||
"url": "https://twitter.com/solana"
|
||||
},
|
||||
{
|
||||
"name": "reddit",
|
||||
"url": "https://reddit.com/solana"
|
||||
},
|
||||
{
|
||||
"name": "whitepaper",
|
||||
"url": "https://github.com/solana-labs/whitepaper"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -7,7 +7,7 @@ require (
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/trustwallet/assets-go-libs v0.3.7
|
||||
github.com/trustwallet/go-libs v0.7.3
|
||||
github.com/trustwallet/go-primitives v0.1.16
|
||||
github.com/trustwallet/go-primitives v0.1.17
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -264,6 +264,14 @@ github.com/trustwallet/go-primitives v0.1.16-0.20240109124104-7cbe07e453d4 h1:sY
|
||||
github.com/trustwallet/go-primitives v0.1.16-0.20240109124104-7cbe07e453d4/go.mod h1:GN9WNIOvtz9j77FSYLEqC7apbMlzQnFLNO9LOCMy5Mc=
|
||||
github.com/trustwallet/go-primitives v0.1.16 h1:vpHVXlAYYK0o7GpWYadbauY+H4GOJ0bKVp/k6gZbH6I=
|
||||
github.com/trustwallet/go-primitives v0.1.16/go.mod h1:GN9WNIOvtz9j77FSYLEqC7apbMlzQnFLNO9LOCMy5Mc=
|
||||
github.com/trustwallet/go-primitives v0.1.17-0.20240112104809-d94379365003 h1:0XkIncqfu2hBCkkhMrVw7hx/lraoyZ+G1BENh+CFtVA=
|
||||
github.com/trustwallet/go-primitives v0.1.17-0.20240112104809-d94379365003/go.mod h1:GN9WNIOvtz9j77FSYLEqC7apbMlzQnFLNO9LOCMy5Mc=
|
||||
github.com/trustwallet/go-primitives v0.1.17-0.20240112110133-ea920360531b h1:akbAfXaD987dDl5nbQbCiBSRP65pIWZdg9SNqY/pkfY=
|
||||
github.com/trustwallet/go-primitives v0.1.17-0.20240112110133-ea920360531b/go.mod h1:GN9WNIOvtz9j77FSYLEqC7apbMlzQnFLNO9LOCMy5Mc=
|
||||
github.com/trustwallet/go-primitives v0.1.17-0.20240112111413-aca39bd29cc2 h1:3tt6/BPEtZWpQlagvqEoGz9Z53Jx0vrU+uPtxT/lCWg=
|
||||
github.com/trustwallet/go-primitives v0.1.17-0.20240112111413-aca39bd29cc2/go.mod h1:GN9WNIOvtz9j77FSYLEqC7apbMlzQnFLNO9LOCMy5Mc=
|
||||
github.com/trustwallet/go-primitives v0.1.17 h1:X0vVRbJZoDcrQJTQ2vi5gubPfXEn43M9mpxkfRg/Fok=
|
||||
github.com/trustwallet/go-primitives v0.1.17/go.mod h1:GN9WNIOvtz9j77FSYLEqC7apbMlzQnFLNO9LOCMy5Mc=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"github.com/trustwallet/assets-go-libs/validation"
|
||||
"github.com/trustwallet/go-primitives/coin"
|
||||
)
|
||||
|
||||
func ValidateAsset(a AssetModel, chain coin.Coin, addr string) error {
|
||||
if err := ValidateAssetRequiredKeys(a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// All fields validated for nil and can be safety used.
|
||||
compErr := validation.NewErrComposite()
|
||||
if err := ValidateAssetType(*a.Type, chain); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateAssetID(*a.ID, addr); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateDecimals(*a.Decimals); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateAssetDecimalsAccordingType(*a.Type, *a.Decimals); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateStatus(*a.Status); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateDescription(*a.Description); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateDescriptionWebsite(*a.Description, *a.Website); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateExplorer(*a.Explorer, *a.Name, chain, addr, *a.Type); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateLinks(a.Links); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if compErr.Len() > 0 {
|
||||
return compErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"github.com/trustwallet/assets-go-libs/validation"
|
||||
)
|
||||
|
||||
func ValidateCoin(c CoinModel, allowedTags []string) error {
|
||||
if err := ValidateCoinRequiredKeys(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// All fields validated for nil and can be safety used.
|
||||
compErr := validation.NewErrComposite()
|
||||
if err := ValidateCoinType(*c.Type); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateDecimals(*c.Decimals); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateStatus(*c.Status); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateTags(c.Tags, allowedTags); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateDescription(*c.Description); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateDescriptionWebsite(*c.Description, *c.Website); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if err := ValidateLinks(c.Links); err != nil {
|
||||
compErr.Append(err)
|
||||
}
|
||||
|
||||
if compErr.Len() > 0 {
|
||||
return compErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/trustwallet/assets-go-libs/http"
|
||||
)
|
||||
|
||||
const ethAPIURL = "https://api.ethplorer.io/getTokenInfo/%s?apiKey=freekey"
|
||||
|
||||
type TokenInfoERC20 struct {
|
||||
Decimals string `json:"decimals"`
|
||||
HoldersCount int `json:"holdersCount"`
|
||||
}
|
||||
|
||||
func GetTokenInfoForERC20(tokenID string) (*TokenInfo, error) {
|
||||
url := fmt.Sprintf(ethAPIURL, tokenID)
|
||||
|
||||
var result TokenInfoERC20
|
||||
err := http.GetHTTPResponse(url, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decimals, err := strconv.Atoi(result.Decimals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TokenInfo{
|
||||
Decimals: decimals,
|
||||
HoldersCount: result.HoldersCount,
|
||||
}, nil
|
||||
}
|
||||
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/trustwallet/assets-go-libs/http"
|
||||
)
|
||||
|
||||
var (
|
||||
holdersRegexp = regexp.MustCompile(`(\d+)\saddresses`)
|
||||
decimalsRegexp = regexp.MustCompile(`(\d+)\s+<\/div>`)
|
||||
symbolRegexp = regexp.MustCompile(`<b>(\w+)<\/b>\s<span`)
|
||||
)
|
||||
|
||||
type TokenInfo struct {
|
||||
Symbol string
|
||||
Decimals int
|
||||
HoldersCount int
|
||||
}
|
||||
|
||||
func GetTokenInfo(tokenID, tokentType string) (*TokenInfo, error) {
|
||||
switch strings.ToLower(tokentType) {
|
||||
case "erc20":
|
||||
return GetTokenInfoForERC20(tokenID)
|
||||
case "bep20":
|
||||
return GetTokenInfoByScraping(fmt.Sprintf("https://bscscan.com/token/%s", tokenID))
|
||||
case "fantom":
|
||||
return GetTokenInfoByScraping(fmt.Sprintf("https://ftmscan.com/token/%s", tokenID))
|
||||
case "polygon":
|
||||
return GetTokenInfoByScraping(fmt.Sprintf("https://polygonscan.com/token/%s", tokenID))
|
||||
case "avalanche":
|
||||
return GetTokenInfoByScraping(fmt.Sprintf("https://snowtrace.io/token/%s", tokenID))
|
||||
case "spl":
|
||||
return GetTokenInfoForSPL(tokenID)
|
||||
case "trc20":
|
||||
return GetTokenInfoForTRC20(tokenID)
|
||||
case "trc10":
|
||||
return GetTokenInfoForTRC10(tokenID)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func GetTokenInfoByScraping(url string) (*TokenInfo, error) {
|
||||
data, err := http.GetHTTPResponseBytes(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove all "," from content.
|
||||
pageContent := strings.ReplaceAll(string(data), ",", "")
|
||||
|
||||
var holders, decimals int
|
||||
var symbol string
|
||||
|
||||
match := symbolRegexp.FindStringSubmatch(pageContent)
|
||||
if len(match) > 1 {
|
||||
symbol = match[1]
|
||||
}
|
||||
|
||||
match = decimalsRegexp.FindStringSubmatch(pageContent)
|
||||
if len(match) > 1 {
|
||||
decimals, err = strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
match = holdersRegexp.FindStringSubmatch(pageContent)
|
||||
if len(match) > 1 {
|
||||
holders, err = strconv.Atoi(match[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &TokenInfo{
|
||||
Symbol: symbol,
|
||||
Decimals: decimals,
|
||||
HoldersCount: holders,
|
||||
}, nil
|
||||
}
|
||||
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/trustwallet/assets-go-libs/http"
|
||||
)
|
||||
|
||||
const splAPIURL = "https://public-api.solscan.io/token/holders?tokenAddress=%s"
|
||||
|
||||
type TokenInfoSPL struct {
|
||||
Data []Data `json:"data"`
|
||||
HoldersCount int `json:"total"`
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Decimals int `json:"decimals"`
|
||||
}
|
||||
|
||||
func GetTokenInfoForSPL(tokenID string) (*TokenInfo, error) {
|
||||
url := fmt.Sprintf(splAPIURL, tokenID)
|
||||
|
||||
var result TokenInfoSPL
|
||||
err := http.GetHTTPResponse(url, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result.Data) == 0 {
|
||||
return nil, fmt.Errorf("failed to get token info for SPL token")
|
||||
}
|
||||
|
||||
return &TokenInfo{
|
||||
Decimals: result.Data[0].Decimals,
|
||||
HoldersCount: result.HoldersCount,
|
||||
}, nil
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/trustwallet/assets-go-libs/http"
|
||||
)
|
||||
|
||||
const trc10APIURL = "https://apilist.tronscan.io/api/token?id=%s"
|
||||
|
||||
type TRC10TokensResponse struct {
|
||||
Data []struct {
|
||||
Symbol string `json:"abbr"`
|
||||
Decimals int `json:"precision"`
|
||||
HoldersCount int `json:"nrOfTokenHolders"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func GetTokenInfoForTRC10(tokenID string) (*TokenInfo, error) {
|
||||
url := fmt.Sprintf(trc10APIURL, tokenID)
|
||||
|
||||
var res TRC10TokensResponse
|
||||
err := http.GetHTTPResponse(url, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(res.Data) == 0 {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
return &TokenInfo{
|
||||
Symbol: res.Data[0].Symbol,
|
||||
Decimals: res.Data[0].Decimals,
|
||||
HoldersCount: res.Data[0].HoldersCount,
|
||||
}, nil
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/trustwallet/assets-go-libs/http"
|
||||
)
|
||||
|
||||
const trc20APIURL = "https://apilist.tronscan.io/api/token_trc20?contract=%s"
|
||||
|
||||
type TRC20TokensResponse struct {
|
||||
TRC20Tokens []struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Decimals int `json:"decimals"`
|
||||
HoldersCount int `json:"holders_count"`
|
||||
} `json:"trc20_tokens"`
|
||||
}
|
||||
|
||||
func GetTokenInfoForTRC20(tokenID string) (*TokenInfo, error) {
|
||||
url := fmt.Sprintf(trc20APIURL, tokenID)
|
||||
|
||||
var res TRC20TokensResponse
|
||||
err := http.GetHTTPResponse(url, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(res.TRC20Tokens) == 0 {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
return &TokenInfo{
|
||||
Symbol: res.TRC20Tokens[0].Symbol,
|
||||
Decimals: res.TRC20Tokens[0].Decimals,
|
||||
HoldersCount: res.TRC20Tokens[0].HoldersCount,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
str "github.com/trustwallet/assets-go-libs/strings"
|
||||
"github.com/trustwallet/assets-go-libs/validation"
|
||||
"github.com/trustwallet/go-primitives/coin"
|
||||
"github.com/trustwallet/go-primitives/types"
|
||||
)
|
||||
|
||||
// Asset info specific validators.
|
||||
|
||||
func ValidateAssetRequiredKeys(a AssetModel) error {
|
||||
var fields []string
|
||||
if a.Name != nil && !isEmpty(*a.Name) {
|
||||
fields = append(fields, "name")
|
||||
}
|
||||
if a.Symbol != nil && !isEmpty(*a.Symbol) {
|
||||
fields = append(fields, "symbol")
|
||||
}
|
||||
if a.Type != nil && !isEmpty(*a.Type) {
|
||||
fields = append(fields, "type")
|
||||
}
|
||||
if a.Decimals != nil {
|
||||
fields = append(fields, "decimals")
|
||||
}
|
||||
if a.Description != nil && !isEmpty(*a.Description) {
|
||||
fields = append(fields, "description")
|
||||
}
|
||||
if a.Website != nil {
|
||||
fields = append(fields, "website")
|
||||
}
|
||||
if a.Explorer != nil && !isEmpty(*a.Explorer) {
|
||||
fields = append(fields, "explorer")
|
||||
}
|
||||
if a.Status != nil && !isEmpty(*a.Status) {
|
||||
fields = append(fields, "status")
|
||||
}
|
||||
if a.ID != nil && !isEmpty(*a.ID) {
|
||||
fields = append(fields, "id")
|
||||
}
|
||||
|
||||
if len(fields) != len(requiredAssetFields) {
|
||||
return fmt.Errorf("%w: %s", validation.ErrMissingField,
|
||||
strings.Join(str.Difference(requiredAssetFields, fields), ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateAssetType(assetType string, chain coin.Coin) error {
|
||||
chainFromType, err := types.GetChainFromAssetType(assetType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get chain from asset type: %w", err)
|
||||
}
|
||||
|
||||
if chainFromType != chain {
|
||||
return fmt.Errorf("%w: asset type field", validation.ErrInvalidField)
|
||||
}
|
||||
|
||||
if strings.ToUpper(assetType) != assetType {
|
||||
return fmt.Errorf("%w: asset type should be ALLCAPS", validation.ErrInvalidField)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateAssetID(id, address string) error {
|
||||
if id != address {
|
||||
if !strings.EqualFold(id, address) {
|
||||
return fmt.Errorf("%w: invalid id field", validation.ErrInvalidField)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: invalid case for id field", validation.ErrInvalidField)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateAssetDecimalsAccordingType(assetType string, decimals int) error {
|
||||
if assetType == "BEP2" && decimals != 8 {
|
||||
return fmt.Errorf("%w: invalid decimals field, BEP2 tokens have 8 decimals", validation.ErrInvalidField)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CoinModel info specific validators.
|
||||
|
||||
func ValidateCoinRequiredKeys(c CoinModel) error {
|
||||
var fields []string
|
||||
if c.Name != nil && !isEmpty(*c.Name) {
|
||||
fields = append(fields, "name")
|
||||
}
|
||||
if c.Symbol != nil && !isEmpty(*c.Symbol) {
|
||||
fields = append(fields, "symbol")
|
||||
}
|
||||
if c.Type != nil && !isEmpty(*c.Type) {
|
||||
fields = append(fields, "type")
|
||||
}
|
||||
if c.Decimals != nil {
|
||||
fields = append(fields, "decimals")
|
||||
}
|
||||
if c.Description != nil && !isEmpty(*c.Description) {
|
||||
fields = append(fields, "description")
|
||||
}
|
||||
if c.Website != nil && !isEmpty(*c.Website) {
|
||||
fields = append(fields, "website")
|
||||
}
|
||||
if c.Explorer != nil && !isEmpty(*c.Explorer) {
|
||||
fields = append(fields, "explorer")
|
||||
}
|
||||
if c.Status != nil && !isEmpty(*c.Status) {
|
||||
fields = append(fields, "status")
|
||||
}
|
||||
|
||||
if len(fields) != len(requiredCoinFields) {
|
||||
return fmt.Errorf("%w: %s", validation.ErrMissingField,
|
||||
strings.Join(str.Difference(requiredCoinFields, fields), ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateLinks(links []Link) error {
|
||||
if len(links) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, l := range links {
|
||||
if l.Name == nil || l.URL == nil {
|
||||
return fmt.Errorf("%w: missing required fields links.url and links.name", validation.ErrMissingField)
|
||||
}
|
||||
|
||||
if !linkNameAllowed(*l.Name) {
|
||||
return fmt.Errorf("invalid value for links.name filed, allowed only: %s",
|
||||
strings.Join(supportedLinkNames(), ", "))
|
||||
}
|
||||
|
||||
prefix := allowedLinkKeys[*l.Name]
|
||||
if prefix != "" {
|
||||
if !strings.HasPrefix(*l.URL, prefix) {
|
||||
return fmt.Errorf("invalid value '%s' for %s link url, allowed only with prefix: %s",
|
||||
*l.URL, *l.Name, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(*l.URL, "https://") {
|
||||
return fmt.Errorf("invalid value for links.url field, allowed only with https:// prefix")
|
||||
}
|
||||
|
||||
if *l.Name == "medium" {
|
||||
if !strings.Contains(*l.URL, "medium.com") {
|
||||
return fmt.Errorf("invalid value for links.url field, should contain medium.com")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateCoinType(assetType string) error {
|
||||
if assetType != "coin" {
|
||||
return fmt.Errorf("%w: only \"coin\" type allowed for coin field", validation.ErrInvalidField)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateTags(tags, allowedTags []string) error {
|
||||
for _, t := range tags {
|
||||
if !str.Contains(t, allowedTags) {
|
||||
return fmt.Errorf("%w: tag '%s' not allowed", validation.ErrInvalidField, t)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Both infos can be validated by this validators.
|
||||
|
||||
func ValidateDecimals(decimals int) error {
|
||||
if decimals > 30 || decimals < 0 {
|
||||
return fmt.Errorf("%w: decimals field", validation.ErrInvalidField)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateStatus(status string) error {
|
||||
for _, f := range allowedStatusValues {
|
||||
if f == status {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: allowed status field values: %s", validation.ErrInvalidField,
|
||||
strings.Join(allowedStatusValues, ", "))
|
||||
}
|
||||
|
||||
func ValidateDescription(description string) error {
|
||||
if len(description) > 600 {
|
||||
return fmt.Errorf("%w: invalid length for description field", validation.ErrInvalidField)
|
||||
}
|
||||
|
||||
for _, ch := range whiteSpaceCharacters {
|
||||
if strings.Contains(description, ch) {
|
||||
return fmt.Errorf("%w: description contains not allowed characters (new line, double space)",
|
||||
validation.ErrInvalidField)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateDescriptionWebsite(description, website string) error {
|
||||
if description != "-" && website == "" {
|
||||
return fmt.Errorf("%w: website field", validation.ErrMissingField)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateExplorer(explorer, name string, chain coin.Coin, addr, tokenType string) error {
|
||||
// explorer url is composed with values outside info.json.
|
||||
if chain == coin.Nativeinjective() {
|
||||
return nil
|
||||
}
|
||||
|
||||
explorerExpected, err := coin.GetCoinExploreURL(chain, addr, tokenType)
|
||||
if err != nil {
|
||||
explorerExpected = ""
|
||||
}
|
||||
|
||||
explorerActual := explorer
|
||||
|
||||
if !strings.EqualFold(explorerActual, explorerExpected) {
|
||||
explorerAlt := explorerURLAlternatives(chain.Handle, name)
|
||||
if len(explorerAlt) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var matchCount int
|
||||
|
||||
for _, e := range explorerAlt {
|
||||
if strings.EqualFold(e, explorerActual) {
|
||||
matchCount++
|
||||
}
|
||||
}
|
||||
|
||||
if matchCount == 0 {
|
||||
return fmt.Errorf("invalid value for explorer field, %s insted of %s", explorerActual, explorerExpected)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isEmpty(field string) bool {
|
||||
return field == ""
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package info
|
||||
|
||||
type (
|
||||
CoinModel struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Website *string `json:"website,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Explorer *string `json:"explorer,omitempty"`
|
||||
Research string `json:"research,omitempty"`
|
||||
Symbol *string `json:"symbol,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Decimals *int `json:"decimals,omitempty"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Links []Link `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
AssetModel struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Symbol *string `json:"symbol,omitempty"`
|
||||
Type *string `json:"type,omitempty"`
|
||||
Decimals *int `json:"decimals,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Website *string `json:"website,omitempty"`
|
||||
Explorer *string `json:"explorer,omitempty"`
|
||||
Research string `json:"research,omitempty"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
ID *string `json:"id,omitempty"`
|
||||
Links []Link `json:"links,omitempty"`
|
||||
ShortDesc *string `json:"short_desc,omitempty"`
|
||||
Audit *string `json:"audit,omitempty"`
|
||||
AuditReport *string `json:"audit_report,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Code *string `json:"code,omitempty"`
|
||||
Ticker *string `json:"ticker,omitempty"`
|
||||
ExplorerEth *string `json:"explorer-ETH,omitempty"`
|
||||
Address *string `json:"address,omitempty"`
|
||||
Twitter *string `json:"twitter,omitempty"`
|
||||
CoinMarketcap *string `json:"coinmarketcap,omitempty"`
|
||||
DataSource *string `json:"data_source,omitempty"`
|
||||
}
|
||||
|
||||
Link struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
func (a *AssetModel) GetStatus() string {
|
||||
if a.Status == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return *a.Status
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/trustwallet/go-primitives/coin"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
requiredCoinFields = []string{
|
||||
"name", "type", "symbol", "decimals",
|
||||
"description", "website", "explorer", "status",
|
||||
}
|
||||
|
||||
requiredAssetFields = []string{
|
||||
"name", "type", "symbol", "decimals",
|
||||
"description", "website", "explorer", "status", "id",
|
||||
}
|
||||
|
||||
allowedStatusValues = []string{"active", "spam", "abandoned"}
|
||||
|
||||
allowedLinkKeys = map[string]string{
|
||||
"github": "https://github.com/",
|
||||
"whitepaper": "",
|
||||
"twitter": "https://twitter.com/",
|
||||
"telegram": "https://t.me/",
|
||||
"telegram_news": "https://t.me/", // Read-only announcement channel.
|
||||
"medium": "", // URL contains 'medium.com'.
|
||||
"discord": "https://discord.com/",
|
||||
"reddit": "https://reddit.com/",
|
||||
"facebook": "https://facebook.com/",
|
||||
"youtube": "https://youtube.com/",
|
||||
"coinmarketcap": "https://coinmarketcap.com/",
|
||||
"coingecko": "https://coingecko.com/",
|
||||
"blog": "", // Blog, other than medium.
|
||||
"forum": "", // Community site.
|
||||
"docs": "",
|
||||
"source_code": "", // Other than github.
|
||||
}
|
||||
|
||||
whiteSpaceCharacters = []string{"\n", " "}
|
||||
)
|
||||
|
||||
func explorerURLAlternatives(chain, name string) []string {
|
||||
var altUrls []string
|
||||
|
||||
if name != "" {
|
||||
nameNorm := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(
|
||||
strings.ToLower(name), " ", ""), ")", ""), "(", "")
|
||||
|
||||
if strings.ToLower(chain) == coin.Coins[coin.ETHEREUM].Name {
|
||||
altUrls = append(altUrls, fmt.Sprintf("https://etherscan.io/token/%s", nameNorm))
|
||||
}
|
||||
|
||||
altUrls = append(altUrls, fmt.Sprintf("https://explorer.%s.io", nameNorm))
|
||||
altUrls = append(altUrls, fmt.Sprintf("https://scan.%s.io", nameNorm))
|
||||
}
|
||||
|
||||
return altUrls
|
||||
}
|
||||
|
||||
func linkNameAllowed(str string) bool {
|
||||
if _, exists := allowedLinkKeys[str]; !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func supportedLinkNames() []string {
|
||||
names := make([]string, 0, len(allowedLinkKeys))
|
||||
for k := range allowedLinkKeys {
|
||||
names = append(names, k)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"github.com/trustwallet/assets-go-libs/file"
|
||||
"github.com/trustwallet/assets-go-libs/path"
|
||||
"github.com/trustwallet/assets-go-libs/validation"
|
||||
"github.com/trustwallet/assets-go-libs/validation/info"
|
||||
"github.com/trustwallet/assets-go-libs/validation/list"
|
||||
"github.com/trustwallet/assets-go-libs/validation/tokenlist"
|
||||
"github.com/trustwallet/assets/internal/config"
|
||||
"github.com/trustwallet/assets/internal/info"
|
||||
"github.com/trustwallet/go-primitives/coin"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user