From 05695bd8546bafae15073968f1add99bb2742dc9 Mon Sep 17 00:00:00 2001 From: Maksym Hrynenko <108219165+mhrynenko@users.noreply.github.com> Date: Wed, 19 Jun 2024 12:16:55 +0300 Subject: [PATCH] Feature: token details endpoint (#8) * add: token image config * add: token details endpoint --- config.yaml | 1 + .../spec/components/schemas/TokenDetails.yaml | 31 +++++++++++++ docs/spec/paths/token@details.yaml | 21 +++++++++ internal/config/airdrop.go | 2 + .../service/api/handlers/get_token_details.go | 42 ++++++++++++++++++ internal/service/api/models/token.go | 17 ++++++++ internal/service/router.go | 1 + resources/model_token_details.go | 43 +++++++++++++++++++ resources/model_token_details_attributes.go | 16 +++++++ 9 files changed, 174 insertions(+) create mode 100644 docs/spec/components/schemas/TokenDetails.yaml create mode 100644 docs/spec/paths/token@details.yaml create mode 100644 internal/service/api/handlers/get_token_details.go create mode 100644 resources/model_token_details.go create mode 100644 resources/model_token_details_attributes.go diff --git a/config.yaml b/config.yaml index 4501da6..5ff017f 100644 --- a/config.yaml +++ b/config.yaml @@ -16,6 +16,7 @@ routing: airdrop: amount: amount # amount in natural numbers (1, 2, 10 etc.) decimals part is taken directly from the contract token_address: erc20_token_address + token_image: image_url # token image URL to display in FE broadcaster: rpc: evm_rpc_url diff --git a/docs/spec/components/schemas/TokenDetails.yaml b/docs/spec/components/schemas/TokenDetails.yaml new file mode 100644 index 0000000..7297d49 --- /dev/null +++ b/docs/spec/components/schemas/TokenDetails.yaml @@ -0,0 +1,31 @@ +allOf: + - $ref: '#/components/schemas/TokenKey' + - type: object + required: + - attributes + properties: + attributes: + type: object + required: + - name + - symbol + - decimals + - image + properties: + name: + type: string + description: ERC20 token name + example: "USD Dollar" + symbol: + type: string + description: ERC20 token symbol + example: "USDD" + decimals: + type: integer + format: uint8 + description: ERC20 token decimals + example: 12 + image: + type: string + description: ERC20 token symbol image + example: "https://image_link/usdd.png" diff --git a/docs/spec/paths/token@details.yaml b/docs/spec/paths/token@details.yaml new file mode 100644 index 0000000..295b199 --- /dev/null +++ b/docs/spec/paths/token@details.yaml @@ -0,0 +1,21 @@ +get: + tags: + - Tokens + summary: Get details + description: Get service token's details + operationId: GetDetails + responses: + 200: + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/TokenDetails' + 400: + $ref: '#/components/responses/invalidParameter' + 500: + $ref: '#/components/responses/internalError' diff --git a/internal/config/airdrop.go b/internal/config/airdrop.go index b67f8eb..a75698b 100644 --- a/internal/config/airdrop.go +++ b/internal/config/airdrop.go @@ -2,6 +2,7 @@ package config import ( "math/big" + "net/url" "github.com/ethereum/go-ethereum/common" "gitlab.com/distributed_lab/figure/v3" @@ -20,6 +21,7 @@ type AirdropConfiger interface { type AirdropConfig struct { Amount *big.Int `fig:"amount,required"` TokenAddress common.Address `fig:"token_address,required"` + TokenImage *url.URL `fig:"token_image,required"` } type airdrop struct { diff --git a/internal/service/api/handlers/get_token_details.go b/internal/service/api/handlers/get_token_details.go new file mode 100644 index 0000000..ba5847b --- /dev/null +++ b/internal/service/api/handlers/get_token_details.go @@ -0,0 +1,42 @@ +package handlers + +import ( + "net/http" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/rarimo/evm-airdrop-svc/internal/service/api" + "github.com/rarimo/evm-airdrop-svc/internal/service/api/models" + "gitlab.com/distributed_lab/ape" + "gitlab.com/distributed_lab/ape/problems" +) + +func GetTokenDetails(w http.ResponseWriter, r *http.Request) { + name, err := api.ERC20Permit(r).Name(&bind.CallOpts{}) + if err != nil { + api.Log(r).WithError(err).Error("failed to get token name") + ape.RenderErr(w, problems.InternalError()) + return + } + + symbol, err := api.ERC20Permit(r).Symbol(&bind.CallOpts{}) + if err != nil { + api.Log(r).WithError(err).Error("failed to get token symbol") + ape.RenderErr(w, problems.InternalError()) + return + } + + decimals, err := api.ERC20Permit(r).Decimals(&bind.CallOpts{}) + if err != nil { + api.Log(r).WithError(err).Error("failed to get token decimals") + ape.RenderErr(w, problems.InternalError()) + return + } + + ape.Render(w, models.NewTokenDetailsResponse( + api.AirdropConfig(r).TokenAddress.String(), + name, + symbol, + api.AirdropConfig(r).TokenImage.String(), + decimals, + )) +} diff --git a/internal/service/api/models/token.go b/internal/service/api/models/token.go index d40a632..d3c4ef0 100644 --- a/internal/service/api/models/token.go +++ b/internal/service/api/models/token.go @@ -19,3 +19,20 @@ func NewBalanceResponse(addr string, amount *big.Int) resources.BalanceResponse }, } } + +func NewTokenDetailsResponse(addr, name, symbol, image string, decimals uint8) resources.TokenDetailsResponse { + return resources.TokenDetailsResponse{ + Data: resources.TokenDetails{ + Key: resources.Key{ + ID: addr, + Type: resources.TOKEN, + }, + Attributes: resources.TokenDetailsAttributes{ + Name: name, + Symbol: symbol, + Decimals: decimals, + Image: image, + }, + }, + } +} diff --git a/internal/service/router.go b/internal/service/router.go index eec9519..d6784e6 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -58,6 +58,7 @@ func Run(ctx context.Context, cfg *config.Config) { r.Route("/token", func(r chi.Router) { r.Get("/balance", handlers.GetBalance) + r.Get("/details", handlers.GetTokenDetails) }) }) diff --git a/resources/model_token_details.go b/resources/model_token_details.go new file mode 100644 index 0000000..10c3a78 --- /dev/null +++ b/resources/model_token_details.go @@ -0,0 +1,43 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +import "encoding/json" + +type TokenDetails struct { + Key + Attributes TokenDetailsAttributes `json:"attributes"` +} +type TokenDetailsResponse struct { + Data TokenDetails `json:"data"` + Included Included `json:"included"` +} + +type TokenDetailsListResponse struct { + Data []TokenDetails `json:"data"` + Included Included `json:"included"` + Links *Links `json:"links"` + Meta json.RawMessage `json:"meta,omitempty"` +} + +func (r *TokenDetailsListResponse) PutMeta(v interface{}) (err error) { + r.Meta, err = json.Marshal(v) + return err +} + +func (r *TokenDetailsListResponse) GetMeta(out interface{}) error { + return json.Unmarshal(r.Meta, out) +} + +// MustTokenDetails - returns TokenDetails from include collection. +// if entry with specified key does not exist - returns nil +// if entry with specified key exists but type or ID mismatches - panics +func (c *Included) MustTokenDetails(key Key) *TokenDetails { + var tokenDetails TokenDetails + if c.tryFindEntry(key, &tokenDetails) { + return &tokenDetails + } + return nil +} diff --git a/resources/model_token_details_attributes.go b/resources/model_token_details_attributes.go new file mode 100644 index 0000000..dfde25c --- /dev/null +++ b/resources/model_token_details_attributes.go @@ -0,0 +1,16 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +type TokenDetailsAttributes struct { + // ERC20 token decimals + Decimals uint8 `json:"decimals"` + // ERC20 token symbol image + Image string `json:"image"` + // ERC20 token name + Name string `json:"name"` + // ERC20 token symbol + Symbol string `json:"symbol"` +}