Skip to content

Commit

Permalink
Feature/poap support (#105)
Browse files Browse the repository at this point in the history
* first implementation of POAP contract type support, includes database migration, support for the use case in the holder scanner and some API modifications to handle the creation and querying for this kind of tokens
* initial external providers design and poap implementation
* modifying external provider to be used also by web3 contracts in the future, also renamed to holders provider, poap provider updated to new interface
* paginated results and partial balances calculation from last snapshot
* fix contract creation block calculation for gnosis chain
  • Loading branch information
lucasmenendez authored Nov 6, 2023
1 parent 81122a1 commit 7d56402
Show file tree
Hide file tree
Showing 40 changed files with 5,638 additions and 328 deletions.
40 changes: 28 additions & 12 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ List of already added tokens.
"symbol": "wANT",
"tags": "testTag1,testTag2",
"chainID": 1,
"externalID": "", // used by POAP contracts
"chainAddress": "eth:0x1234"
}
],
Expand All @@ -89,6 +90,7 @@ List of already added tokens.
```

> If `tags` is empty, it will be ommited.
> If `externalID` is empty, it will be ommited.
- ⚠️ errors:

Expand All @@ -107,8 +109,13 @@ List the supported token types.
```json
{
"supportedTypes": [
"erc20", "erc721", "erc777",
"erc1155", "nation3", "wANT"
"erc20",
"erc721",
"erc777",
"erc1155",
"nation3",
"wANT",
"poap"
]
}
```
Expand All @@ -129,17 +136,19 @@ Triggers a new scan for the provided token, starting from the defined block.
```json
{
"ID": "0x1234",
"type": "erc20|erc721|erc777|erc1155|nation3|wANT",
"type": "erc20|erc721|erc777|erc1155|nation3|wANT|poap",
"tags": "testTag1,testTag2",
"chainID": 1
"chainID": 1,
"externalID": "" // id for external holders providers
}
```

> `tags` attribute is *optional*.
> `externalID` attribute is *optional*.
- ⚠️ errors:

| HTTP Status | Message | Internal error |
| HTTP Status | Message | Internal error |
|:---:|:---|:---:|
| 400 | `malformed token information` | 4000 |
| 409 | `token already created` | 4009 |
Expand All @@ -148,8 +157,8 @@ Triggers a new scan for the provided token, starting from the defined block.
| 500 | `error getting token information` | 5004 |
| 500 | `error initialising web3 client` | 5019 |

### GET `/tokens/{tokenID}?chainID={chainID}`
Returns the information about the token referenced by the provided ID.
### GET `/tokens/{tokenID}?chainID={chainID}&externalID={externalID}`
Returns the information about the token referenced by the provided ID and chain ID, the external ID is optional.

- 📥 response:

Expand All @@ -170,11 +179,13 @@ Returns the information about the token referenced by the provided ID.
"defaultStrategy": 1,
"tags": "testTag1,testTag2",
"chainID": 1,
"externalID": "",
"chainAddress": "eth:0x1234"
}
```

> If `tags` is empty, it will be ommited.
> If `externalID` is empty, it will be ommited.
- ⚠️ errors:

