OSDN Git Service

add accesstoken
authorzhouyasong <zhouarrogant@outlook.com>
Tue, 19 Sep 2017 10:16:46 +0000 (18:16 +0800)
committerzhouyasong <zhouarrogant@outlook.com>
Tue, 19 Sep 2017 10:16:46 +0000 (18:16 +0800)
blockchain/accesstoken/accesstoken.go [new file with mode: 0644]
blockchain/accesstokens.go [new file with mode: 0644]
blockchain/reactor.go
cmd/bytomcli/main.go

diff --git a/blockchain/accesstoken/accesstoken.go b/blockchain/accesstoken/accesstoken.go
new file mode 100644 (file)
index 0000000..8039423
--- /dev/null
@@ -0,0 +1,175 @@
+// Package accesstoken provides storage and validation of Chain Core
+// credentials.
+package accesstoken
+
+// TODO(tessr): merge this package into chain/net/http/authn
+
+import (
+       "context"
+       "crypto/rand"
+       //      "database/sql"
+       "fmt"
+       "regexp"
+       "time"
+
+       "github.com/bytom/crypto/sha3pool"
+       //      "chain/database/pg"
+       "github.com/bytom/errors"
+)
+
+const (
+       tokenSize    = 32
+       defaultLimit = 100
+)
+
+var (
+       // ErrBadID is returned when Create is called on an invalid id string.
+       ErrBadID = errors.New("invalid id")
+       // ErrDuplicateID is returned when Create is called on an existing ID.
+       ErrDuplicateID = errors.New("duplicate access token ID")
+       // ErrBadType is returned when Create is called with a bad type.
+       ErrBadType = errors.New("type must be client or network")
+
+       // validIDRegexp checks that all characters are alphumeric, _ or -.
+       // It also must have a length of at least 1.
+       validIDRegexp = regexp.MustCompile(`^[\w-]+$`)
+)
+
+type Token struct {
+       ID      string    `json:"id"`
+       Token   string    `json:"token,omitempty"`
+       Type    string    `json:"type,omitempty"` // deprecated in 1.2
+       Created time.Time `json:"created_at"`
+       sortID  string
+}
+
+type CredentialStore struct {
+       //      DB pg.DB
+}
+
+// Create generates a new access token with the given ID.
+func (cs *Token) Create(ctx context.Context, id, typ string) (*Token, error) {
+       if !validIDRegexp.MatchString(id) {
+               return nil, errors.WithDetailf(ErrBadID, "invalid id %q", id)
+       }
+
+       var secret [tokenSize]byte
+       _, err := rand.Read(secret[:])
+       if err != nil {
+               return nil, err
+       }
+       var hashedSecret [32]byte
+       sha3pool.Sum256(hashedSecret[:], secret[:])
+
+       /*      const q = `
+                       INSERT INTO access_tokens (id, type, hashed_secret)
+                       VALUES($1, $2, $3)
+                       RETURNING created, sort_id
+               `
+       */var (
+               created time.Time
+               sortID  string
+       //      maybeType = sql.NullString{String: typ, Valid: typ != ""}
+       )
+       /*      err = cs.DB.QueryRowContext(ctx, q, id, maybeType, hashedSecret[:]).Scan(&created, &sortID)
+               if pg.IsUniqueViolation(err) {
+                       return nil, errors.WithDetailf(ErrDuplicateID, "id %q already in use", id)
+               }
+               if err != nil {
+                       return nil, errors.Wrap(err)
+               }
+       */
+       return &Token{
+               ID:      id,
+               Token:   fmt.Sprintf("%s:%x", id, secret),
+               Type:    typ,
+               Created: created,
+               sortID:  sortID,
+       }, nil
+}
+
+// Check returns whether or not an id-secret pair is a valid access token.
+func (cs *Token) Check(ctx context.Context, id string, secret []byte) (bool, error) {
+       var (
+               toHash [tokenSize]byte
+               hashed [32]byte
+       )
+       copy(toHash[:], secret)
+       sha3pool.Sum256(hashed[:], toHash[:])
+
+       /*const q = `SELECT EXISTS(SELECT 1 FROM access_tokens WHERE id=$1 AND hashed_secret=$2)`
+       var valid bool
+       err := cs.DB.QueryRowContext(ctx, q, id, hashed[:]).Scan(&valid)
+       if err != nil {
+               return false, err
+       }
+       */
+       //      return valid, nil
+       return true, nil
+}
+
+// Exists returns whether an id is part of a valid access token. It does not validate a secret.
+func (cs *Token) Exists(ctx context.Context, id string) bool {
+       /*      const q = `SELECT EXISTS(SELECT 1 FROM access_tokens WHERE id=$1)`
+               var valid bool
+               err := cs.DB.QueryRowContext(ctx, q, id).Scan(&valid)
+               if err != nil {
+                       return false
+               }
+               return valid
+       */
+       return true
+}
+
+// List lists all access tokens.
+func (cs *Token) List(ctx context.Context, typ, after string, limit int) ([]*Token, string, error) {
+       if limit == 0 {
+               limit = defaultLimit
+       }
+       /*      const q = `
+                               SELECT id, type, sort_id, created FROM access_tokens
+                               WHERE ($1='' OR type=$1::access_token_type) AND ($2='' OR sort_id<$2)
+                               ORDER BY sort_id DESC
+                               LIMIT $3
+                       `
+               var tokens []*Token
+               //      err := pg.ForQueryRows(ctx, cs.DB, q, typ, after, limit, func(id string, maybeType sql.NullString, sortID string, created time.Time) {
+               t := Token{
+                       ID:      id,
+                       Created: created,
+                       Type:    maybeType.String,
+                       sortID:  sortID,
+               }
+               tokens = append(tokens, &t)
+                       })
+               if err != nil {
+                       return nil, "", errors.Wrap(err)
+               }
+       */
+       var tokens []*Token
+       var next string
+       if len(tokens) > 0 {
+               next = tokens[len(tokens)-1].sortID
+       }
+
+       return tokens, next, nil
+}
+
+// Delete deletes an access token by id.
+func (cs *Token) Delete(ctx context.Context, id string) error {
+       /*      const q = `DELETE FROM access_tokens WHERE id=$1`
+               res, err := cs.DB.ExecContext(ctx, q, id)
+               if err != nil {
+                       return errors.Wrap(err)
+               }
+       */
+       /*      deleted, err := res.RowsAffected()
+               if err != nil {
+                       return errors.Wrap(err)
+               }
+
+               if deleted == 0 {
+                       return errors.WithDetailf(pg.ErrUserInputNotFound, "acccess token id %s", id)
+               }
+       */return nil
+}
diff --git a/blockchain/accesstokens.go b/blockchain/accesstokens.go
new file mode 100644 (file)
index 0000000..733158e
--- /dev/null
@@ -0,0 +1,116 @@
+package blockchain
+
+import (
+       "context"
+       "encoding/json"
+
+       "github.com/bytom/blockchain/accesstoken"
+       "github.com/bytom/errors"
+       "github.com/bytom/log"
+       //      "github.com/bytom/net/http/authz"
+       "github.com/bytom/net/http/httpjson"
+)
+
+/*
+const (
+       defGenericPageSize = 100
+)
+*/
+var errCurrentToken = errors.New("token cannot delete itself")
+
+func (bcr *BlockchainReactor) createAccessToken(ctx context.Context, x struct{ ID, Type string }) (*accesstoken.Token, error) {
+       token, err := bcr.accesstoken.Create(ctx, x.ID, x.Type)
+       if err != nil {
+               return nil, errors.Wrap(err)
+       }
+
+       log.Printf(ctx, "-------createAccessToken-------")
+
+       if x.Type == "" {
+               return token, nil
+       }
+
+       data := map[string]interface{}{
+               "id": token.ID,
+       }
+       _, err = json.Marshal(data)
+       //      guardData, err := json.Marshal(data)
+       if err != nil {
+               return nil, errors.Wrap(err)
+       }
+       /*
+               var grant *authz.Grant
+
+               // Type is deprecated; however, for backward compatibility, using the
+               // Type field will create a grant associated with this new token.
+               switch x.Type {
+               case "client":
+                       grant = &authz.Grant{
+                               GuardType: "access_token",
+                               GuardData: guardData,
+                               Policy:    "client-readwrite",
+                       }
+               case "network":
+                       grant = &authz.Grant{
+                               GuardType: "access_token",
+                               GuardData: guardData,
+                               Policy:    "crosscore",
+                       }
+               default:
+                       // We've already returned if x.Type wasn't specified, so this must be a bad type.
+                       return nil, accesstoken.ErrBadType
+               }
+               err = a.sdb.Exec(ctx, a.grants.Save(ctx, grant))
+               if err != nil {
+                       return nil, errors.Wrap(err)
+               }
+       */
+       token.Type = x.Type // deprecated
+
+       return token, nil
+}
+
+func (bcr *BlockchainReactor) listAccessTokens(ctx context.Context, x requestQuery) (*page, error) {
+       limit := x.PageSize
+       if limit == 0 {
+               limit = 100
+       }
+
+       tokens, next, err := bcr.accesstoken.List(ctx, x.Type, x.After, limit)
+       if err != nil {
+               return nil, err
+       }
+
+       log.Printf(ctx, "--------listAccessTokens-------")
+
+       outQuery := x
+       outQuery.After = next
+
+       return &page{
+               Items:    httpjson.Array(tokens),
+               LastPage: len(tokens) < limit,
+               Next:     outQuery,
+       }, nil
+}
+
+func (bcr *BlockchainReactor) deleteAccessToken(ctx context.Context, x struct{ ID string }) error {
+       currentID, _, _ := httpjson.Request(ctx).BasicAuth()
+       if currentID == x.ID {
+               return errCurrentToken
+       }
+       err := bcr.accesstoken.Delete(ctx, x.ID)
+       if err != nil {
+               return err
+       }
+
+       log.Printf(ctx, "-------deleteAccessToken-------")
+
+       /*      err = a.sdb.Exec(ctx, a.deleteGrantsByAccessToken(x.ID))
+               if err != nil {
+                       // well, technically we did delete the access token, so don't return the error
+                       // TODO(tessr): make this whole operation atomic, such that we either delete
+                       // both the access token and its grants, or we return a failure.
+                       log.Printkv(ctx, log.KeyError, err, "at", "revoking grants for access token", "token", x.ID)
+               }
+       */return nil
+}
index a47ddca..54a7954 100644 (file)
@@ -3,31 +3,31 @@ package blockchain
 import (
        "bytes"
        "context"
-       "reflect"
-    "time"
-       "net/http"
        "fmt"
+       "net/http"
+       "reflect"
+       "time"
 
-       wire "github.com/tendermint/go-wire"
-       "github.com/bytom/p2p"
-       "github.com/bytom/types"
-    "github.com/bytom/protocol/bc/legacy"
-    "github.com/bytom/protocol"
-       "github.com/bytom/blockchain/query"
-       "github.com/bytom/encoding/json"
-       cmn "github.com/tendermint/tmlibs/common"
-       "github.com/bytom/blockchain/txdb"
+       "github.com/bytom/blockchain/accesstoken"
        "github.com/bytom/blockchain/account"
        "github.com/bytom/blockchain/asset"
+       "github.com/bytom/blockchain/txdb"
        "github.com/bytom/blockchain/txfeed"
+       "github.com/bytom/encoding/json"
        "github.com/bytom/log"
+       "github.com/bytom/p2p"
+       "github.com/bytom/protocol"
+       "github.com/bytom/protocol/bc/legacy"
+       "github.com/bytom/types"
+       wire "github.com/tendermint/go-wire"
+       cmn "github.com/tendermint/tmlibs/common"
        //"github.com/bytom/net/http/gzip"
        "github.com/bytom/net/http/httpjson"
        //"github.com/bytom/net/http/limit"
-       "github.com/bytom/net/http/static"
-       "github.com/bytom/generated/dashboard"
-       "github.com/bytom/errors"
        "github.com/bytom/blockchain/txbuilder"
+       "github.com/bytom/errors"
+       "github.com/bytom/generated/dashboard"
+       "github.com/bytom/net/http/static"
 )
 
 const (
@@ -46,26 +46,26 @@ const (
        // check if we should switch to consensus reactor
        switchToConsensusIntervalSeconds = 1
        maxBlockchainResponseSize        = 22020096 + 2
-       crosscoreRPCPrefix = "/rpc/"
+       crosscoreRPCPrefix               = "/rpc/"
 )
 
 // BlockchainReactor handles long-term catchup syncing.
 type BlockchainReactor struct {
        p2p.BaseReactor
 
-       chain        *protocol.Chain
-       store        *txdb.Store
-       accounts         *account.Manager
-       assets       *asset.Registry
-       txFeeds          *txfeed.TxFeed
-       indexer         *query.Indexer
-       pool         *BlockPool
-       mux          *http.ServeMux
-       handler      http.Handler
-       fastSync     bool
-       requestsCh   chan BlockRequest
-       timeoutsCh   chan string
-       submitter    txbuilder.Submitter
+       chain       *protocol.Chain
+       store       *txdb.Store
+       accounts    *account.Manager
+       assets      *asset.Registry
+       txFeeds     *txfeed.TxFeed
+       pool        *BlockPool
+       mux         *http.ServeMux
+       accesstoken *accesstoken.Token
+       handler     http.Handler
+       fastSync    bool
+       requestsCh  chan BlockRequest
+       timeoutsCh  chan string
+       submitter   txbuilder.Submitter
 
        evsw types.EventSwitch
 }
@@ -94,7 +94,7 @@ func batchRecover(ctx context.Context, v *interface{}) {
 }
 
 func jsonHandler(f interface{}) http.Handler {
-    h, err := httpjson.Handler(f, errorFormatter.Write)
+       h, err := httpjson.Handler(f, errorFormatter.Write)
        if err != nil {
                panic(err)
        }
@@ -106,12 +106,12 @@ func alwaysError(err error) http.Handler {
 }
 
 func (bcr *BlockchainReactor) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
-    bcr.handler.ServeHTTP(rw, req)
+       bcr.handler.ServeHTTP(rw, req)
 }
 
 func (bcr *BlockchainReactor) info(ctx context.Context) (map[string]interface{}, error) {
-    //if a.config == nil {
-               // never configured
+       //if a.config == nil {
+       // never configured
        log.Printf(ctx, "-------info-----")
        return map[string]interface{}{
                "is_configured": false,
@@ -124,7 +124,7 @@ func (bcr *BlockchainReactor) info(ctx context.Context) (map[string]interface{},
 }
 
 func (bcr *BlockchainReactor) createblockkey(ctx context.Context) {
-       log.Printf(ctx,"creat-block-key")
+       log.Printf(ctx, "creat-block-key")
 }
 
 func webAssetsHandler(next http.Handler) http.Handler {
@@ -138,7 +138,7 @@ func webAssetsHandler(next http.Handler) http.Handler {
 }
 
 func maxBytes(h http.Handler) http.Handler {
-    const maxReqSize = 1e7 // 10MB
+       const maxReqSize = 1e7 // 10MB
        return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
                // A block can easily be bigger than maxReqSize, but everything
                // else should be pretty small.
@@ -153,44 +153,41 @@ func (bcr *BlockchainReactor) BuildHander() {
        m := bcr.mux
        m.Handle("/create-account", jsonHandler(bcr.createAccount))
        m.Handle("/create-asset", jsonHandler(bcr.createAsset))
-       m.Handle("/update-account-tags",jsonHandler(bcr.updateAccountTags))
-       m.Handle("/update-asset-tags",jsonHandler(bcr.updateAssetTags))
+       m.Handle("/update-account-tags", jsonHandler(bcr.updateAccountTags))
+       m.Handle("/update-asset-tags", jsonHandler(bcr.updateAssetTags))
        m.Handle("/build-transaction", jsonHandler(bcr.build))
-       m.Handle("/create-control-program",jsonHandler(bcr.createControlProgram))
+       m.Handle("/create-control-program", jsonHandler(bcr.createControlProgram))
        m.Handle("/create-account-receiver", jsonHandler(bcr.createAccountReceiver))
        m.Handle("/create-transaction-feed", jsonHandler(bcr.createTxFeed))
        m.Handle("/get-transaction-feed", jsonHandler(bcr.getTxFeed))
        m.Handle("/update-transaction-feed", jsonHandler(bcr.updateTxFeed))
        m.Handle("/delete-transaction-feed", jsonHandler(bcr.deleteTxFeed))
-       m.Handle("/list-accounts", jsonHandler(bcr.listAccounts))
-        m.Handle("/list-assets", jsonHandler(bcr.listAssets))
-        m.Handle("/list-transaction-feeds", jsonHandler(bcr.listTxFeeds))
-        m.Handle("/list-transactions", jsonHandler(bcr.listTransactions))
-        m.Handle("/list-balances", jsonHandler(bcr.listBalances))
-        m.Handle("/list-unspent-outputs", jsonHandler(bcr.listUnspentOutputs))
        m.Handle("/", alwaysError(errors.New("not Found")))
        m.Handle("/info", jsonHandler(bcr.info))
        m.Handle("/create-block-key", jsonHandler(bcr.createblockkey))
        m.Handle("/submit-transaction", jsonHandler(bcr.submit))
+       m.Handle("/create-access-token", jsonHandler(bcr.createAccessToken))
+       m.Handle("/list-access-tokens", jsonHandler(bcr.listAccessTokens))
+       m.Handle("/delete-access-token", jsonHandler(bcr.deleteAccessToken))
 
-    latencyHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+       latencyHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
                if l := latency(m, req); l != nil {
                        defer l.RecordSince(time.Now())
                }
                m.ServeHTTP(w, req)
-               })
+       })
        handler := maxBytes(latencyHandler) // TODO(tessr): consider moving this to non-core specific mux
        handler = webAssetsHandler(handler)
-/*     handler = healthHandler(handler)
-       for _, l := range a.requestLimits {
-               handler = limit.Handler(handler, alwaysError(errRateLimited), l.perSecond, l.burst, l.key)
-       }
-       handler = gzip.Handler{Handler: handler}
-       handler = coreCounter(handler)
-       handler = timeoutContextHandler(handler)
-       if a.config != nil && a.config.BlockchainId != nil {
-               handler = blockchainIDHandler(handler, a.config.BlockchainId.String())
-       }
+       /*      handler = healthHandler(handler)
+               for _, l := range a.requestLimits {
+                       handler = limit.Handler(handler, alwaysError(errRateLimited), l.perSecond, l.burst, l.key)
+               }
+               handler = gzip.Handler{Handler: handler}
+               handler = coreCounter(handler)
+               handler = timeoutContextHandler(handler)
+               if a.config != nil && a.config.BlockchainId != nil {
+                       handler = blockchainIDHandler(handler, a.config.BlockchainId.String())
+               }
        */
        bcr.handler = handler
 }
@@ -236,39 +233,39 @@ type page struct {
 }
 
 func NewBlockchainReactor(store *txdb.Store, chain *protocol.Chain, accounts *account.Manager, assets *asset.Registry, fastSync bool) *BlockchainReactor {
-    requestsCh    := make(chan BlockRequest, defaultChannelCapacity)
-    timeoutsCh    := make(chan string, defaultChannelCapacity)
-    pool := NewBlockPool(
-        store.Height()+1,
-        requestsCh,
-        timeoutsCh,
-    )
-    bcR := &BlockchainReactor {
-        chain:         chain,
-        store:         store,
-               accounts:      accounts,
-               assets:            assets,
-        pool:          pool,
-               mux:           http.NewServeMux(),
-        fastSync:      fastSync,
-        requestsCh:    requestsCh,
-        timeoutsCh:   timeoutsCh,
-    }
-    bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR)
-    return bcR
+       requestsCh := make(chan BlockRequest, defaultChannelCapacity)
+       timeoutsCh := make(chan string, defaultChannelCapacity)
+       pool := NewBlockPool(
+               store.Height()+1,
+               requestsCh,
+               timeoutsCh,
+       )
+       bcR := &BlockchainReactor{
+               chain:      chain,
+               store:      store,
+               accounts:   accounts,
+               assets:     assets,
+               pool:       pool,
+               mux:        http.NewServeMux(),
+               fastSync:   fastSync,
+               requestsCh: requestsCh,
+               timeoutsCh: timeoutsCh,
+       }
+       bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR)
+       return bcR
 }
 
 // OnStart implements BaseService
 func (bcR *BlockchainReactor) OnStart() error {
        bcR.BaseReactor.OnStart()
        bcR.BuildHander()
-    if bcR.fastSync {
-        _, err := bcR.pool.Start()
-        if err != nil {
-            return err
-        }
-        go bcR.poolRoutine()
-    }
+       if bcR.fastSync {
+               _, err := bcR.pool.Start()
+               if err != nil {
+                       return err
+               }
+               go bcR.poolRoutine()
+       }
        return nil
 }
 
