feat: stickerpacks service

This commit is contained in:
Richard Ramos 2022-02-02 18:50:55 -04:00
parent 37b06cd3b1
commit 0a758b0f7d
52 changed files with 25447 additions and 112 deletions

View File

@ -0,0 +1 @@
UPDATE TABLE settings SET stickers_recent_stickers = NULL WHERE synthetic_id = 'id';

122
contracts/contracts.go Normal file
View File

@ -0,0 +1,122 @@
package contracts
import (
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/contracts/registrar"
"github.com/status-im/status-go/contracts/resolver"
"github.com/status-im/status-go/contracts/snt"
"github.com/status-im/status-go/contracts/stickers"
"github.com/status-im/status-go/rpc"
)
type ContractMaker struct {
RPCClient *rpc.Client
}
func (c *ContractMaker) NewRegistry(chainID uint64) (*resolver.ENSRegistryWithFallback, error) {
contractAddr, err := resolver.ContractAddress(chainID)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return resolver.NewENSRegistryWithFallback(
contractAddr,
backend,
)
}
func (c *ContractMaker) NewPublicResolver(chainID uint64, resolverAddress *common.Address) (*resolver.PublicResolver, error) {
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return resolver.NewPublicResolver(*resolverAddress, backend)
}
func (c *ContractMaker) NewUsernameRegistrar(chainID uint64) (*registrar.UsernameRegistrar, error) {
contractAddr, err := registrar.ContractAddress(chainID)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return registrar.NewUsernameRegistrar(
contractAddr,
backend,
)
}
func (c *ContractMaker) NewSNT(chainID uint64) (*snt.SNT, error) {
contractAddr, err := snt.ContractAddress(chainID)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return snt.NewSNT(contractAddr, backend)
}
func (c *ContractMaker) NewStickerType(chainID uint64) (*stickers.StickerType, error) {
contractAddr, err := stickers.StickerTypeContractAddress(chainID)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return stickers.NewStickerType(
contractAddr,
backend,
)
}
func (c *ContractMaker) NewStickerMarket(chainID uint64) (*stickers.StickerMarket, error) {
contractAddr, err := stickers.StickerMarketContractAddress(chainID)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return stickers.NewStickerMarket(
contractAddr,
backend,
)
}
func (c *ContractMaker) NewStickerPack(chainID uint64) (*stickers.StickerPack, error) {
contractAddr, err := stickers.StickerPackContractAddress(chainID)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return stickers.NewStickerPack(
contractAddr,
backend,
)
}

View File