Expand Down Expand Up @@ -236,7 +247,8 @@ Returns the ID's list of the strategies registered.
"MON": {
"ID": "0x1234",
"chainID": 5,
"chainAddress": "gor:0x1234"
"chainAddress": "gor:0x1234",
"externalID": "mon_id_on_external_holder_provider"
}
}
},
Expand Down Expand Up @@ -272,7 +284,8 @@ Returns the ID's list of the strategies registered.
"MON": {
"ID": "0x1234",
"chainID": 5,
"chainAddress": "gor:0x1234"
"chainAddress": "gor:0x1234",
"externalID": "mon_id_on_external_holder_provider"
},
"ANT": {
"ID": "0x1234",
Expand Down Expand Up @@ -408,11 +421,13 @@ Returns the information of the strategy related to the provided ID.
"ID": 4,
"alias": "strategy_alias",
"predicate": "MON AND (ANT OR USDC)",
"uri": "ipfs://...",
"tokens": {
"MON": {
"ID": "0x1234",
"chainID": 5,
"chainAddress": "gor:0x1234"
"chainAddress": "gor:0x1234",
"externalID": "mon_id_on_external_holder_provider"
},
"ANT": {
"ID": "0x1234",
Expand All @@ -439,7 +454,7 @@ Returns the information of the strategy related to the provided ID.
| 500 | `error getting strategy information` | 5007 |
| 500 | `error encoding strategy info` | 5015 |

### GET `/strategies/token/{tokenID}`
### GET `/strategies/token/{tokenID}?chainID={chainID}&externalID={externalID}`
Returns ID's of the already created strategies including the `tokenAddress` provided.

- 📥 response:
Expand Down Expand Up @@ -467,7 +482,8 @@ Returns ID's of the already created strategies including the `tokenAddress` prov
"MON": {
"ID": "0x1234",
"chainID": 5,
"chainAddress": "gor:0x1234"
"chainAddress": "gor:0x1234",
"externalID": "mon_id_on_external_holder_provider"
},
"ANT": {
"ID": "0x1234",
Expand Down
28 changes: 16 additions & 12 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/vocdoni/census3/census"
"github.com/vocdoni/census3/db"
"github.com/vocdoni/census3/queue"
"github.com/vocdoni/census3/service"
"github.com/vocdoni/census3/state"
storagelayer "go.vocdoni.io/dvote/data"
"go.vocdoni.io/dvote/data/downloader"
Expand All @@ -22,25 +23,28 @@ type Census3APIConf struct {
DataDir string
GroupKey string
Web3Providers state.Web3Providers
ExtProviders map[state.TokenType]service.HolderProvider
}

type census3API struct {
conf Census3APIConf
db *db.DB
endpoint *api.API
censusDB *census.CensusDB
queue *queue.BackgroundQueue
w3p state.Web3Providers
storage storagelayer.Storage
downloader *downloader.Downloader
conf Census3APIConf
db *db.DB
endpoint *api.API
censusDB *census.CensusDB
queue *queue.BackgroundQueue
w3p state.Web3Providers
storage storagelayer.Storage
downloader *downloader.Downloader
extProviders map[state.TokenType]service.HolderProvider
}

func Init(db *db.DB, conf Census3APIConf) (*census3API, error) {
newAPI := &census3API{
conf: conf,
db: db,
w3p: conf.Web3Providers,
queue: queue.NewBackgroundQueue(),
conf: conf,
db: db,
w3p: conf.Web3Providers,
queue: queue.NewBackgroundQueue(),
extProviders: conf.ExtProviders,
}
// get the current chainID
log.Infow("starting API", "chainID-web3Providers", conf.Web3Providers)
Expand Down
37 changes: 34 additions & 3 deletions api/strategies.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func (capi *census3API) getStrategies(msg *api.APIdata, ctx *httprouter.HTTPCont
ChainID: strategyToken.ChainID,
MinBalance: new(big.Int).SetBytes(strategyToken.MinBalance).String(),
ChainAddress: strategyToken.ChainAddress,
ExternalID: strategyToken.ExternalID,
}
}
strategiesResponse.Strategies = append(strategiesResponse.Strategies, strategyResponse)
Expand Down Expand Up @@ -224,6 +225,8 @@ func (capi *census3API) createStrategy(msg *api.APIdata, ctx *httprouter.HTTPCon
StrategyID: uint64(strategyID),
TokenID: common.HexToAddress(tokenData.ID).Bytes(),
MinBalance: minBalance.Bytes(),
ChainID: tokenData.ChainID,
ExternalID: tokenData.ExternalID,
}); err != nil {
return ErrCantCreateStrategy.WithErr(err)
}
Expand Down Expand Up @@ -346,6 +349,7 @@ func (capi *census3API) importStrategyDump(dump []byte) (uint64, error) {
TokenID: common.HexToAddress(token.ID).Bytes(),
MinBalance: minBalance.Bytes(),
ChainID: token.ChainID,
ExternalID: token.ExternalID,
}); err != nil {
return 0, ErrCantCreateStrategy.WithErr(err)
}
Expand Down Expand Up @@ -465,6 +469,7 @@ func (capi *census3API) getStrategy(msg *api.APIdata, ctx *httprouter.HTTPContex
ChainAddress: tokenData.ChainAddress,
MinBalance: new(big.Int).SetBytes(tokenData.MinBalance).String(),
ChainID: tokenData.ChainID,
ExternalID: tokenData.ExternalID,
}
}
res, err := json.Marshal(strategy)
Expand All @@ -479,18 +484,43 @@ func (capi *census3API) getStrategy(msg *api.APIdata, ctx *httprouter.HTTPContex
// if the provided ID is wrong or empty, a 204 response if the token has not any
// associated strategy or a 500 error if something fails.
func (capi *census3API) getTokenStrategies(msg *api.APIdata, ctx *httprouter.HTTPContext) error {
// get contract address from the tokenID query param and decode check if
// it is provided, if not return an error
strAddress := ctx.URLParam("tokenID")
if strAddress == "" {
return ErrMalformedToken.With("tokenID is required")
}
address := common.HexToAddress(strAddress)
// get chainID from query params and decode it as integer, if it's not
// provided or it's not a valid integer return an error
strChainID := ctx.Request.URL.Query().Get("chainID")
if strChainID == "" {
return ErrMalformedChainID.With("chainID is required")
}
chainID, err := strconv.Atoi(strChainID)
if err != nil {
return ErrMalformedChainID.WithErr(err)
} else if chainID < 0 {
return ErrMalformedChainID.With("chainID must be a positive number")
}
// get externalID from query params and decode it as string, it is optional
// so if it's not provided continue
externalID := ctx.Request.URL.Query().Get("externalID")
internalCtx, cancel := context.WithTimeout(ctx.Request.Context(), getTokensStrategyTimeout)
defer cancel()
// get the tokenID provided
tokenID := ctx.URLParam("tokenID")
// create db transaction
tx, err := capi.db.RO.BeginTx(internalCtx, nil)
if err != nil {
return ErrCantGetStrategies.WithErr(err)
}
qtx := capi.db.QueriesRO.WithTx(tx)
// get strategies associated to the token provided
rows, err := qtx.StrategiesByTokenID(internalCtx, common.HexToAddress(tokenID).Bytes())
rows, err := qtx.StrategiesByTokenIDAndChainIDAndExternalID(internalCtx,
queries.StrategiesByTokenIDAndChainIDAndExternalIDParams{
TokenID: address.Bytes(),
ChainID: uint64(chainID),
ExternalID: externalID,
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return ErrNoStrategies.WithErr(err)
Expand Down Expand Up @@ -520,6 +550,7 @@ func (capi *census3API) getTokenStrategies(msg *api.APIdata, ctx *httprouter.HTT
ChainAddress: strategyToken.ChainAddress,
ChainID: strategyToken.ChainID,
MinBalance: new(big.Int).SetBytes(strategyToken.MinBalance).String(),
ExternalID: strategyToken.ExternalID,
}
}
strategies.Strategies = append(strategies.Strategies, strategyResponse)
Expand Down
Loading

0 comments on commit 7d56402

Please sign in to comment.