@@ -313,7 +310,7 @@ func (bcR *BlockchainReactor) Receive(chID byte, src *p2p.Peer, msgBytes []byte)
        switch msg := msg.(type) {
        case *bcBlockRequestMessage:
                // Got a request for a block. Respond with block if we have it.
-               block, _:= bcR.store.GetBlock(msg.Height)
+               block, _ := bcR.store.GetBlock(msg.Height)
                if block != nil {
                        msg := &bcBlockResponseMessage{Block: block}
                        queued := src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{msg})
@@ -340,7 +337,6 @@ func (bcR *BlockchainReactor) Receive(chID byte, src *p2p.Peer, msgBytes []byte)
        }
 }
 
-
 // Handle messages from the poolReactor telling the reactor what to do.
 // NOTE: Don't sleep in the FOR_LOOP or otherwise slow it down!
 // (Except for the SYNC_LOOP, which is the primary purpose and must be synchronous.)
@@ -375,19 +371,19 @@ FOR_LOOP:
                        // ask for status updates
                        go bcR.BroadcastStatusRequest()
                /*case _ = <-switchToConsensusTicker.C:
-                       height, numPending, _ := bcR.pool.GetStatus()
-                       outbound, inbound, _ := bcR.Switch.NumPeers()
-                       bcR.Logger.Info("Consensus ticker", "numPending", numPending, "total", len(bcR.pool.requesters),
-                               "outbound", outbound, "inbound", inbound)
-                       if bcR.pool.IsCaughtUp() {
-                               bcR.Logger.Info("Time to switch to consensus reactor!", "height", height)
-                               bcR.pool.Stop()
-
-                               conR := bcR.Switch.Reactor("CONSENSUS").(consensusReactor)
-                               conR.SwitchToConsensus(bcR.state)
-
-                               break FOR_LOOP
-                       }*/
+               height, numPending, _ := bcR.pool.GetStatus()
+               outbound, inbound, _ := bcR.Switch.NumPeers()
+               bcR.Logger.Info("Consensus ticker", "numPending", numPending, "total", len(bcR.pool.requesters),
+                       "outbound", outbound, "inbound", inbound)
+               if bcR.pool.IsCaughtUp() {
+                       bcR.Logger.Info("Time to switch to consensus reactor!", "height", height)
+                       bcR.pool.Stop()
+
+                       conR := bcR.Switch.Reactor("CONSENSUS").(consensusReactor)
+                       conR.SwitchToConsensus(bcR.state)
+
+                       break FOR_LOOP
+               }*/
                case _ = <-trySyncTicker.C: // chan time
                        // This loop can be slow as long as it's doing syncing work.
                SYNC_LOOP:
@@ -399,8 +395,8 @@ FOR_LOOP:
                                        // We need both to sync the first block.
                                        break SYNC_LOOP
                                }
-                           bcR.pool.PopRequest()
-                bcR.store.SaveBlock(first)
+                               bcR.pool.PopRequest()
+                               bcR.store.SaveBlock(first)
                        }
                        continue FOR_LOOP
                case <-bcR.Quit:
@@ -415,7 +411,6 @@ func (bcR *BlockchainReactor) BroadcastStatusRequest() error {
        return nil
 }
 
-
 /*
 // SetEventSwitch implements events.Eventable
 func (bcR *BlockchainReactor) SetEventSwitch(evsw types.EventSwitch) {
index 4033c13..59280e1 100644 (file)
@@ -3,6 +3,7 @@ package main
 import (
        "bytes"
        "context"
+       stdjson "encoding/json"
        "flag"
        "fmt"
        "io"
@@ -12,20 +13,19 @@ import (
        "path/filepath"
        "strings"
        "time"
-       stdjson "encoding/json"
 
        "github.com/bytom/blockchain"
-//     "chain/core/accesstoken"
+       //      "chain/core/accesstoken"
        //"github.com/bytom/config"
-       "github.com/bytom/encoding/json"
+       "github.com/bytom/blockchain/query"
        "github.com/bytom/blockchain/rpc"
+       "github.com/bytom/cmd/bytomcli/example"
        "github.com/bytom/crypto/ed25519"
+       "github.com/bytom/crypto/ed25519/chainkd"
+       "github.com/bytom/encoding/json"
        "github.com/bytom/env"
        "github.com/bytom/errors"
        "github.com/bytom/log"
-       "github.com/bytom/crypto/ed25519/chainkd"
-       "github.com/bytom/cmd/bytomcli/example"
-       "github.com/bytom/blockchain/query"
 )
 
 // config vars
@@ -54,29 +54,33 @@ type grantReq struct {
 }
 
 var commands = map[string]*command{
-       "create-block-keypair": {createBlockKeyPair},
-       "reset":                {reset},
-       "grant":                {grant},
-       "revoke":               {revoke},
-       "wait":                 {wait},
-       "create-account":       {createAccount},
-       "update-account-tags":  {updateAccountTags},
-       "create-asset":         {createAsset},
-       "update-asset-tags":    {updateAssetTags},
-       "build-transaction": {buildTransaction},
-       "create-control-program": {createControlProgram},
+       "create-block-keypair":    {createBlockKeyPair},
+       "reset":                   {reset},
+       "grant":                   {grant},
+       "revoke":                  {revoke},
+       "wait":                    {wait},
+       "create-account":          {createAccount},
+       "update-account-tags":     {updateAccountTags},
+       "create-asset":            {createAsset},
+       "update-asset-tags":       {updateAssetTags},
+       "build-transaction":       {buildTransaction},
+       "create-control-program":  {createControlProgram},
        "create-account-receiver": {createAccountReceiver},
        "create-transaction-feed": {createTxFeed},
        "get-transaction-feed":    {getTxFeed},
        "update-transaction-feed": {updateTxFeed},
-        "list-accounts":           {listAccounts},
-        "list-assets":             {listAssets},
-        "list-transaction-feeds":  {listTxFeeds},
-        "list-transactions":       {listTransactions},
-        "list-balances":           {listBalances},
-        "list-unspent-outputs":    {listUnspentOutputs},
+       "list-accounts":           {listAccounts},
+       "list-assets":             {listAssets},
+       "list-transaction-feeds":  {listTxFeeds},
+       "list-transactions":       {listTransactions},
+       "list-balances":           {listBalances},
+       "list-unspent-outputs":    {listUnspentOutputs},
        "delete-transaction-feed": {deleteTxFeed},
-       "issue-test": {example.IssueTest},
+       "issue-test":              {example.IssueTest},
+       "spend-test":              {example.SpendTest},
+       "create-access-token":     {createAccessToken},
+       "list-access-token":       {listAccessTokens},
+       "delete-access-token":     {deleteAccessToken},
 }
 
 func main() {
@@ -111,7 +115,6 @@ func main() {
        cmd.f(mustRPCClient(), os.Args[2:])
 }
 
-
 func createBlockKeyPair(client *rpc.Client, args []string) {
        if len(args) != 0 {
                fatalln("error: create-block-keypair takes no args")
@@ -312,20 +315,20 @@ func createAccount(client *rpc.Client, args []string) {
        fmt.Printf("xprv:%v\n", xprv)
        fmt.Printf("xpub:%v\n", xpub)
        type Ins struct {
-           RootXPubs []chainkd.XPub `json:"root_xpubs"`
-               Quorum    int
-               Alias     string
-               Tags      map[string]interface{}
+               RootXPubs   []chainkd.XPub `json:"root_xpubs"`
+               Quorum      int
+               Alias       string
+               Tags        map[string]interface{}
                ClientToken string `json:"client_token"`
        }
        var ins Ins
        ins.RootXPubs = []chainkd.XPub{xpub}
        ins.Quorum = 1
        ins.Alias = "alice"
-       ins.Tags = map[string]interface{}{"test_tag": "v0",}
+       ins.Tags = map[string]interface{}{"test_tag": "v0"}
        ins.ClientToken = args[0]
        account := make([]query.AnnotatedAccount, 1)
-       client.Call(context.Background(), "/create-account", &[]Ins{ins,}, &account)
+       client.Call(context.Background(), "/create-account", &[]Ins{ins}, &account)
        //dieOnRPCError(err)
        fmt.Printf("responses:%v\n", account[0])
 }
@@ -342,10 +345,10 @@ func createAsset(client *rpc.Client, args []string) {
        fmt.Printf("xprv:%v\n", xprv)
        fmt.Printf("xpub:%v\n", xpub)
        type Ins struct {
-           RootXPubs []chainkd.XPub `json:"root_xpubs"`
-               Quorum    int
-               Alias     string
-               Tags      map[string]interface{}
+               RootXPubs   []chainkd.XPub `json:"root_xpubs"`
+               Quorum      int
+               Alias       string
+               Tags        map[string]interface{}
                Definition  map[string]interface{}
                ClientToken string `json:"client_token"`
        }
@@ -353,11 +356,11 @@ func createAsset(client *rpc.Client, args []string) {
        ins.RootXPubs = []chainkd.XPub{xpub}
        ins.Quorum = 1
        ins.Alias = "bob"
-       ins.Tags = map[string]interface{}{"test_tag": "v0",}
+       ins.Tags = map[string]interface{}{"test_tag": "v0"}
        ins.Definition = map[string]interface{}{}
        ins.ClientToken = args[0]
        assets := make([]query.AnnotatedAsset, 1)
-       client.Call(context.Background(), "/create-asset", &[]Ins{ins,}, &assets)
+       client.Call(context.Background(), "/create-asset", &[]Ins{ins}, &assets)
        //dieOnRPCError(err)
        fmt.Printf("responses:%v\n", assets)
 }
@@ -395,23 +398,23 @@ func updateAccountTags(client *rpc.Client, args []string) {
        fmt.Printf("responses:%v\n", responses)
 }
 
-func updateAssetTags(client *rpc.Client, args []string){
-       if len(args) != 0{
-                       fatalln("error:updateAccountTags not use args")
+func updateAssetTags(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:updateAccountTags not use args")
        }
        type Ins struct {
-       ID    *string
-       Alias *string
-       Tags  map[string]interface{} `json:"tags"`
+               ID    *string
+               Alias *string
+               Tags  map[string]interface{} `json:"tags"`
        }
        var ins Ins
        id := "123456"
        alias := "asdfg"
        ins.ID = &id
        ins.Alias = &alias
-       ins.Tags = map[string]interface{}{"test_tag": "v0",}
+       ins.Tags = map[string]interface{}{"test_tag": "v0"}
        responses := make([]interface{}, 50)
-       client.Call(context.Background(), "/update-asset-tags", &[]Ins{ins,}, &responses)
+       client.Call(context.Background(), "/update-asset-tags", &[]Ins{ins}, &responses)
        fmt.Printf("responses:%v\n", responses)
 }
 
@@ -421,244 +424,297 @@ func buildTransaction(client *rpc.Client, args []string) {
        }
 }
 
-func createControlProgram(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:createControlProgram not use args")
-        }
+func createControlProgram(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:createControlProgram not use args")
+       }
        type Ins struct {
-       Type   string
-       Params stdjson.RawMessage
-}
+               Type   string
+               Params stdjson.RawMessage
+       }
        var ins Ins
        //TODO:undefined arguments to ins
-       responses := make([]interface{},50)
-        client.Call(context.Background(),"/create-control-program", &[]Ins{ins,}, &responses)
-        fmt.Printf("responses:%v\n", responses)
+       responses := make([]interface{}, 50)
+       client.Call(context.Background(), "/create-control-program", &[]Ins{ins}, &responses)
+       fmt.Printf("responses:%v\n", responses)
 }
 
-func createAccountReceiver(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:createAccountReceiver not use args")
-        }
+func createAccountReceiver(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:createAccountReceiver not use args")
+       }
        type Ins struct {
-       AccountID    string    `json:"account_id"`
-       AccountAlias string    `json:"account_alias"`
-       ExpiresAt    time.Time `json:"expires_at"`
-}
+               AccountID    string    `json:"account_id"`
+               AccountAlias string    `json:"account_alias"`
+               ExpiresAt    time.Time `json:"expires_at"`
+       }
        var ins Ins
        //TODO:undefined argument to ExpiresAt
        ins.AccountID = "123456"
        ins.AccountAlias = "zxcvbn"
-       responses := make([]interface{},50)
-        client.Call(context.Background(),"/create-Account-Receiver", &[]Ins{ins,}, &responses)
-        fmt.Printf("responses:%v\n", responses)
+       responses := make([]interface{}, 50)
+       client.Call(context.Background(), "/create-Account-Receiver", &[]Ins{ins}, &responses)
+       fmt.Printf("responses:%v\n", responses)
 }
 
-func createTxFeed(client *rpc.Client, args []string){
-        if len(args) != 1{
-                fatalln("error:createTxFeed take no arguments")
-        }
+func createTxFeed(client *rpc.Client, args []string) {
+       if len(args) != 1 {
+               fatalln("error:createTxFeed take no arguments")
+       }
        type In struct {
-       Alias  string
-       Filter string
-       ClientToken string `json:"client_token"`
-}
+               Alias       string
+               Filter      string
+               ClientToken string `json:"client_token"`
+       }
        var in In
        in.Alias = "asdfgh"
        in.Filter = "zxcvbn"
        in.ClientToken = args[0]
-       client.Call(context.Background(),"/create-transaction-feed",&[]In{in,},nil)
+       client.Call(context.Background(), "/create-transaction-feed", &[]In{in}, nil)
 }
 
-func getTxFeed(client *rpc.Client, args []string){
-       if len(args) != 0{
+func getTxFeed(client *rpc.Client, args []string) {
+       if len(args) != 0 {
                fatalln("error:getTxFeed not use args")
        }
        type In struct {
-       ID    string `json:"id,omitempty"`
-       Alias string `json:"alias,omitempty"`
-}
+               ID    string `json:"id,omitempty"`
+               Alias string `json:"alias,omitempty"`
+       }
        var in In
        in.Alias = "qwerty"
        in.ID = "123456"
-       client.Call(context.Background(),"/get-transaction-feed",&[]In{in,},nil)
+       client.Call(context.Background(), "/get-transaction-feed", &[]In{in}, nil)
 }
 
-func updateTxFeed(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:updateTxFeed not use args")
-        }
-        type In struct {
-       ID    string `json:"id,omitempty"`
-       Alias string `json:"alias,omitempty"`
-}
+func updateTxFeed(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:updateTxFeed not use args")
+       }
+       type In struct {
+               ID    string `json:"id,omitempty"`
+               Alias string `json:"alias,omitempty"`
+       }
        var in In
        in.ID = "123456"
        in.Alias = "qwerty"
-       client.Call(context.Background(),"/update-transaction-feed",&[]In{in,},nil)
+       client.Call(context.Background(), "/update-transaction-feed", &[]In{in}, nil)
 }
 
-func deleteTxFeed(client *rpc.Client, args []string){
-       if len(args) != 0{
+func deleteTxFeed(client *rpc.Client, args []string) {
+       if len(args) != 0 {
                fatalln("error:deleteTxFeed not use args")
        }
        type In struct {
-       ID    string `json:"id,omitempty"`
-       Alias string `json:"alias,omitempty"`
-}
+               ID    string `json:"id,omitempty"`
+               Alias string `json:"alias,omitempty"`
+       }
        var in In
-        in.ID = "123456"
-        in.Alias = "qwerty"
-        client.Call(context.Background(),"/delete-transaction-feed",&[]In{in,},nil)
+       in.ID = "123456"
+       in.Alias = "qwerty"
+       client.Call(context.Background(), "/delete-transaction-feed", &[]In{in}, nil)
 }
 
-func listAccounts(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:listAccounts not use args")
-        }
+func listAccounts(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:listAccounts not use args")
+       }
        type requestQuery struct {
-                Filter       string        `json:"filter,omitempty"`
-                FilterParams []interface{} `json:"filter_params,omitempty"`
-                SumBy        []string      `json:"sum_by,omitempty"`
-                PageSize     int           `json:"page_size"`
-                AscLongPoll bool          `json:"ascending_with_long_poll,omitempty"`
-                Timeout     json.Duration `json:"timeout"`
-                After string `json:"after"`
-                StartTimeMS uint64 `json:"start_time,omitempty"`
-                EndTimeMS   uint64 `json:"end_time,omitempty"`
-                TimestampMS uint64 `json:"timestamp,omitempty"`
-                Type string `json:"type"`
-                Aliases []string `json:"aliases,omitempty"`
-}
+               Filter       string        `json:"filter,omitempty"`
+               FilterParams []interface{} `json:"filter_params,omitempty"`
+               SumBy        []string      `json:"sum_by,omitempty"`
+               PageSize     int           `json:"page_size"`
+               AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
+               Timeout      json.Duration `json:"timeout"`
+               After        string        `json:"after"`
+               StartTimeMS  uint64        `json:"start_time,omitempty"`
+               EndTimeMS    uint64        `json:"end_time,omitempty"`
+               TimestampMS  uint64        `json:"timestamp,omitempty"`
+               Type         string        `json:"type"`
+               Aliases      []string      `json:"aliases,omitempty"`
+       }
        var in requestQuery
        after := in.After
        out := in
        out.After = after
-        client.Call(context.Background(),"/list-accounts",&[]requestQuery{in,},nil)
+       client.Call(context.Background(), "/list-accounts", &[]requestQuery{in}, nil)
 }
 
-func listAssets(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:listAssets not use args")
-        }
+func listAssets(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:listAssets not use args")
+       }
        type requestQuery struct {
-                Filter       string        `json:"filter,omitempty"`
-                FilterParams []interface{} `json:"filter_params,omitempty"`
-                SumBy        []string      `json:"sum_by,omitempty"`
-                PageSize     int           `json:"page_size"`
-                AscLongPoll bool          `json:"ascending_with_long_poll,omitempty"`
-                Timeout     json.Duration `json:"timeout"`
-                After string `json:"after"`
-                StartTimeMS uint64 `json:"start_time,omitempty"`
-                EndTimeMS   uint64 `json:"end_time,omitempty"`
-                TimestampMS uint64 `json:"timestamp,omitempty"`
-                Type string `json:"type"`
-                Aliases []string `json:"aliases,omitempty"`
-}
+               Filter       string        `json:"filter,omitempty"`
+               FilterParams []interface{} `json:"filter_params,omitempty"`
+               SumBy        []string      `json:"sum_by,omitempty"`
+               PageSize     int           `json:"page_size"`
+               AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
+               Timeout      json.Duration `json:"timeout"`
+               After        string        `json:"after"`
+               StartTimeMS  uint64        `json:"start_time,omitempty"`
+               EndTimeMS    uint64        `json:"end_time,omitempty"`
+               TimestampMS  uint64        `json:"timestamp,omitempty"`
+               Type         string        `json:"type"`
+               Aliases      []string      `json:"aliases,omitempty"`
+       }
        var in requestQuery
        after := in.After
        out := in
        out.After = after
-       client.Call(context.Background(),"/list-assets",&[]requestQuery{in,},nil)
+       client.Call(context.Background(), "/list-assets", &[]requestQuery{in}, nil)
 }
 
-func listTxFeeds(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:listTxFeeds not use args")
-        }
+func listTxFeeds(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:listTxFeeds not use args")
+       }
        type requestQuery struct {
-                Filter       string        `json:"filter,omitempty"`
-                FilterParams []interface{} `json:"filter_params,omitempty"`
-                SumBy        []string      `json:"sum_by,omitempty"`
-                PageSize     int           `json:"page_size"`
-                AscLongPoll bool          `json:"ascending_with_long_poll,omitempty"`
-                Timeout     json.Duration `json:"timeout"`
-                After string `json:"after"`
-                StartTimeMS uint64 `json:"start_time,omitempty"`
-                EndTimeMS   uint64 `json:"end_time,omitempty"`
-                TimestampMS uint64 `json:"timestamp,omitempty"`
-                Type string `json:"type"`
-                Aliases []string `json:"aliases,omitempty"`
-}
+               Filter       string        `json:"filter,omitempty"`
+               FilterParams []interface{} `json:"filter_params,omitempty"`
+               SumBy        []string      `json:"sum_by,omitempty"`
+               PageSize     int           `json:"page_size"`
+               AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
+               Timeout      json.Duration `json:"timeout"`
+               After        string        `json:"after"`
+               StartTimeMS  uint64        `json:"start_time,omitempty"`
+               EndTimeMS    uint64        `json:"end_time,omitempty"`
+               TimestampMS  uint64        `json:"timestamp,omitempty"`
+               Type         string        `json:"type"`
+               Aliases      []string      `json:"aliases,omitempty"`
+       }
        var in requestQuery
        after := in.After
        out := in
        out.After = after
-               client.Call(context.Background(),"/list-transactions-feeds",&[]requestQuery{in,},nil)
+       client.Call(context.Background(), "/list-transactions-feeds", &[]requestQuery{in}, nil)
 }
 
-func listTransactions(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:listTransactions not use args")
-        }
+func listTransactions(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:listTransactions not use args")
+       }
        type requestQuery struct {
-                Filter       string        `json:"filter,omitempty"`
-                FilterParams []interface{} `json:"filter_params,omitempty"`
-                SumBy        []string      `json:"sum_by,omitempty"`
-                PageSize     int           `json:"page_size"`
-                AscLongPoll bool          `json:"ascending_with_long_poll,omitempty"`
-                Timeout     json.Duration `json:"timeout"`
-                After string `json:"after"`
-                StartTimeMS uint64 `json:"start_time,omitempty"`
-                EndTimeMS   uint64 `json:"end_time,omitempty"`
-                TimestampMS uint64 `json:"timestamp,omitempty"`
-                Type string `json:"type"`
-                Aliases []string `json:"aliases,omitempty"`
-}
+               Filter       string        `json:"filter,omitempty"`
+               FilterParams []interface{} `json:"filter_params,omitempty"`
+               SumBy        []string      `json:"sum_by,omitempty"`
+               PageSize     int           `json:"page_size"`
+               AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
+               Timeout      json.Duration `json:"timeout"`
+               After        string        `json:"after"`
+               StartTimeMS  uint64        `json:"start_time,omitempty"`
+               EndTimeMS    uint64        `json:"end_time,omitempty"`
+               TimestampMS  uint64        `json:"timestamp,omitempty"`
+               Type         string        `json:"type"`
+               Aliases      []string      `json:"aliases,omitempty"`
+       }
        var in requestQuery
        after := in.After
        out := in
        out.After = after
-        client.Call(context.Background(),"/list-transactions",&[]requestQuery{in,},nil)
-}
-
-func listBalances(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:listBalances not use args")
-        }
-type requestQuery struct {
-                Filter       string        `json:"filter,omitempty"`
-                FilterParams []interface{} `json:"filter_params,omitempty"`
-                SumBy        []string      `json:"sum_by,omitempty"`
-                PageSize     int           `json:"page_size"`
-                AscLongPoll bool          `json:"ascending_with_long_poll,omitempty"`
-                Timeout     json.Duration `json:"timeout"`
-                After string `json:"after"`
-                StartTimeMS uint64 `json:"start_time,omitempty"`
-                EndTimeMS   uint64 `json:"end_time,omitempty"`
-                TimestampMS uint64 `json:"timestamp,omitempty"`
-                Type string `json:"type"`
-                Aliases []string `json:"aliases,omitempty"`
+       client.Call(context.Background(), "/list-transactions", &[]requestQuery{in}, nil)
 }
 
+func listBalances(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:listBalances not use args")
+       }
+       type requestQuery struct {
+               Filter       string        `json:"filter,omitempty"`
+               FilterParams []interface{} `json:"filter_params,omitempty"`
+               SumBy        []string      `json:"sum_by,omitempty"`
+               PageSize     int           `json:"page_size"`
+               AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
+               Timeout      json.Duration `json:"timeout"`
+               After        string        `json:"after"`
+               StartTimeMS  uint64        `json:"start_time,omitempty"`
+               EndTimeMS    uint64        `json:"end_time,omitempty"`
+               TimestampMS  uint64        `json:"timestamp,omitempty"`
+               Type         string        `json:"type"`
+               Aliases      []string      `json:"aliases,omitempty"`
+       }
+
        var in requestQuery
        after := in.After
        out := in
        out.After = after
-        client.Call(context.Background(),"/list-balance",&[]requestQuery{in,},nil)
-}
-
-func listUnspentOutputs(client *rpc.Client, args []string){
-        if len(args) != 0{
-                fatalln("error:listUnspentOutputs not use args")
-        }
-type requestQuery struct {
-                Filter       string        `json:"filter,omitempty"`
-                FilterParams []interface{} `json:"filter_params,omitempty"`
-                SumBy        []string      `json:"sum_by,omitempty"`
-                PageSize     int           `json:"page_size"`
-                AscLongPoll bool          `json:"ascending_with_long_poll,omitempty"`
-                Timeout     json.Duration `json:"timeout"`
-                After string `json:"after"`
-                StartTimeMS uint64 `json:"start_time,omitempty"`
-                EndTimeMS   uint64 `json:"end_time,omitempty"`
-                TimestampMS uint64 `json:"timestamp,omitempty"`
-                Type string `json:"type"`
-                Aliases []string `json:"aliases,omitempty"`
+       client.Call(context.Background(), "/list-balance", &[]requestQuery{in}, nil)
 }
+
+func listUnspentOutputs(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:listUnspentOutputs not use args")
+       }
+       type requestQuery struct {
+               Filter       string        `json:"filter,omitempty"`
+               FilterParams []interface{} `json:"filter_params,omitempty"`
+               SumBy        []string      `json:"sum_by,omitempty"`
+               PageSize     int           `json:"page_size"`
+               AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
+               Timeout      json.Duration `json:"timeout"`
+               After        string        `json:"after"`
+               StartTimeMS  uint64        `json:"start_time,omitempty"`
+               EndTimeMS    uint64        `json:"end_time,omitempty"`
+               TimestampMS  uint64        `json:"timestamp,omitempty"`
+               Type         string        `json:"type"`
+               Aliases      []string      `json:"aliases,omitempty"`
+       }
        var in requestQuery
        after := in.After
        out := in
        out.After = after
-        client.Call(context.Background(),"/list-unspent-outputs",&[]requestQuery{in,},nil)
+       client.Call(context.Background(), "/list-unspent-outputs", &[]requestQuery{in}, nil)
+}
+
+func createAccessToken(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:createAccessToken not use args")
+       }
+       type Token struct {
+               ID      string    `json:"id"`
+               Token   string    `json:"token,omitempty"`
+               Type    string    `json:"type,omitempty"` // deprecated in 1.2
+               Created time.Time `json:"created_at"`
+               sortID  string
+       }
+       var token Token
+       token.ID = "Alice"
+       token.Token = "token"
+
+       client.Call(context.Background(), "/create-access-token", &[]Token{token}, nil)
+}
+
+func listAccessTokens(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:listAccessTokens not use args")
+       }
+       type Token struct {
+               ID      string    `json:"id"`
+               Token   string    `json:"token,omitempty"`
+               Type    string    `json:"type,omitempty"` // deprecated in 1.2
+               Created time.Time `json:"created_at"`
+               sortID  string
+       }
+       var token Token
+       token.ID = "Alice"
+       token.Token = "token"
+
+       client.Call(context.Background(), "/list-access-token", &[]Token{token}, nil)
+}
+func deleteAccessToken(client *rpc.Client, args []string) {
+       if len(args) != 0 {
+               fatalln("error:deleteAccessToken not use args")
+       }
+       type Token struct {
+               ID      string    `json:"id"`
+               Token   string    `json:"token,omitempty"`
+               Type    string    `json:"type,omitempty"` // deprecated in 1.2
+               Created time.Time `json:"created_at"`
+               sortID  string
+       }
+       var token Token
+       token.ID = "Alice"
+       token.Token = "token"
+
+       client.Call(context.Background(), "/delete-access-token", &[]Token{token}, nil)
 }