@ -0,0 +1,22 @@
package registrar
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
var errorNotAvailableOnChainID = errors.New("not available for chainID")
var contractAddressByChainID = map[uint64]common.Address{
1: common.HexToAddress("0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"), // mainnet
3: common.HexToAddress("0xdaae165beb8c06e0b7613168138ebba774aff071"), // ropsten
}
func ContractAddress(chainID uint64) (common.Address, error) {
addr, exists := contractAddressByChainID[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}

View File

@ -0,0 +1,22 @@
package resolver
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
var errorNotAvailableOnChainID = errors.New("not available for chainID")
var contractAddressByChainID = map[uint64]common.Address{
1: common.HexToAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"), // mainnet
3: common.HexToAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"), // ropsten
}
func ContractAddress(chainID uint64) (common.Address, error) {
addr, exists := contractAddressByChainID[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}

22
contracts/snt/address.go Normal file
View File

@ -0,0 +1,22 @@
package snt
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
var errorNotAvailableOnChainID = errors.New("not available for chainID")
var contractAddressByChainID = map[uint64]common.Address{
1: common.HexToAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"), // mainnet
3: common.HexToAddress("0xc55cf4b03948d7ebc8b9e8bad92643703811d162"), // ropsten
}
func ContractAddress(chainID uint64) (common.Address, error) {
addr, exists := contractAddressByChainID[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}

3
contracts/snt/doc.go Normal file
View File

@ -0,0 +1,3 @@
package snt
//go:generate abigen -sol snt.sol -pkg snt -out snt.go

View File

@ -1,7 +1,7 @@
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package erc20
package snt
import (
"math/big"

View File

@ -0,0 +1,48 @@
package stickers
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
var errorNotAvailableOnChainID = errors.New("not available for chainID")
var stickerTypeByChainID = map[uint64]common.Address{
1: common.HexToAddress("0x0577215622f43a39f4bc9640806dfea9b10d2a36"), // mainnet
3: common.HexToAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d"), // ropsten
}
var stickerMarketByChainID = map[uint64]common.Address{
1: common.HexToAddress("0x12824271339304d3a9f7e096e62a2a7e73b4a7e7"), // mainnet
3: common.HexToAddress("0x6CC7274aF9cE9572d22DFD8545Fb8c9C9Bcb48AD"), // ropsten
}
var stickerPackByChainID = map[uint64]common.Address{
1: common.HexToAddress("0x110101156e8F0743948B2A61aFcf3994A8Fb172e"), // mainnet
3: common.HexToAddress("0xf852198d0385c4b871e0b91804ecd47c6ba97351"), // ropsten
}
func StickerTypeContractAddress(chainID uint64) (common.Address, error) {
addr, exists := stickerTypeByChainID[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}
func StickerMarketContractAddress(chainID uint64) (common.Address, error) {
addr, exists := stickerMarketByChainID[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}
func StickerPackContractAddress(chainID uint64) (common.Address, error) {
addr, exists := stickerPackByChainID[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
package stickers
//go:generate abigen -sol contracts.sol -pkg stickers -out contracts.go

1
go.mod
View File

@ -73,4 +73,5 @@ require (
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3
)

2
go.sum
View File

@ -1864,6 +1864,8 @@ modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=

View File

@ -760,6 +760,21 @@ func (db *Database) GetMessagesFromContactsOnly() (bool, error) {
return result, err
}
func (db *Database) GetInstalledStickerPacks() (rst *json.RawMessage, err error) {
err = db.db.QueryRow("SELECT stickers_packs_installed FROM settings WHERE synthetic_id = 'id'").Scan(&rst)
return
}
func (db *Database) GetPendingStickerPacks() (rst *json.RawMessage, err error) {
err = db.db.QueryRow("SELECT stickers_packs_pending FROM settings WHERE synthetic_id = 'id'").Scan(&rst)
return
}
func (db *Database) GetRecentStickers() (rst *json.RawMessage, err error) {
err = db.db.QueryRow("SELECT stickers_recent_stickers FROM settings WHERE synthetic_id = 'id'").Scan(&rst)
return
}
func (db *Database) GetWalletAddress() (rst types.Address, err error) {
err = db.db.QueryRow("SELECT address FROM accounts WHERE wallet = 1").Scan(&rst)
return

View File

@ -42,6 +42,7 @@ import (
"github.com/status-im/status-go/services/rpcfilters"
"github.com/status-im/status-go/services/rpcstats"
"github.com/status-im/status-go/services/status"
"github.com/status-im/status-go/services/stickers"
"github.com/status-im/status-go/services/subscriptions"
"github.com/status-im/status-go/services/wakuext"
"github.com/status-im/status-go/services/wakuv2ext"
@ -113,6 +114,7 @@ type StatusNode struct {
wakuV2ExtSrvc *wakuv2ext.Service
ensSrvc *ens.Service
gifSrvc *gif.Service
stickersSrvc *stickers.Service
}
// New makes new instance of StatusNode.
@ -422,6 +424,7 @@ func (n *StatusNode) stop() error {
n.wakuV2Srvc = nil
n.wakuV2ExtSrvc = nil
n.ensSrvc = nil
n.stickersSrvc = nil
n.publicMethods = make(map[string]bool)
return nil

View File

@ -35,6 +35,7 @@ import (
"github.com/status-im/status-go/services/rpcfilters"
"github.com/status-im/status-go/services/rpcstats"
"github.com/status-im/status-go/services/status"
"github.com/status-im/status-go/services/stickers"
"github.com/status-im/status-go/services/subscriptions"
"github.com/status-im/status-go/services/wakuext"
"github.com/status-im/status-go/services/wakuv2ext"
@ -66,6 +67,7 @@ func (b *StatusNode) initServices(config *params.NodeConfig) error {
services = append(services, b.personalService())
services = append(services, b.statusPublicService())
services = append(services, b.ensService())
services = append(services, b.stickersService())
services = appendIf(config.EnableNTPSync, services, b.timeSource())
services = appendIf(b.appDB != nil && b.multiaccountsDB != nil, services, b.accountsService(accountsFeed))
services = appendIf(config.BrowsersConfig.Enabled, services, b.browsersService())
@ -370,6 +372,13 @@ func (b *StatusNode) ensService() *ens.Service {
return b.ensSrvc
}
func (b *StatusNode) stickersService() *stickers.Service {
if b.stickersSrvc == nil {
b.stickersSrvc = stickers.NewService(b.appDB, b.rpcClient, b.gethAccountManager, b.rpcFiltersSrvc, b.config)
}
return b.stickersSrvc
}
func (b *StatusNode) gifService() *gif.Service {
if b.gifSrvc == nil {
b.gifSrvc = gif.NewService(accounts.NewDB(b.appDB))

View File

@ -21,19 +21,20 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/contracts/registrar"
"github.com/status-im/status-go/contracts/resolver"
"github.com/status-im/status-go/contracts/snt"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/ens/erc20"
"github.com/status-im/status-go/services/ens/registrar"
"github.com/status-im/status-go/services/ens/resolver"
"github.com/status-im/status-go/services/rpcfilters"
"github.com/status-im/status-go/transactions"
)
func NewAPI(rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, config *params.NodeConfig) *API {
return &API{
contractMaker: &contractMaker{
contractMaker: &contracts.ContractMaker{
RPCClient: rpcClient,
},
accountsManager: accountsManager,
@ -49,7 +50,7 @@ type uri struct {
}
type API struct {
contractMaker *contractMaker
contractMaker *contracts.ContractMaker
accountsManager *account.GethManager
rpcFiltersSrvc *rpcfilters.Service
config *params.NodeConfig
@ -61,7 +62,7 @@ func (api *API) Resolver(ctx context.Context, chainID uint64, username string) (
return nil, err
}
registry, err := api.contractMaker.newRegistry(chainID)
registry, err := api.contractMaker.NewRegistry(chainID)
if err != nil {
return nil, err
}
@ -81,7 +82,7 @@ func (api *API) OwnerOf(ctx context.Context, chainID uint64, username string) (*
return nil, err
}
registry, err := api.contractMaker.newRegistry(chainID)
registry, err := api.contractMaker.NewRegistry(chainID)
if err != nil {
return nil, err
}
@ -106,7 +107,7 @@ func (api *API) ContentHash(ctx context.Context, chainID uint64, username string
return nil, err
}
resolver, err := api.contractMaker.newPublicResolver(chainID, resolverAddress)
resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
if err != nil {
return nil, err
}
@ -131,7 +132,7 @@ func (api *API) PublicKeyOf(ctx context.Context, chainID uint64, username string
return "", err
}
resolver, err := api.contractMaker.newPublicResolver(chainID, resolverAddress)
resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
if err != nil {
return "", err
}
@ -155,7 +156,7 @@ func (api *API) AddressOf(ctx context.Context, chainID uint64, username string)
return nil, err
}
resolver, err := api.contractMaker.newPublicResolver(chainID, resolverAddress)
resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
if err != nil {
return nil, err
}
@ -170,7 +171,7 @@ func (api *API) AddressOf(ctx context.Context, chainID uint64, username string)
}
func (api *API) ExpireAt(ctx context.Context, chainID uint64, username string) (string, error) {
registrar, err := api.contractMaker.newUsernameRegistrar(chainID)
registrar, err := api.contractMaker.NewUsernameRegistrar(chainID)
if err != nil {
return "", err
}
@ -185,7 +186,7 @@ func (api *API) ExpireAt(ctx context.Context, chainID uint64, username string) (
}
func (api *API) Price(ctx context.Context, chainID uint64) (string, error) {
registrar, err := api.contractMaker.newUsernameRegistrar(chainID)
registrar, err := api.contractMaker.NewUsernameRegistrar(chainID)
if err != nil {
return "", err
}
@ -211,7 +212,7 @@ func (api *API) getSigner(chainID uint64, from types.Address, password string) b
}
func (api *API) Release(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string) (string, error) {
registrar, err := api.contractMaker.newUsernameRegistrar(chainID)
registrar, err := api.contractMaker.NewUsernameRegistrar(chainID)
if err != nil {
return "", err
}
@ -242,7 +243,11 @@ func (api *API) ReleaseEstimate(ctx context.Context, chainID uint64, txArgs tran
return 0, err
}
registrarAddress := usernameRegistrarsByChainID[chainID]
registrarAddress, err := registrar.ContractAddress(chainID)
if err != nil {
return 0, err
}
return ethClient.EstimateGas(ctx, ethereum.CallMsg{
From: common.Address(txArgs.From),
To: &registrarAddress,
@ -252,7 +257,7 @@ func (api *API) ReleaseEstimate(ctx context.Context, chainID uint64, txArgs tran
}
func (api *API) Register(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string, pubkey string) (string, error) {
snt, err := api.contractMaker.newSNT(chainID)
snt, err := api.contractMaker.NewSNT(chainID)
if err != nil {
return "", err
}
@ -275,10 +280,15 @@ func (api *API) Register(ctx context.Context, chainID uint64, txArgs transaction
return "", err
}
registrarAddress, err := registrar.ContractAddress(chainID)
if err != nil {
return "", err
}
txOpts := txArgs.ToTransactOpts(api.getSigner(chainID, txArgs.From, password))
tx, err := snt.ApproveAndCall(
txOpts,
usernameRegistrarsByChainID[chainID],
registrarAddress,
price,
extraData,
)
@ -310,21 +320,28 @@ func (api *API) RegisterPrepareTxCallMsg(ctx context.Context, chainID uint64, tx
return ethereum.CallMsg{}, err
}
sntABI, err := abi.JSON(strings.NewReader(erc20.SNTABI))
sntABI, err := abi.JSON(strings.NewReader(snt.SNTABI))
if err != nil {
return ethereum.CallMsg{}, err
}
data, err := sntABI.Pack("approveAndCall", usernameRegistrarsByChainID[chainID], price, extraData)
registrarAddress, err := registrar.ContractAddress(chainID)
if err != nil {
return ethereum.CallMsg{}, err
}
contractAddress := sntByChainID[chainID]
data, err := sntABI.Pack("approveAndCall", registrarAddress, price, extraData)
if err != nil {
return ethereum.CallMsg{}, err
}
sntAddress, err := snt.ContractAddress(chainID)
if err != nil {
return ethereum.CallMsg{}, err
}
return ethereum.CallMsg{
From: common.Address(txArgs.From),
To: &contractAddress,
To: &sntAddress,
Value: big.NewInt(0),
Data: data,
}, nil
@ -364,7 +381,7 @@ func (api *API) SetPubKey(ctx context.Context, chainID uint64, txArgs transactio
return "", err
}
resolver, err := api.contractMaker.newPublicResolver(chainID, resolverAddress)
resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
if err != nil {
return "", err
}

View File

@ -1,86 +0,0 @@
package ens
import (
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/ens/erc20"
"github.com/status-im/status-go/services/ens/registrar"
"github.com/status-im/status-go/services/ens/resolver"
)
var errorNotAvailableOnChainID = errors.New("not available for chainID")
var resolversByChainID = map[uint64]common.Address{
1: common.HexToAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"), // mainnet
3: common.HexToAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"), // ropsten
}
var usernameRegistrarsByChainID = map[uint64]common.Address{
1: common.HexToAddress("0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"), // mainnet
3: common.HexToAddress("0xdaae165beb8c06e0b7613168138ebba774aff071"), // ropsten
}
var sntByChainID = map[uint64]common.Address{
1: common.HexToAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"), // mainnet
3: common.HexToAddress("0xc55cf4b03948d7ebc8b9e8bad92643703811d162"), // ropsten
}
type contractMaker struct {
RPCClient *rpc.Client
}
func (c *contractMaker) newRegistry(chainID uint64) (*resolver.ENSRegistryWithFallback, error) {
if _, ok := resolversByChainID[chainID]; !ok {
return nil, errorNotAvailableOnChainID
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return resolver.NewENSRegistryWithFallback(
resolversByChainID[chainID],
backend,
)
}
func (c *contractMaker) newPublicResolver(chainID uint64, resolverAddress *common.Address) (*resolver.PublicResolver, error) {
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return resolver.NewPublicResolver(*resolverAddress, backend)
}
func (c *contractMaker) newUsernameRegistrar(chainID uint64) (*registrar.UsernameRegistrar, error) {
if _, ok := usernameRegistrarsByChainID[chainID]; !ok {
return nil, errorNotAvailableOnChainID
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return registrar.NewUsernameRegistrar(
usernameRegistrarsByChainID[chainID],
backend,
)
}
func (c *contractMaker) newSNT(chainID uint64) (*erc20.SNT, error) {
if _, ok := sntByChainID[chainID]; !ok {
return nil, errorNotAvailableOnChainID
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return erc20.NewSNT(sntByChainID[chainID], backend)
}

View File

@ -1,3 +0,0 @@
package erc20
//go:generate abigen -sol erc20.sol -pkg erc20 -out erc20.go

540
services/stickers/api.go Normal file
View File

@ -0,0 +1,540 @@
package stickers
import (
"context"
"database/sql"
"errors"
"io/ioutil"
"math/big"
"net/http"
"sync"
"time"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multibase"
"github.com/wealdtech/go-multicodec"
"olympos.io/encoding/edn"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/contracts/stickers"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/rpcfilters"
"github.com/status-im/status-go/services/wallet/bigint"
)
const ipfsGateway = ".ipfs.cf-ipfs.com"
// ConnectionType constants
type stickerStatus int
const (
statusInstalled stickerStatus = iota
statusPurchased
statusPending
)
type API struct {
contractMaker *contracts.ContractMaker
accountsManager *account.GethManager
accountsDB *accounts.Database
rpcFiltersSrvc *rpcfilters.Service
config *params.NodeConfig
ctx context.Context
client *http.Client
}
type Sticker struct {
PackID *bigint.BigInt `json:"packID"`
URL string `json:"url,omitempty"`
Hash string `json:"hash,omitempty"`
}
type StickerPack struct {
ID *bigint.BigInt `json:"id"`
Name string `json:"name"`
Author string `json:"author"`
Owner common.Address `json:"owner"`
Price *bigint.BigInt `json:"price"`
Preview string `json:"preview"`
Thumbnail string `json:"thumbnail"`
Stickers []Sticker `json:"stickers"`
Status stickerStatus `json:"status"`
}
type ednSticker struct {
Hash string
}
type ednStickerPack struct {
Name string
Author string
Thumbnail string
Preview string
Stickers []ednSticker
}
type ednStickerPackInfo struct {
Meta ednStickerPack
}
func NewAPI(ctx context.Context, appDB *sql.DB, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, config *params.NodeConfig) *API {
return &API{
contractMaker: &contracts.ContractMaker{
RPCClient: rpcClient,
},
accountsManager: accountsManager,
accountsDB: accounts.NewDB(appDB),
rpcFiltersSrvc: rpcFiltersSrvc,
config: config,
ctx: ctx,
client: &http.Client{
Timeout: time.Second * 5,
},
}
}
func (api *API) Market(chainID uint64) ([]StickerPack, error) {
// TODO: eventually this should be changed to include pagination
accounts, err := api.accountsDB.GetAccounts()
if err != nil {
return nil, err
}
allStickerPacks, err := api.getContractPacks(chainID)
if err != nil {
return nil, err
}
purchasedPacks := make(map[uint]struct{})
purchasedPackChan := make(chan *big.Int)
errChan := make(chan error)
doneChan := make(chan struct{}, 1)
go api.getAccountsPurchasedPack(chainID, accounts, purchasedPackChan, errChan, doneChan)
for {
select {
case err := <-errChan:
if err != nil {
return nil, err
}
case packID := <-purchasedPackChan:
if packID != nil {
purchasedPacks[uint(packID.Uint64())] = struct{}{}
}
case <-doneChan:
// TODO: add an attribute to indicate if the sticker pack
// is bought, but the transaction is still pending confirmation.
var result []StickerPack
for _, pack := range allStickerPacks {
packID := uint(pack.ID.Uint64())
_, isPurchased := purchasedPacks[packID]
if isPurchased {
pack.Status = statusPurchased
}
result = append(result, pack)
}
return result, nil
}
}
}
func (api *API) execTokenPackID(chainID uint64, tokenIDs []*big.Int, resultChan chan<- *big.Int, errChan chan<- error, doneChan chan<- struct{}) {
defer close(doneChan)
defer close(errChan)
defer close(resultChan)
stickerPack, err := api.contractMaker.NewStickerPack(chainID)
if err != nil {
errChan <- err
return
}
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
wg := sync.WaitGroup{}
wg.Add(len(tokenIDs))
for _, tokenID := range tokenIDs {
go func(tokenID *big.Int) {
defer wg.Done()
packID, err := stickerPack.TokenPackId(callOpts, tokenID)
if err != nil {
errChan <- err
return
}
resultChan <- packID
}(tokenID)
}
wg.Wait()
}
func (api *API) getTokenPackIDs(chainID uint64, tokenIDs []*big.Int) ([]*big.Int, error) {
tokenPackIDChan := make(chan *big.Int)
errChan := make(chan error)
doneChan := make(chan struct{}, 1)
go api.execTokenPackID(chainID, tokenIDs, tokenPackIDChan, errChan, doneChan)
var tokenPackIDs []*big.Int
for {
select {
case <-doneChan:
return tokenPackIDs, nil
case err := <-errChan:
if err != nil {
return nil, err
}
case t := <-tokenPackIDChan:
if t != nil {
tokenPackIDs = append(tokenPackIDs, t)
}
}
}
}
func (api *API) getPurchasedPackIDs(chainID uint64, account types.Address) ([]*big.Int, error) {
// TODO: this should be replaced in the future by something like TheGraph to reduce the number of requests to infura
stickerPack, err := api.contractMaker.NewStickerPack(chainID)
if err != nil {
return nil, err
}
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
balance, err := stickerPack.BalanceOf(callOpts, common.Address(account))
if err != nil {
return nil, err
}
tokenIDs, err := api.getTokenOwnerOfIndex(chainID, account, balance)
if err != nil {
return nil, err
}
return api.getTokenPackIDs(chainID, tokenIDs)
}
func hashToURL(hash []byte) (string, error) {
// contract response includes a contenthash, which needs to be decoded to reveal
// an IPFS identifier. Once decoded, download the content from IPFS. This content
// is in EDN format, ie https://ipfs.infura.io/ipfs/QmWVVLwVKCwkVNjYJrRzQWREVvEk917PhbHYAUhA1gECTM
// and it also needs to be decoded in to a nim type
data, codec, err := multicodec.RemoveCodec(hash)
if err != nil {
return "", err
}
codecName, err := multicodec.Name(codec)
if err != nil {
return "", err
}
if codecName != "ipfs-ns" {
return "", errors.New("codecName is not ipfs-ns")
}
thisCID, err := cid.Parse(data)
if err != nil {
return "", err
}
str, err := thisCID.StringOfBase(multibase.Base32)
if err != nil {
return "", err
}
return "https://" + str + ipfsGateway, nil
}
func (api *API) fetchStickerPacks(chainID uint64, resultChan chan<- *StickerPack, errChan chan<- error, doneChan chan<- struct{}) {
defer close(doneChan)
defer close(errChan)
defer close(resultChan)
installedPacks, err := api.Installed()
if err != nil {
errChan <- err
return
}
pendingPacks, err := api.pendingStickerPacks()
if err != nil {
errChan <- err
return
}
stickerType, err := api.contractMaker.NewStickerType(chainID)
if err != nil {
errChan <- err
return
}
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
numPacks, err := stickerType.PackCount(callOpts)
if err != nil {
errChan <- err
return
}
wg := sync.WaitGroup{}
wg.Add(int(numPacks.Int64()))
for i := uint64(0); i < numPacks.Uint64(); i++ {
go func(i uint64) {
defer wg.Done()
packID := new(big.Int).SetUint64(i)
_, exists := installedPacks[uint(i)]
if exists {
return // We already have the sticker pack data, no need to query it
}
_, exists = pendingPacks[uint(i)]
if exists {
return // We already have the sticker pack data, no need to query it
}
stickerPack, err := api.fetchPackData(stickerType, packID, true)
if err != nil {
log.Warn("Could not retrieve stickerpack data", "packID", packID, "error", err)
return
}
resultChan <- stickerPack
}(i)
}
wg.Wait()
}
func (api *API) fetchPackData(stickerType *stickers.StickerType, packID *big.Int, translateHashes bool) (*StickerPack, error) {
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
packData, err := stickerType.GetPackData(callOpts, packID)
if err != nil {
return nil, err
}
packDetailsURL, err := hashToURL(packData.Contenthash)
if err != nil {
return nil, err
}
stickerPack := &StickerPack{
ID: &bigint.BigInt{Int: packID},
Owner: packData.Owner,
Price: &bigint.BigInt{Int: packData.Price},
}
err = api.downloadIPFSData(stickerPack, packDetailsURL, translateHashes)
if err != nil {
return nil, err
}
return stickerPack, nil
}
func (api *API) downloadIPFSData(stickerPack *StickerPack, packDetailsURL string, translateHashes bool) error {
// This can be improved by adding a cache using packDetailsURL as key
req, err := http.NewRequest(http.MethodGet, packDetailsURL, nil)
if err != nil {
return err
}
resp, err := api.client.Do(req)
if err != nil {
return err
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Error("failed to close the stickerpack request body", "err", err)
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return populateStickerPackAttributes(stickerPack, body, translateHashes)
}
func populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, translateHashes bool) error {
var stickerpackIPFSInfo ednStickerPackInfo
err := edn.Unmarshal(ednSource, &stickerpackIPFSInfo)
if err != nil {
return err
}
stickerPack.Author = stickerpackIPFSInfo.Meta.Author
stickerPack.Name = stickerpackIPFSInfo.Meta.Name
if translateHashes {
stickerPack.Preview, err = decodeStringHash(stickerpackIPFSInfo.Meta.Preview)
if err != nil {
return err
}
stickerPack.Thumbnail, err = decodeStringHash(stickerpackIPFSInfo.Meta.Thumbnail)
if err != nil {
return err
}
} else {
stickerPack.Preview = stickerpackIPFSInfo.Meta.Preview
stickerPack.Thumbnail = stickerpackIPFSInfo.Meta.Thumbnail
}
for _, s := range stickerpackIPFSInfo.Meta.Stickers {
url := ""
if translateHashes {
hash, err := hexutil.Decode("0x" + s.Hash)
if err != nil {
return err
}
url, err = hashToURL(hash)
if err != nil {
return err
}
}
stickerPack.Stickers = append(stickerPack.Stickers, Sticker{
PackID: stickerPack.ID,
URL: url,
Hash: s.Hash,
})
}
return nil
}
func decodeStringHash(input string) (string, error) {
hash, err := hexutil.Decode("0x" + input)
if err != nil {
return "", err
}
url, err := hashToURL(hash)
if err != nil {
return "", err
}
return url, nil
}
func (api *API) getContractPacks(chainID uint64) ([]StickerPack, error) {
stickerPackChan := make(chan *StickerPack)
errChan := make(chan error)
doneChan := make(chan struct{}, 1)
go api.fetchStickerPacks(chainID, stickerPackChan, errChan, doneChan)
var packs []StickerPack
for {
select {
case <-doneChan:
return packs, nil
case err := <-errChan:
if err != nil {
return nil, err
}
case pack := <-stickerPackChan:
if pack != nil {
packs = append(packs, *pack)
}
}
}
}
func (api *API) getAccountsPurchasedPack(chainID uint64, accs []accounts.Account, resultChan chan<- *big.Int, errChan chan<- error, doneChan chan<- struct{}) {
defer close(doneChan)
defer close(errChan)
defer close(resultChan)
wg := sync.WaitGroup{}
wg.Add(len(accs))
for _, account := range accs {
go func(acc accounts.Account) {
defer wg.Done()
packs, err := api.getPurchasedPackIDs(chainID, acc.Address)
if err != nil {
errChan <- err
return
}
for _, p := range packs {
resultChan <- p
}
}(account)
}
wg.Wait()
}
func (api *API) execTokenOwnerOfIndex(chainID uint64, account types.Address, balance *big.Int, resultChan chan<- *big.Int, errChan chan<- error, doneChan chan<- struct{}) {
defer close(doneChan)
defer close(errChan)
defer close(resultChan)
stickerPack, err := api.contractMaker.NewStickerPack(chainID)
if err != nil {
errChan <- err
return
}
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
wg := sync.WaitGroup{}
wg.Add(int(balance.Int64()))
for i := uint64(0); i < balance.Uint64(); i++ {
go func(i uint64) {
defer wg.Done()
tokenID, err := stickerPack.TokenOfOwnerByIndex(callOpts, common.Address(account), new(big.Int).SetUint64(i))
if err != nil {
errChan <- err
return
}
resultChan <- tokenID
}(i)
}
wg.Wait()
}
func (api *API) getTokenOwnerOfIndex(chainID uint64, account types.Address, balance *big.Int) ([]*big.Int, error) {
tokenIDChan := make(chan *big.Int)
errChan := make(chan error)
doneChan := make(chan struct{}, 1)
go api.execTokenOwnerOfIndex(chainID, account, balance, tokenIDChan, errChan, doneChan)
var tokenIDs []*big.Int
for {
select {
case <-doneChan:
return tokenIDs, nil
case err := <-errChan:
if err != nil {
return nil, err
}
case tokenID := <-tokenIDChan:
if tokenID != nil {
tokenIDs = append(tokenIDs, tokenID)
}
}
}
}

View File

@ -0,0 +1,139 @@
package stickers
import (
"encoding/json"
"errors"
"math/big"
"github.com/status-im/status-go/services/wallet/bigint"
)
func (api *API) Install(chainID uint64, packID uint64) error {
installedPacks, err := api.installedStickerPacks()
if err != nil {
return err
}
if _, exists := installedPacks[uint(packID)]; exists {
return errors.New("sticker pack is already installed")
}
// TODO: this does not validate if the pack is purchased. Should it?
stickerType, err := api.contractMaker.NewStickerType(chainID)
if err != nil {
return err
}
stickerPack, err := api.fetchPackData(stickerType, new(big.Int).SetUint64(packID), false)
if err != nil {
return err
}
installedPacks[uint(packID)] = *stickerPack
err = api.accountsDB.SaveSetting("stickers/packs-installed", installedPacks)
if err != nil {
return err
}
return nil
}
func (api *API) installedStickerPacks() (map[uint]StickerPack, error) {
stickerPacks := make(map[uint]StickerPack)
installedStickersJSON, err := api.accountsDB.GetInstalledStickerPacks()
if err != nil {
return nil, err
}
if installedStickersJSON == nil {
return stickerPacks, nil
}
err = json.Unmarshal(*installedStickersJSON, &stickerPacks)
if err != nil {
return nil, err
}
return stickerPacks, nil
}
func (api *API) Installed() (map[uint]StickerPack, error) {
stickerPacks, err := api.installedStickerPacks()
if err != nil {
return nil, err
}
for packID, stickerPack := range stickerPacks {
stickerPack.Status = statusInstalled
stickerPack.Preview, err = decodeStringHash(stickerPack.Preview)
if err != nil {
return nil, err
}
stickerPack.Thumbnail, err = decodeStringHash(stickerPack.Thumbnail)
if err != nil {
return nil, err
}
for i, sticker := range stickerPack.Stickers {
sticker.URL, err = decodeStringHash(sticker.Hash)
if err != nil {
return nil, err
}
stickerPack.Stickers[i] = sticker
}
stickerPacks[packID] = stickerPack
}
return stickerPacks, nil
}
func (api *API) Uninstall(packID uint64) error {
installedPacks, err := api.installedStickerPacks()
if err != nil {
return err
}
if _, exists := installedPacks[uint(packID)]; !exists {
return errors.New("sticker pack is not installed")
}
delete(installedPacks, uint(packID))
err = api.accountsDB.SaveSetting("stickers/packs-installed", installedPacks)
if err != nil {
return err
}
// Removing uninstalled pack from recent stickers
recentStickers, err := api.recentStickers()
if err != nil {
return err
}
pID := &bigint.BigInt{Int: new(big.Int).SetUint64(packID)}
idx := -1
for i, r := range recentStickers {
if r.PackID.Cmp(pID.Int) == 0 {
idx = i
break
}
}
if idx > -1 {
var newRecentStickers []Sticker
newRecentStickers = append(newRecentStickers, recentStickers[:idx]...)
if idx != len(recentStickers)-1 {
newRecentStickers = append(newRecentStickers, recentStickers[idx+1:]...)
}
return api.accountsDB.SaveSetting("stickers/recent-stickers", newRecentStickers)
}
return nil
}

View File

@ -0,0 +1,81 @@
package stickers
import (
"encoding/json"
"errors"
"math/big"
)
func (api *API) AddPending(chainID uint64, packID uint64) error {
pendingPacks, err := api.pendingStickerPacks()
if err != nil {
return err
}
if _, exists := pendingPacks[uint(packID)]; exists {
return errors.New("sticker pack is already pending")
}
stickerType, err := api.contractMaker.NewStickerType(chainID)
if err != nil {
return err
}
stickerPack, err := api.fetchPackData(stickerType, new(big.Int).SetUint64(packID), false)
if err != nil {
return err
}
pendingPacks[uint(packID)] = *stickerPack
return api.accountsDB.SaveSetting("stickers/packs-pending", pendingPacks)
}
func (api *API) pendingStickerPacks() (map[uint]StickerPack, error) {
stickerPacks := make(map[uint]StickerPack)
pendingStickersJSON, err := api.accountsDB.GetPendingStickerPacks()
if err != nil {
return nil, err
}
if pendingStickersJSON == nil {
return stickerPacks, nil
}
err = json.Unmarshal(*pendingStickersJSON, &stickerPacks)
if err != nil {
return nil, err
}
return stickerPacks, nil
}
func (api *API) Pending() (map[uint]StickerPack, error) {
stickerPacks, err := api.pendingStickerPacks()
if err != nil {
return nil, err
}
for packID, stickerPack := range stickerPacks {
stickerPack.Status = statusPending
stickerPacks[packID] = stickerPack
}
return stickerPacks, nil
}
func (api *API) RemovePending(packID uint64) error {
pendingPacks, err := api.pendingStickerPacks()
if err != nil {
return err
}
if _, exists := pendingPacks[uint(packID)]; !exists {
return errors.New("sticker pack is not pending")
}
delete(pendingPacks, uint(packID))
return api.accountsDB.SaveSetting("stickers/packs-pending", pendingPacks)
}

View File

@ -0,0 +1,72 @@
package stickers
import (
"encoding/json"
)
const maxNumberRecentStickers = 24
func (api *API) recentStickers() ([]Sticker, error) {
var recentStickersList []Sticker
recentStickersJSON, err := api.accountsDB.GetRecentStickers()
if err != nil {
return nil, err
}
if recentStickersJSON == nil {
return nil, nil
}
err = json.Unmarshal(*recentStickersJSON, &recentStickersList)
if err != nil {
return nil, err
}
return recentStickersList, nil
}
func (api *API) Recent() ([]Sticker, error) {
recentStickersList, err := api.recentStickers()
if err != nil {
return nil, err
}
for i, sticker := range recentStickersList {
sticker.URL, err = decodeStringHash(sticker.Hash)
if err != nil {
return nil, err
}
recentStickersList[i] = sticker
}
return recentStickersList, nil
}
func (api *API) AddRecent(sticker Sticker) error {
recentStickersList, err := api.recentStickers()
if err != nil {
return err
}
// Remove duplicated
idx := -1
for i, currSticker := range recentStickersList {
if currSticker.PackID.Cmp(sticker.PackID.Int) == 0 {
idx = i
}
}
if idx > -1 {
recentStickersList = append(recentStickersList[:idx], recentStickersList[idx+1:]...)
}
sticker.URL = ""
if len(recentStickersList) >= maxNumberRecentStickers {
recentStickersList = append([]Sticker{sticker}, recentStickersList[:maxNumberRecentStickers-1]...)
} else {
recentStickersList = append([]Sticker{sticker}, recentStickersList...)
}
return api.accountsDB.SaveSetting("stickers/recent-stickers", recentStickersList)
}

View File

@ -0,0 +1,68 @@
package stickers
import (
"context"
"database/sql"
"github.com/ethereum/go-ethereum/p2p"
ethRpc "github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/rpcfilters"
)
// NewService initializes service instance.
func NewService(appDB *sql.DB, rpcClient *rpc.Client, accountsManager *account.GethManager, rpcFiltersSrvc *rpcfilters.Service, config *params.NodeConfig) *Service {
ctx, cancel := context.WithCancel(context.Background())
return &Service{
appDB: appDB,
rpcClient: rpcClient,
accountsManager: accountsManager,
rpcFiltersSrvc: rpcFiltersSrvc,
config: config,
ctx: ctx,
cancel: cancel,
}
}
// Service is a browsers service.
type Service struct {
appDB *sql.DB
rpcClient *rpc.Client
accountsManager *account.GethManager
rpcFiltersSrvc *rpcfilters.Service
config *params.NodeConfig
ctx context.Context
cancel context.CancelFunc
}
// Start a service.
func (s *Service) Start() error {
return nil
}
// Stop a service.
func (s *Service) Stop() error {
s.cancel()
return nil
}
// APIs returns list of available RPC APIs.
func (s *Service) APIs() []ethRpc.API {
return []ethRpc.API{
{
Namespace: "stickers",
Version: "0.1.0",
Service: NewAPI(s.ctx, s.appDB, s.rpcClient, s.accountsManager, s.rpcFiltersSrvc, s.config),
},
}
}
// Protocols returns list of p2p protocols.
func (s *Service) Protocols() []p2p.Protocol {
return nil
}

View File

@ -0,0 +1,140 @@
package stickers
import (
"context"
"math/big"
"strings"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/contracts/snt"
"github.com/status-im/status-go/contracts/stickers"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/transactions"
)
func (api *API) getSigner(chainID uint64, from types.Address, password string) bind.SignerFn {
return func(addr common.Address, tx *ethTypes.Transaction) (*ethTypes.Transaction, error) {
selectedAccount, err := api.accountsManager.VerifyAccountPassword(api.config.KeyStoreDir, from.Hex(), password)
if err != nil {
return nil, err
}
s := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID))
return ethTypes.SignTx(tx, s, selectedAccount.PrivateKey)
}
}
func (api *API) Buy(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, packID *big.Int, password string) (string, error) {
err := api.AddPending(chainID, packID.Uint64())
if err != nil {
return "", err
}
snt, err := api.contractMaker.NewSNT(chainID)
if err != nil {
return "", err
}
stickerType, err := api.contractMaker.NewStickerType(chainID)
if err != nil {
return "", err
}
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
packInfo, err := stickerType.GetPackData(callOpts, packID)
if err != nil {
return "", err
}
stickerMarketABI, err := abi.JSON(strings.NewReader(stickers.StickerMarketABI))
if err != nil {
return "", err
}
extraData, err := stickerMarketABI.Pack("buyToken", packID, packInfo.Price)
if err != nil {
return "", err
}
stickerMarketAddress, err := stickers.StickerMarketContractAddress(chainID)
if err != nil {
return "", err
}
txOpts := txArgs.ToTransactOpts(api.getSigner(chainID, txArgs.From, password))
tx, err := snt.ApproveAndCall(
txOpts,
stickerMarketAddress,
packInfo.Price,
extraData,
)
if err != nil {
return "", err
}
// TODO: track pending transaction (do this in ENS service too)
go api.rpcFiltersSrvc.TriggerTransactionSentToUpstreamEvent(types.Hash(tx.Hash()))
return tx.Hash().String(), nil
}
func (api *API) BuyEstimate(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, packID *big.Int) (uint64, error) {
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
stickerType, err := api.contractMaker.NewStickerType(chainID)
if err != nil {
return 0, err
}
packInfo, err := stickerType.GetPackData(callOpts, packID)
if err != nil {
return 0, err
}
stickerMarketABI, err := abi.JSON(strings.NewReader(stickers.StickerMarketABI))
if err != nil {
return 0, err
}
extraData, err := stickerMarketABI.Pack("buyToken", packID, packInfo.Price)
if err != nil {
return 0, err
}
sntABI, err := abi.JSON(strings.NewReader(snt.SNTABI))
if err != nil {
return 0, err
}
stickerMarketAddress, err := stickers.StickerMarketContractAddress(chainID)
if err != nil {
return 0, err
}
data, err := sntABI.Pack("approveAndCall", stickerMarketAddress, packInfo.Price, extraData)
if err != nil {
return 0, err
}
ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
if err != nil {
return 0, err
}
sntAddress, err := snt.ContractAddress(chainID)
if err != nil {
return 0, err
}
return ethClient.EstimateGas(ctx, ethereum.CallMsg{
From: common.Address(txArgs.From),
To: &sntAddress,
Value: big.NewInt(0),
Data: data,
})
}

View File

@ -12,9 +12,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/contracts/ierc20"
"github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/chain"
"github.com/status-im/status-go/services/wallet/ierc20"
)
var requestTimeout = 20 * time.Second

2
vendor/modules.txt vendored
View File

@ -687,3 +687,5 @@ gopkg.in/natefinch/npipe.v2
gopkg.in/urfave/cli.v1
# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3
# olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3
olympos.io/encoding/edn

4
vendor/olympos.io/encoding/edn/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,4 @@
language: go
sudo: false
go:
- 1.5

25
vendor/olympos.io/encoding/edn/LICENSE generated vendored Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) 2015, The Go Authors, Jean Niklas L'orange
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the name of Google Inc., the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

116
vendor/olympos.io/encoding/edn/README.md generated vendored Normal file
View File

@ -0,0 +1,116 @@
# Go implementation of EDN, extensible data notation
[![GoDoc](https://godoc.org/olympos.io/encoding/edn?status.svg)](https://godoc.org/olympos.io/encoding/edn)
go-edn is a Golang library to read and write
[EDN](https://github.com/edn-format/edn) (extensible data notation), a subset of
Clojure used for transferring data between applications, much like JSON or XML.
EDN is also a very good language for configuration files, much like a JSON-like
version of YAML.
This library is heavily influenced by the JSON library that ships with Go, and
people familiar with that package should know the basics of how this library
works. In fact, this should be close to a drop-in replacement for the
`encoding/json` package if you only use basic functionality.
This implementation is complete, stable, and presumably also bug free. This
is why you don't see any changes in the repository.
If you wonder why you should (or should not) use EDN, you can have a look at the
[why](docs/why.md) document.
## Installation and Usage
The import path for the package is `olympos.io/encoding/edn`
To install it, run:
```shell
go get olympos.io/encoding/edn
```
To use it in your project, you import `olympos.io/encoding/edn` and refer to it as `edn`
like this:
```go
import "olympos.io/encoding/edn"
//...
edn.DoStuff()
```
The previous import path of this library was `gopkg.in/edn.v1`, which is still
permanently supported.
## Quickstart
You can follow http://blog.golang.org/json-and-go and replace every occurence of
JSON with EDN (and the JSON data with EDN data), and the text makes almost
perfect sense. The only caveat is that, since EDN is more general than JSON, go-edn
stores arbitrary maps on the form `map[interface{}]interface{}`.
go-edn also ships with keywords, symbols and tags as types.
For a longer introduction on how to use the library, see
[introduction.md](docs/introduction.md). If you're familiar with the JSON
package, then the [API Documentation](https://godoc.org/olympos.io/encoding/edn) might
be the only thing you need.
## Example Usage
Say you want to describe your pet forum's users as EDN. They have the following
types:
```go
type Animal struct {
Name string
Type string `edn:"kind"`
}
type Person struct {
Name string
Birthyear int `edn:"born"`
Pets []Animal
}
```
With go-edn, we can do as follows to read and write these types:
```go
import "olympos.io/encoding/edn"
//...
func ReturnData() (Person, error) {
data := `{:name "Hans",
:born 1970,
:pets [{:name "Cap'n Jack" :kind "Sparrow"}
{:name "Freddy" :kind "Cockatiel"}]}`
var user Person
err := edn.Unmarshal([]byte(data), &user)
// user '==' Person{"Hans", 1970,
// []Animal{{"Cap'n Jack", "Sparrow"}, {"Freddy", "Cockatiel"}}}
return user, err
}
```
If you want to write that user again, just `Marshal` it:
```go
bs, err := edn.Marshal(user)
```
## Dependencies
go-edn has no external dependencies, except the default Go library. However, as
it depends on `math/big.Float`, go-edn requires Go 1.5 or higher.
## License
Copyright © 2015-2019 Jean Niklas L'orange and [contributors](https://github.com/go-edn/edn/graphs/contributors)
Distributed under the BSD 3-clause license, which is available in the file
LICENSE.

98
vendor/olympos.io/encoding/edn/compact.go generated vendored Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2015 Jean Niklas L'orange. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edn
import (
"bytes"
"io"
)
func tokNeedsDelim(t tokenType) bool {
switch t {
case tokenString, tokenListStart, tokenListEnd, tokenVectorStart,
tokenVectorEnd, tokenMapEnd, tokenMapStart, tokenSetStart, tokenDiscard, tokenError:
return false
}
return true
}
func delimits(r rune) bool {
switch r {
case '{', '}', '[', ']', '(', ')', '\\', '"':
return true
}
return isWhitespace(r)
}
// Compact appends to dst a compacted form of the EDN-encoded src. It does not
// remove discard values.
func Compact(dst *bytes.Buffer, src []byte) error {
origLen := dst.Len()
var lex lexer
lex.reset()
buf := bytes.NewBuffer(src)
start, pos := 0, 0
needsDelim := false
prevIgnore := '\uFFFD'
r, size, err := buf.ReadRune()
for ; err == nil; r, size, err = buf.ReadRune() {
ls := lex.state(r)
ppos := pos
pos += size
switch ls {
case lexCont:
if ppos == start && needsDelim && !delimits(r) {
dst.WriteRune(prevIgnore)
}
continue
case lexIgnore:
prevIgnore = r
start = pos
case lexError:
dst.Truncate(origLen)
return lex.err
case lexEnd:
// here we might want to discard #_ and the like. Currently we don't.
dst.Write(src[start:pos])
needsDelim = tokNeedsDelim(lex.token)
lex.reset()
start = pos
case lexEndPrev:
dst.Write(src[start:ppos])
lex.reset()
lss := lex.state(r)
needsDelim = tokNeedsDelim(lex.token)
switch lss {
case lexIgnore:
prevIgnore = r
start = pos
case lexCont:
start = ppos
case lexEnd:
dst.WriteRune(r)
lex.reset()
start = pos
case lexEndPrev:
dst.Truncate(origLen)
return errInternal
case lexError:
dst.Truncate(origLen)
return lex.err
}
}
}
if err != io.EOF {
return err
}
ls := lex.eof()
switch ls {
case lexEnd:
dst.Write(src[start:pos])
case lexError:
dst.Truncate(origLen)
return lex.err
}
return nil
}

1678
vendor/olympos.io/encoding/edn/decode.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

142
vendor/olympos.io/encoding/edn/edn_tags.go generated vendored Normal file
View File

@ -0,0 +1,142 @@
// Copyright 2015 Jean Niklas L'orange. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package edn implements encoding and decoding of EDN values as defined in
// https://github.com/edn-format/edn. For a full introduction on how to use
// go-edn, see https://github.com/go-edn/edn/blob/v1/docs/introduction.md. Fully
// self-contained examples of go-edn can be found at
// https://github.com/go-edn/edn/tree/v1/examples.
//
// Note that the small examples in this package is not checking errors as
// persively as you should do when you use this package. This is done because
// I'd like the examples to be easily readable and understandable. The bigger
// examples provide proper error handling.
package edn
import (
"encoding/base64"
"errors"
"math/big"
"reflect"
"sync"
"time"
)
var (
ErrNotFunc = errors.New("Value is not a function")
ErrMismatchArities = errors.New("Function does not have single argument in, two argument out")
ErrNotConcrete = errors.New("Value is not a concrete non-function type")
ErrTagOverwritten = errors.New("Previous tag implementation was overwritten")
)
var globalTags TagMap
// A TagMap contains mappings from tag literals to functions and structs that is
// used when decoding.
type TagMap struct {
sync.RWMutex
m map[string]reflect.Value
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()
// AddTagFn adds fn as a converter function for tagname tags to this TagMap. fn
// must have the signature func(T) (U, error), where T is the expected input
// type and U is the output type. See Decoder.AddTagFn for examples.
func (tm *TagMap) AddTagFn(tagname string, fn interface{}) error {
// TODO: check name
rfn := reflect.ValueOf(fn)
rtyp := rfn.Type()
if rtyp.Kind() != reflect.Func {
return ErrNotFunc
}
if rtyp.NumIn() != 1 || rtyp.NumOut() != 2 || !rtyp.Out(1).Implements(errorType) {
// ok to have variadic arity?
return ErrMismatchArities
}
return tm.addVal(tagname, rfn)
}
// MustAddTagFn adds fn as a converter function for tagname tags to this TagMap
// like AddTagFn, except this function panics if the tag could not be added.
func (tm *TagMap) MustAddTagFn(tagname string, fn interface{}) {
if err := tm.AddTagFn(tagname, fn); err != nil {
panic(err)
}
}
func (tm *TagMap) addVal(name string, val reflect.Value) error {
tm.Lock()
if tm.m == nil {
tm.m = map[string]reflect.Value{}
}
_, ok := tm.m[name]
tm.m[name] = val
tm.Unlock()
if ok {
return ErrTagOverwritten
} else {
return nil
}
}
// AddTagFn adds fn as a converter function for tagname tags to the global
// TagMap. fn must have the signature func(T) (U, error), where T is the
// expected input type and U is the output type. See Decoder.AddTagFn for
// examples.
func AddTagFn(tagname string, fn interface{}) error {
return globalTags.AddTagFn(tagname, fn)
}
// MustAddTagFn adds fn as a converter function for tagname tags to the global
// TagMap like AddTagFn, except this function panics if the tag could not be added.
func MustAddTagFn(tagname string, fn interface{}) {
globalTags.MustAddTagFn(tagname, fn)
}
// AddTagStructs adds the struct as a matching struct for tagname tags to this
// TagMap. val can not be a channel, function, interface or an unsafe pointer.
// See Decoder.AddTagStruct for examples.
func (tm *TagMap) AddTagStruct(tagname string, val interface{}) error {
rstruct := reflect.ValueOf(val)
switch rstruct.Type().Kind() {
case reflect.Invalid, reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer:
return ErrNotConcrete
}
return tm.addVal(tagname, rstruct)
}
// AddTagStructs adds the struct as a matching struct for tagname tags to the
// global TagMap. val can not be a channel, function, interface or an unsafe
// pointer. See Decoder.AddTagStruct for examples.
func AddTagStruct(tagname string, val interface{}) error {
return globalTags.AddTagStruct(tagname, val)
}
func init() {
err := AddTagFn("inst", func(s string) (time.Time, error) {
return time.Parse(time.RFC3339Nano, s)
})
if err != nil {
panic(err)
}
err = AddTagFn("base64", base64.StdEncoding.DecodeString)
if err != nil {
panic(err)
}
}
// A MathContext specifies the precision and rounding mode for
// `math/big.Float`s when decoding.
type MathContext struct {
Precision uint
Mode big.RoundingMode
}
// The GlobalMathContext is the global MathContext. It is used if no other
// context is provided. See MathContext for example usage.
var GlobalMathContext = MathContext{
Mode: big.ToNearestEven,
Precision: 192,
}

1422
vendor/olympos.io/encoding/edn/encode.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

177
vendor/olympos.io/encoding/edn/extras.go generated vendored Normal file
View File

@ -0,0 +1,177 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edn
import (
"reflect"
"strconv"
"unicode/utf8"
)
// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
// or it returns -1.
func getu4(s []byte) rune {
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
return -1
}
r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
if err != nil {
return -1
}
return rune(r)
}
// indirect walks down v allocating pointers as needed,
// until it gets to a non-pointer.
// if it encounters an Unmarshaler, indirect stops and returns that.
// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
func (d *Decoder) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, reflect.Value) {
// If v is a named type and is addressable,
// start with its address, so that if the type has pointer methods,
// we find them.
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
v = v.Addr()
}
for {
// Load value from interface, but only if the result will be
// usefully addressable.
if v.Kind() == reflect.Interface && !v.IsNil() {
e := v.Elem()
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
v = e
continue
}
}
if v.Kind() != reflect.Ptr {
break
}
if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
break
}
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
if v.Type().NumMethod() > 0 {
if u, ok := v.Interface().(Unmarshaler); ok {
return u, reflect.Value{}
}
}
v = v.Elem()
}
return nil, v
}
// unquote converts a quoted EDN string literal s into an actual string t.
// The rules are different than for Go, so cannot use strconv.Unquote.
func unquote(s []byte) (t string, ok bool) {
s, ok = unquoteBytes(s)
t = string(s)
return
}
func unquoteBytes(s []byte) (t []byte, ok bool) {
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
return
}
s = s[1 : len(s)-1]
// Check for unusual characters. If there are none,
// then no unquoting is needed, so return a slice of the
// original bytes.
r := 0
for r < len(s) {
c := s[r]
if c == '\\' || c == '"' {
break
}
if c < utf8.RuneSelf {
r++
continue
}
rr, size := utf8.DecodeRune(s[r:])
if rr == utf8.RuneError && size == 1 {
break
}
r += size
}
if r == len(s) {
return s, true
}
b := make([]byte, len(s)+2*utf8.UTFMax)
w := copy(b, s[0:r])
for r < len(s) {
// Out of room? Can only happen if s is full of
// malformed UTF-8 and we're replacing each
// byte with RuneError.
if w >= len(b)-2*utf8.UTFMax {
nb := make([]byte, (len(b)+utf8.UTFMax)*2)
copy(nb, b[0:w])
b = nb
}
switch c := s[r]; {
case c == '\\':
r++
if r >= len(s) {
return
}
switch s[r] {
default:
return
case '"', '\\', '/', '\'':
b[w] = s[r]
r++
w++
case 'b':
b[w] = '\b'
r++
w++
case 'f':
b[w] = '\f'
r++
w++
case 'n':
b[w] = '\n'
r++
w++
case 'r':
b[w] = '\r'
r++
w++
case 't':
b[w] = '\t'
r++
w++
case 'u':
r--
rr := getu4(s[r:])
if rr < 0 {
return
}
r += 6
w += utf8.EncodeRune(b[w:], rr)
}
// Quote is invalid
case c == '"':
return
// ASCII
case c < utf8.RuneSelf:
b[w] = c
r++
w++
// Coerce to well-formed UTF-8.
default:
rr, size := utf8.DecodeRune(s[r:])
r += size
w += utf8.EncodeRune(b[w:], rr)
}
}
return b[0:w], true
}

143
vendor/olympos.io/encoding/edn/fold.go generated vendored Normal file
View File

@ -0,0 +1,143 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edn
import (
"bytes"
"unicode/utf8"
)
const (
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
kelvin = '\u212a'
smallLongEss = '\u017f'
)
// foldFunc returns one of four different case folding equivalence
// functions, from most general (and slow) to fastest:
//
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
// 3) asciiEqualFold, no special, but includes non-letters (including _)
// 4) simpleLetterEqualFold, no specials, no non-letters.
//
// The letters S and K are special because they map to 3 runes, not just 2:
// * S maps to s and to U+017F 'ſ' Latin small letter long s
// * k maps to K and to U+212A '' Kelvin sign
// See https://play.golang.org/p/tTxjOc0OGo
//
// The returned function is specialized for matching against s and
// should only be given s. It's not curried for performance reasons.
func foldFunc(s []byte) func(s, t []byte) bool {
nonLetter := false
special := false // special letter
for _, b := range s {
if b >= utf8.RuneSelf {
return bytes.EqualFold
}
upper := b & caseMask
if upper < 'A' || upper > 'Z' {
nonLetter = true
} else if upper == 'K' || upper == 'S' {
// See above for why these letters are special.
special = true
}
}
if special {
return equalFoldRight
}
if nonLetter {
return asciiEqualFold
}
return simpleLetterEqualFold
}
// equalFoldRight is a specialization of bytes.EqualFold when s is
// known to be all ASCII (including punctuation), but contains an 's',
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
// See comments on foldFunc.
func equalFoldRight(s, t []byte) bool {
for _, sb := range s {
if len(t) == 0 {
return false
}
tb := t[0]
if tb < utf8.RuneSelf {
if sb != tb {
sbUpper := sb & caseMask
if 'A' <= sbUpper && sbUpper <= 'Z' {
if sbUpper != tb&caseMask {
return false
}
} else {
return false
}
}
t = t[1:]
continue
}
// sb is ASCII and t is not. t must be either kelvin
// sign or long s; sb must be s, S, k, or K.
tr, size := utf8.DecodeRune(t)
switch sb {
case 's', 'S':
if tr != smallLongEss {
return false
}
case 'k', 'K':
if tr != kelvin {
return false
}
default:
return false
}
t = t[size:]
}
if len(t) > 0 {
return false
}
return true
}
// asciiEqualFold is a specialization of bytes.EqualFold for use when
// s is all ASCII (but may contain non-letters) and contains no
// special-folding letters.
// See comments on foldFunc.
func asciiEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, sb := range s {
tb := t[i]
if sb == tb {
continue
}
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
if sb&caseMask != tb&caseMask {
return false
}
} else {
return false
}
}
return true
}
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
// use when s is all ASCII letters (no underscores, etc) and also
// doesn't contain 'k', 'K', 's', or 'S'.
// See comments on foldFunc.
func simpleLetterEqualFold(s, t []byte) bool {
if len(s) != len(t) {
return false
}
for i, b := range s {
if b&caseMask != t[i]&caseMask {
return false
}
}
return true
}

3
vendor/olympos.io/encoding/edn/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module olympos.io/encoding/edn
go 1.13

603
vendor/olympos.io/encoding/edn/lexer.go generated vendored Normal file
View File

@ -0,0 +1,603 @@
// Copyright 2015 Jean Niklas L'orange. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edn
import (
"strconv"
u "unicode"
)
type lexState int
const (
lexCont = lexState(iota) // continue reading
lexIgnore // values you can ignore, just whitespace and comments atm
lexEnd // value ended with input given in
lexEndPrev // value ended with previous input
lexError // erroneous input
)
type tokenType int
const ( // value types from lexer
tokenSymbol = tokenType(iota)
tokenKeyword
tokenString
tokenInt
tokenFloat
tokenTag
tokenChar
tokenListStart
tokenListEnd
tokenVectorStart
tokenVectorEnd
tokenMapStart
tokenMapEnd
tokenSetStart
tokenDiscard
tokenError
)
func (t tokenType) String() string {
switch t {
case tokenSymbol:
return "symbol"
case tokenKeyword:
return "keyword"
case tokenString:
return "string"
case tokenInt:
return "integer"
case tokenFloat:
return "float"
case tokenTag:
return "tag"
case tokenChar:
return "character"
case tokenListStart:
return "list start"
case tokenListEnd:
return "list end"
case tokenVectorStart:
return "vector start"
case tokenVectorEnd:
return "vector end"
case tokenMapStart:
return "map start"
case tokenMapEnd:
return "map/set end"
case tokenSetStart:
return "set start"
case tokenDiscard:
return "discard token"
case tokenError:
return "error"
default:
return "[unknown]"
}
}
const tokenSetEnd = tokenMapEnd // sets ends the same way as maps do
// A SyntaxError is a description of an EDN syntax error.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string {
return e.msg
}
func okSymbolFirst(r rune) bool {
switch r {
case '.', '*', '+', '!', '-', '_', '?', '$', '%', '&', '=', '<', '>':
return true
}
return false
}
func okSymbol(r rune) bool {
switch r {
case '.', '*', '+', '!', '-', '_', '?', '$', '%', '&', '=', '<', '>', ':', '#', '\'':
return true
}
return false
}
func isWhitespace(r rune) bool {
return u.IsSpace(r) || r == ','
}
type lexer struct {
state func(rune) lexState
err error
position int64
token tokenType
count int // counter is used in some functions within the lexer
expecting []rune // expecting is used to avoid duplication when we expect e.g. \newline
}
func (l *lexer) reset() {
l.state = l.stateBegin
l.token = tokenType(-1)
l.err = nil
}
func (l *lexer) eof() lexState {
if l.err != nil {
return lexError
}
lt := l.state(' ')
if lt == lexCont {
l.err = &SyntaxError{"unexpected end of EDN input", l.position}
lt = lexError
}
if l.err != nil {
return lexError
}
if lt == lexEndPrev {
return lexEnd
}
return lt
}
func (l *lexer) stateBegin(r rune) lexState {
switch {
case isWhitespace(r):
return lexIgnore
case r == '{':
l.token = tokenMapStart
return lexEnd
case r == '}':
l.token = tokenMapEnd
return lexEnd
case r == '[':
l.token = tokenVectorStart
return lexEnd
case r == ']':
l.token = tokenVectorEnd
return lexEnd
case r == '(':
l.token = tokenListStart
return lexEnd
case r == ')':
l.token = tokenListEnd
return lexEnd
case r == '#':
l.state = l.statePound
return lexCont
case r == ':':
l.state = l.stateKeyword
return lexCont
case r == '/': // ohh, the lovely slash edge case
l.token = tokenSymbol
l.state = l.stateEndLit
return lexCont
case r == '+':
l.state = l.statePos
return lexCont
case r == '-':
l.state = l.stateNeg
return lexCont
case r == '.':
l.token = tokenSymbol
l.state = l.stateDotPre
return lexCont
case r == '"':
l.state = l.stateInString
return lexCont
case r == '\\':
l.state = l.stateChar
return lexCont
case okSymbolFirst(r) || u.IsLetter(r):
l.token = tokenSymbol
l.state = l.stateSym
return lexCont
case '0' < r && r <= '9':
l.state = l.state1
return lexCont
case r == '0':
l.state = l.state0
return lexCont
case r == ';':
l.state = l.stateComment
return lexIgnore
}
return l.error(r, "- unexpected rune")
}
func (l *lexer) stateComment(r rune) lexState {
if r == '\n' {
l.state = l.stateBegin
}
return lexIgnore
}
func (l *lexer) stateEndLit(r rune) lexState {
if isWhitespace(r) || r == '"' || r == '{' || r == '[' || r == '(' || r == ')' || r == ']' || r == '}' || r == '\\' || r == ';' {
return lexEndPrev
}
return l.error(r, "- unexpected rune after legal "+l.token.String())
}
func (l *lexer) stateKeyword(r rune) lexState {
switch {
case r == ':':
l.state = l.stateError
l.err = &SyntaxError{"EDN does not support namespace-qualified keywords", l.position}
return lexError
case r == '/':
l.state = l.stateError
l.err = &SyntaxError{"keywords cannot begin with /", l.position}
return lexError
case okSymbol(r) || u.IsLetter(r) || ('0' <= r && r <= '9'):
l.token = tokenKeyword
l.state = l.stateSym
return lexCont
}
return l.error(r, "after keyword start")
}
// examples: 'foo' 'bar'
// we reuse this from the keyword states, so we don't set token at the end,
// but before we call this
func (l *lexer) stateSym(r rune) lexState {
switch {
case okSymbol(r) || u.IsLetter(r) || ('0' <= r && r <= '9'):
l.state = l.stateSym
return lexCont
case r == '/':
l.state = l.stateSlash
return lexCont
}
return l.stateEndLit(r)
}
// example: 'foo/'
func (l *lexer) stateSlash(r rune) lexState {
switch {
case okSymbol(r) || u.IsLetter(r) || ('0' <= r && r <= '9'):
l.state = l.statePostSlash
return lexCont
}
return l.error(r, "directly after '/' in namespaced symbol")
}
// example : 'foo/bar'
func (l *lexer) statePostSlash(r rune) lexState {
switch {
case okSymbol(r) || u.IsLetter(r) || ('0' <= r && r <= '9'):
l.state = l.statePostSlash
return lexCont
}
return l.stateEndLit(r)
}
// example: '-'
func (l *lexer) stateNeg(r rune) lexState {
switch {
case r == '0':
l.state = l.state0
return lexCont
case '1' <= r && r <= '9':
l.state = l.state1
return lexCont
case okSymbol(r) || u.IsLetter(r):
l.token = tokenSymbol
l.state = l.stateSym
return lexCont
case r == '/':
l.token = tokenSymbol
l.state = l.stateSlash
return lexCont
}
l.token = tokenSymbol
return l.stateEndLit(r)
}
// example: '+'
func (l *lexer) statePos(r rune) lexState {
switch {
case r == '0':
l.state = l.state0
return lexCont
case '1' <= r && r <= '9':
l.state = l.state1
return lexCont
case okSymbol(r) || u.IsLetter(r):
l.token = tokenSymbol
l.state = l.stateSym
return lexCont
case r == '/':
l.token = tokenSymbol
l.state = l.stateSlash
return lexCont
}
l.token = tokenSymbol
return l.stateEndLit(r)
}
// value is '0'
func (l *lexer) state0(r rune) lexState {
switch {
case r == '.':
l.state = l.stateDot
return lexCont
case r == 'e' || r == 'E':
l.state = l.stateE
return lexCont
case r == 'M': // bigdecimal
l.token = tokenFloat
l.state = l.stateEndLit
return lexCont // must be ws or delimiter afterwards
case r == 'N': // bigint
l.token = tokenInt
l.state = l.stateEndLit
return lexCont // must be ws or delimiter afterwards
}
l.token = tokenInt
return l.stateEndLit(r)
}
// anything but a result starting with 0. example '10', '34'
func (l *lexer) state1(r rune) lexState {
if '0' <= r && r <= '9' {
return lexCont
}
return l.state0(r)
}
// example: '.', can only receive non-numerics here
func (l *lexer) stateDotPre(r rune) lexState {
switch {
case okSymbol(r) || u.IsLetter(r):
l.token = tokenSymbol
l.state = l.stateSym
return lexCont
case r == '/':
l.token = tokenSymbol
l.state = l.stateSlash
return lexCont
}
return l.stateEndLit(r)
}
// after reading numeric values plus '.', example: '12.'
func (l *lexer) stateDot(r rune) lexState {
if '0' <= r && r <= '9' {
l.state = l.stateDot0
return lexCont
}
// TODO (?): The spec says that there must be numbers after the dot, yet
// (clojure.edn/read-string "1.e1") returns 10.0
return l.error(r, "after decimal point in numeric literal")
}
// after reading numeric values plus '.', example: '12.34'
func (l *lexer) stateDot0(r rune) lexState {
switch {
case '0' <= r && r <= '9':
return lexCont
case r == 'e' || r == 'E':
l.state = l.stateE
return lexCont
case r == 'M':
l.token = tokenFloat
l.state = l.stateEndLit
return lexCont
}
l.token = tokenFloat
return l.stateEndLit(r)
}
// stateE is the state after reading the mantissa and e in a number,
// such as after reading `314e` or `0.314e`.
func (l *lexer) stateE(r rune) lexState {
if r == '+' || r == '-' {
l.state = l.stateESign
return lexCont
}
return l.stateESign(r)
}
// stateESign is the state after reading the mantissa, e, and sign in a number,
// such as after reading `314e-` or `0.314e+`.
func (l *lexer) stateESign(r rune) lexState {
if '0' <= r && r <= '9' {
l.state = l.stateE0
return lexCont
}
return l.error(r, "in exponent of numeric literal")
}
// stateE0 is the state after reading the mantissa, e, optional sign,
// and at least one digit of the exponent in a number,
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
func (l *lexer) stateE0(r rune) lexState {
if '0' <= r && r <= '9' {
return lexCont
}
if r == 'M' {
l.token = tokenFloat
l.state = l.stateEndLit
return lexCont
}
l.token = tokenFloat
return l.stateEndLit(r)
}
var (
newlineRunes = []rune("newline")
returnRunes = []rune("return")
spaceRunes = []rune("space")
tabRunes = []rune("tab")
formfeedRunes = []rune("formfeed")
)
// stateChar after a backslash ('\')
func (l *lexer) stateChar(r rune) lexState {
switch {
// oh my, I'm so happy that none of these share the same prefix.
case r == 'n':
l.count = 1
l.expecting = newlineRunes
l.state = l.stateSpecialChar
return lexCont
case r == 'r':
l.count = 1
l.expecting = returnRunes
l.state = l.stateSpecialChar
return lexCont
case r == 's':
l.count = 1
l.expecting = spaceRunes
l.state = l.stateSpecialChar
return lexCont
case r == 't':
l.count = 1
l.expecting = tabRunes
l.state = l.stateSpecialChar
return lexCont
case r == 'f':
l.count = 1
l.expecting = formfeedRunes
l.state = l.stateSpecialChar
return lexCont
case r == 'u':
l.count = 0
l.state = l.stateUnicodeChar
return lexCont
case isWhitespace(r):
l.state = l.stateError
l.err = &SyntaxError{"backslash cannot be followed by whitespace", l.position}
return lexError
}
// default is single name character
l.token = tokenChar
l.state = l.stateEndLit
return lexCont
}
func (l *lexer) stateSpecialChar(r rune) lexState {
if r == l.expecting[l.count] {
l.count++
if l.count == len(l.expecting) {
l.token = tokenChar
l.state = l.stateEndLit
return lexCont
}
return lexCont
}
if l.count != 1 {
return l.error(r, "after start of special character")
}
// it is likely just a normal character, like 'n' or 't'
l.token = tokenChar
return l.stateEndLit(r)
}
func (l *lexer) stateUnicodeChar(r rune) lexState {
if '0' <= r && r <= '9' || 'a' <= r && r <= 'f' || 'A' <= r && r <= 'F' {
l.count++
if l.count == 4 {
l.token = tokenChar
l.state = l.stateEndLit
}
return lexCont
}
if l.count != 0 {
return l.error(r, "after start of unicode character")
}
// likely just '\u'
l.token = tokenChar
return l.stateEndLit(r)
}
// stateInString is the state after reading `"`.
func (l *lexer) stateInString(r rune) lexState {
if r == '"' {
l.token = tokenString
return lexEnd
}
if r == '\\' {
l.state = l.stateInStringEsc
return lexCont
}
return lexCont
}
// stateInStringEsc is the state after reading `"\` during a quoted string.
func (l *lexer) stateInStringEsc(r rune) lexState {
switch r {
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
l.state = l.stateInString
return lexCont
case 'u':
l.state = l.stateInStringEscU
l.count = 0
return lexCont
}
return l.error(r, "in string escape code")
}
// stateInStringEscU is the state after reading `"\u` and l.count elements in a
// quoted string.
func (l *lexer) stateInStringEscU(r rune) lexState {
if '0' <= r && r <= '9' || 'a' <= r && r <= 'f' || 'A' <= r && r <= 'F' {
l.count++
if l.count == 4 {
l.state = l.stateInString
}
return lexCont
}
// numbers
return l.error(r, "in \\u hexadecimal character escape")
}
// after reading the character '#'
func (l *lexer) statePound(r rune) lexState {
switch {
case r == '_':
l.token = tokenDiscard
return lexEnd
case r == '{':
l.token = tokenSetStart
return lexEnd
case u.IsLetter(r):
l.token = tokenTag
l.state = l.stateSym
return lexCont
}
return l.error(r, `after token starting with "#"`)
}
func (l *lexer) stateError(r rune) lexState {
return lexError
}
// error records an error and switches to the error state.
func (l *lexer) error(r rune, context string) lexState {
l.state = l.stateError
l.err = &SyntaxError{"invalid character " + quoteRune(r) + " " + context, l.position}
return lexError
}
// quoteRune formats r as a quoted rune literal
func quoteRune(r rune) string {
// special cases - different from quoted strings
if r == '\'' {
return `'\''`
}
if r == '"' {
return `'"'`
}
// use quoted string with different quotation marks
s := strconv.Quote(string(r))
return "'" + s[1:len(s)-1] + "'"
}

245
vendor/olympos.io/encoding/edn/pprint.go generated vendored Normal file
View File

@ -0,0 +1,245 @@
// Copyright 2015 Jean Niklas L'orange. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edn
import (
"bytes"
"io"
"unicode/utf8"
)
var (
// we can't call it spaceBytes not to conflict with decode.go's spaceBytes.
spaceOutputBytes = []byte(" ")
commaOutputBytes = []byte(",")
)
func newline(dst io.Writer, prefix, indent string, depth int) {
dst.Write([]byte{'\n'})
dst.Write([]byte(prefix))
for i := 0; i < depth; i++ {
dst.Write([]byte(indent))
}
}
// Indent writes to dst an indented form of the EDN-encoded src. Each EDN
// collection begins on a new, indented line beginning with prefix followed by
// one or more copies of indent according to the indentation nesting. The data
// written to dst does not begin with the prefix nor any indentation, and has
// no trailing newline, to make it easier to embed inside other formatted EDN
// data.
//
// Indent filters away whitespace, including comments and discards.
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
origLen := dst.Len()
err := IndentStream(dst, bytes.NewBuffer(src), prefix, indent)
if err != nil {
dst.Truncate(origLen)
}
return err
}
// IndentStream is an implementation of PPrint for generic readers and writers
func IndentStream(dst io.Writer, src io.Reader, prefix, indent string) error {
var lex lexer
lex.reset()
tokStack := newTokenStack()
curType := tokenError
curSize := 0
d := NewDecoder(src)
depth := 0
for {
bs, tt, err := d.nextToken()
if err != nil {
return err
}
err = tokStack.push(tt)
if err != nil {
return err
}
prevType := curType
prevSize := curSize
if len(tokStack.toks) > 0 {
curType = tokStack.peek()
curSize = tokStack.peekCount()
}
switch tt {
case tokenMapStart, tokenVectorStart, tokenListStart, tokenSetStart:
if prevType == tokenMapStart {
dst.Write([]byte{' '})
} else if depth > 0 {
newline(dst, prefix, indent, depth)
}
dst.Write(bs)
depth++
case tokenVectorEnd, tokenListEnd, tokenMapEnd: // tokenSetEnd == tokenMapEnd
depth--
if prevSize > 0 { // suppress indent for empty collections
newline(dst, prefix, indent, depth)
}
// all of these are of length 1 in bytes, so utilise this for perf
dst.Write(bs)
case tokenTag:
// need to know what the previous type was.
switch prevType {
case tokenMapStart:
if prevSize%2 == 0 { // If previous size modulo 2 is equal to 0, we're a key
if prevSize > 0 {
dst.Write(commaOutputBytes)
}
newline(dst, prefix, indent, depth)
} else { // We're a value, add a space after the key
dst.Write(spaceOutputBytes)
}
dst.Write(bs)
dst.Write(spaceOutputBytes)
case tokenSetStart, tokenVectorStart, tokenListStart:
newline(dst, prefix, indent, depth)
dst.Write(bs)
dst.Write(spaceOutputBytes)
default: // tokenError or nested tag
dst.Write(bs)
dst.Write(spaceOutputBytes)
}
default:
switch prevType {
case tokenMapStart:
if prevSize%2 == 0 { // If previous size modulo 2 is equal to 0, we're a key
if prevSize > 0 {
dst.Write(commaOutputBytes)
}
newline(dst, prefix, indent, depth)
} else { // We're a value, add a space after the key
dst.Write(spaceOutputBytes)
}
dst.Write(bs)
case tokenSetStart, tokenVectorStart, tokenListStart:
newline(dst, prefix, indent, depth)
dst.Write(bs)
default: // toplevel or nested tag. This should collapse the whole tag tower
dst.Write(bs)
}
}
if tokStack.done() {
break
}
}
return nil
}
// PPrintOpts is a configuration map for PPrint. The values in this struct has
// no effect as of now.
type PPrintOpts struct {
RightMargin int
MiserWidth int
}
func pprintIndent(dst io.Writer, shift int) {
spaces := make([]byte, shift+1)
spaces[0] = '\n'
// TODO: This may be slower than caching the size as a byte slice
for i := 1; i <= shift; i++ {
spaces[i] = ' '
}
dst.Write(spaces)
}
// PPrint writes to dst an indented form of the EDN-encoded src. This
// implementation attempts to write idiomatic/readable EDN values, in a fashion
// close to (but not quite equal to) clojure.pprint/pprint.
//
// PPrint filters away whitespace, including comments and discards.
func PPrint(dst *bytes.Buffer, src []byte, opt *PPrintOpts) error {
origLen := dst.Len()
err := PPrintStream(dst, bytes.NewBuffer(src), opt)
if err != nil {
dst.Truncate(origLen)
}
return err
}
// PPrintStream is an implementation of PPrint for generic readers and writers
func PPrintStream(dst io.Writer, src io.Reader, opt *PPrintOpts) error {
var lex lexer
var col, prevCollStart, curSize int
var prevColl bool
lex.reset()
tokStack := newTokenStack()
shift := make([]int, 1, 8) // pre-allocate some space
curType := tokenError
d := NewDecoder(src)
for {
bs, tt, err := d.nextToken()
if err != nil {
return err
}
err = tokStack.push(tt)
if err != nil {
return err
}
prevType := curType
prevSize := curSize
if len(tokStack.toks) > 0 {
curType = tokStack.peek()
curSize = tokStack.peekCount()
}
// Indentation
switch tt {
case tokenVectorEnd, tokenListEnd, tokenMapEnd:
default:
switch prevType {
case tokenMapStart:
if prevSize%2 == 0 && prevSize > 0 {
dst.Write(commaOutputBytes)
pprintIndent(dst, shift[len(shift)-1])
col = shift[len(shift)-1]
} else if prevSize%2 == 1 { // We're a value, add a space after the key
dst.Write(spaceOutputBytes)
col++
}
case tokenSetStart, tokenVectorStart, tokenListStart:
if prevColl {
// begin on new line where prevColl started
// This will look so strange for heterogenous maps.
pprintIndent(dst, prevCollStart)
col = prevCollStart
} else if prevSize > 0 {
dst.Write(spaceOutputBytes)
col++
}
}
}
switch tt {
case tokenMapStart, tokenVectorStart, tokenListStart, tokenSetStart:
dst.Write(bs)
col += len(bs) // either 2 or 1
shift = append(shift, col) // we only use maps for now, but we'll utilise this more thoroughly later on
case tokenVectorEnd, tokenListEnd, tokenMapEnd: // tokenSetEnd == tokenMapEnd
dst.Write(bs) // all of these are of length 1 in bytes, so this is ok
prevCollStart = shift[len(shift)-1] - 1
shift = shift[:len(shift)-1]
case tokenTag:
bslen := utf8.RuneCount(bs)
dst.Write(bs)
dst.Write(spaceOutputBytes)
col += bslen + 1
default:
bslen := utf8.RuneCount(bs)
dst.Write(bs)
col += bslen
}
prevColl = (tt == tokenMapEnd || tt == tokenVectorEnd || tt == tokenListEnd)
if tokStack.done() {
break
}
}
return nil
}

44
vendor/olympos.io/encoding/edn/tags.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edn
import (
"strings"
)
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}

149
vendor/olympos.io/encoding/edn/types.go generated vendored Normal file
View File

@ -0,0 +1,149 @@
// Copyright 2015-2017 Jean Niklas L'orange. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edn
import (
"bufio"
"bytes"
"errors"
"fmt"
)
// RawMessage is a raw encoded, but valid, EDN value. It implements Marshaler
// and Unmarshaler and can be used to delay EDN decoding or precompute an EDN
// encoding.
type RawMessage []byte
// MarshalEDN returns m as the EDN encoding of m.
func (m RawMessage) MarshalEDN() ([]byte, error) {
if m == nil {
return []byte("nil"), nil
}
return m, nil
}
// UnmarshalEDN sets *m to a copy of data.
func (m *RawMessage) UnmarshalEDN(data []byte) error {
if m == nil {
return errors.New("edn.RawMessage: UnmarshalEDN on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
// A Keyword is an EDN keyword without : prepended in front.
type Keyword string
func (k Keyword) String() string {
return fmt.Sprintf(":%s", string(k))
}
func (k Keyword) MarshalEDN() ([]byte, error) {
return []byte(k.String()), nil
}
// A Symbol is an EDN symbol.
type Symbol string
func (s Symbol) String() string {
return string(s)
}
func (s Symbol) MarshalEDN() ([]byte, error) {
return []byte(s), nil
}
// A Tag is a tagged value. The Tagname represents the name of the tag, and the
// Value is the value of the element.
type Tag struct {
Tagname string
Value interface{}
}
func (t Tag) String() string {
return fmt.Sprintf("#%s %v", t.Tagname, t.Value)
}
func (t Tag) MarshalEDN() ([]byte, error) {
str := []byte(fmt.Sprintf(`#%s `, t.Tagname))
b, err := Marshal(t.Value)
if err != nil {
return nil, err
}
return append(str, b...), nil
}
func (t *Tag) UnmarshalEDN(bs []byte) error {
// read actual tag, using the lexer.
var lex lexer
lex.reset()
buf := bufio.NewReader(bytes.NewBuffer(bs))
start := 0
endTag := 0
tag:
for {
r, rlen, err := buf.ReadRune()
if err != nil {
return err
}
ls := lex.state(r)
switch ls {
case lexIgnore:
start += rlen
endTag += rlen
case lexError:
return lex.err
case lexEndPrev:
break tag
case lexEnd: // unexpected, assuming tag which is not ending with lexEnd
return errUnexpected
case lexCont:
endTag += rlen
}
}
t.Tagname = string(bs[start+1 : endTag])
return Unmarshal(bs[endTag:], &t.Value)
}
// A Rune type is a wrapper for a rune. It can be used to encode runes as
// characters instead of int32 values.
type Rune rune
func (r Rune) MarshalEDN() ([]byte, error) {
buf := bytes.NewBuffer(make([]byte, 0, 10))
encodeRune(buf, rune(r))
return buf.Bytes(), nil
}
func encodeRune(buf *bytes.Buffer, r rune) {
const hex = "0123456789abcdef"
if !isWhitespace(r) {
buf.WriteByte('\\')
buf.WriteRune(r)
} else {
switch r {
case '\b':
buf.WriteString(`\backspace`)
case '\f':
buf.WriteString(`\formfeed`)
case '\n':
buf.WriteString(`\newline`)
case '\r':
buf.WriteString(`\return`)
case '\t':
buf.WriteString(`\tab`)
case ' ':
buf.WriteString(`\space`)
default:
buf.WriteByte('\\')
buf.WriteByte('u')
buf.WriteByte(hex[r>>12&0xF])
buf.WriteByte(hex[r>>8&0xF])
buf.WriteByte(hex[r>>4&0xF])
buf.WriteByte(hex[r&0xF])
}
}
}