github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/tequilapi/endpoints/identities.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package endpoints
    19  
    20  import (
    21  	"encoding/json"
    22  	"fmt"
    23  	"math/big"
    24  	"net/http"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/gin-gonic/gin"
    28  	"github.com/mysteriumnetwork/go-rest/apierror"
    29  	"github.com/mysteriumnetwork/node/consumer/migration"
    30  	"github.com/mysteriumnetwork/payments/client"
    31  	"github.com/pkg/errors"
    32  
    33  	"github.com/mysteriumnetwork/node/config"
    34  	"github.com/mysteriumnetwork/node/core/beneficiary"
    35  	"github.com/mysteriumnetwork/node/identity"
    36  	"github.com/mysteriumnetwork/node/identity/registry"
    37  	identity_selector "github.com/mysteriumnetwork/node/identity/selector"
    38  	pingpong_event "github.com/mysteriumnetwork/node/session/pingpong/event"
    39  	"github.com/mysteriumnetwork/node/tequilapi/contract"
    40  	"github.com/mysteriumnetwork/node/tequilapi/middlewares"
    41  	"github.com/mysteriumnetwork/node/tequilapi/utils"
    42  	"github.com/rs/zerolog/log"
    43  )
    44  
    45  type balanceProvider interface {
    46  	GetBalance(chainID int64, id identity.Identity) *big.Int
    47  	ForceBalanceUpdateCached(chainID int64, id identity.Identity) *big.Int
    48  }
    49  
    50  type earningsProvider interface {
    51  	GetEarningsDetailed(chainID int64, id identity.Identity) *pingpong_event.EarningsDetailed
    52  }
    53  
    54  type beneficiaryProvider interface {
    55  	GetBeneficiary(identity common.Address) (common.Address, error)
    56  }
    57  
    58  type providerChannel interface {
    59  	GetProviderChannel(chainID int64, hermesAddress common.Address, provider common.Address, pending bool) (client.ProviderChannel, error)
    60  }
    61  
    62  type identityMover interface {
    63  	Import(blob []byte, currPass, newPass string) (identity.Identity, error)
    64  	Export(address, currPass, newPass string) ([]byte, error)
    65  }
    66  
    67  type identitiesAPI struct {
    68  	mover              identityMover
    69  	idm                identity.Manager
    70  	selector           identity_selector.Handler
    71  	registry           registry.IdentityRegistry
    72  	addressProvider    AddressProvider
    73  	balanceProvider    balanceProvider
    74  	earningsProvider   earningsProvider
    75  	bc                 providerChannel
    76  	transactor         Transactor
    77  	bprovider          beneficiaryProvider
    78  	beneficiaryStorage beneficiary.BeneficiaryStorage
    79  	hermesMigrator     *migration.HermesMigrator
    80  }
    81  
    82  // AddressProvider provides sc addresses.
    83  type AddressProvider interface {
    84  	GetActiveHermes(chainID int64) (common.Address, error)
    85  	GetActiveChannelAddress(chainID int64, id common.Address) (common.Address, error)
    86  	GetKnownHermeses(chainID int64) ([]common.Address, error)
    87  	GetHermesChannelAddress(chainID int64, id, hermesAddr common.Address) (common.Address, error)
    88  }
    89  
    90  // swagger:operation GET /identities Identity listIdentities
    91  //
    92  //	---
    93  //	summary: Returns identities
    94  //	description: Returns list of identities
    95  //	responses:
    96  //	  200:
    97  //	    description: List of identities
    98  //	    schema:
    99  //	      "$ref": "#/definitions/ListIdentitiesResponse"
   100  func (ia *identitiesAPI) List(c *gin.Context) {
   101  	ids := ia.idm.GetIdentities()
   102  	idsDTO := contract.NewIdentityListResponse(ids)
   103  	utils.WriteAsJSON(idsDTO, c.Writer)
   104  }
   105  
   106  // swagger:operation PUT /identities/current Identity currentIdentity
   107  //
   108  //	---
   109  //	summary: Returns my current identity
   110  //	description: Tries to retrieve the last used identity, the first identity, or creates and returns a new identity
   111  //	parameters:
   112  //	  - in: body
   113  //	    name: body
   114  //	    description: Parameter in body (passphrase) required for creating new identity
   115  //	    schema:
   116  //	      $ref: "#/definitions/IdentityCurrentRequestDTO"
   117  //	responses:
   118  //	  200:
   119  //	    description: Unlocked identity returned
   120  //	    schema:
   121  //	      "$ref": "#/definitions/IdentityRefDTO"
   122  //	  400:
   123  //	    description: Failed to parse or request validation failed
   124  //	    schema:
   125  //	      "$ref": "#/definitions/APIError"
   126  //	  500:
   127  //	    description: Internal server error
   128  //	    schema:
   129  //	      "$ref": "#/definitions/APIError"
   130  func (ia *identitiesAPI) Current(c *gin.Context) {
   131  	var req contract.IdentityCurrentRequest
   132  	err := json.NewDecoder(c.Request.Body).Decode(&req)
   133  	if err != nil {
   134  		c.Error(apierror.ParseFailed())
   135  		return
   136  	}
   137  
   138  	if err := req.Validate(); err != nil {
   139  		c.Error(err)
   140  		return
   141  	}
   142  
   143  	idAddress := ""
   144  	if req.Address != nil {
   145  		idAddress = *req.Address
   146  	}
   147  
   148  	chainID := config.GetInt64(config.FlagChainID)
   149  	id, err := ia.selector.UseOrCreate(idAddress, *req.Passphrase, chainID)
   150  
   151  	if err != nil {
   152  		c.Error(apierror.Internal("Failed to use/create ID: "+err.Error(), contract.ErrCodeIDUseOrCreate))
   153  		return
   154  	}
   155  
   156  	idDTO := contract.NewIdentityDTO(id)
   157  	utils.WriteAsJSON(idDTO, c.Writer)
   158  }
   159  
   160  // swagger:operation POST /identities Identity createIdentity
   161  //
   162  //	---
   163  //	summary: Creates new identity
   164  //	description: Creates identity and stores in keystore encrypted with passphrase
   165  //	parameters:
   166  //	  - in: body
   167  //	    name: body
   168  //	    description: Parameter in body (passphrase) required for creating new identity
   169  //	    schema:
   170  //	      $ref: "#/definitions/IdentityCreateRequestDTO"
   171  //	responses:
   172  //	  200:
   173  //	    description: Identity created
   174  //	    schema:
   175  //	      "$ref": "#/definitions/IdentityRefDTO"
   176  //	  400:
   177  //	    description: Failed to parse or request validation failed
   178  //	    schema:
   179  //	      "$ref": "#/definitions/APIError"
   180  //	  500:
   181  //	    description: Internal server error
   182  //	    schema:
   183  //	      "$ref": "#/definitions/APIError"
   184  func (ia *identitiesAPI) Create(c *gin.Context) {
   185  	var req contract.IdentityCreateRequest
   186  	err := json.NewDecoder(c.Request.Body).Decode(&req)
   187  	if err != nil {
   188  		c.Error(apierror.ParseFailed())
   189  		return
   190  	}
   191  
   192  	if err := req.Validate(); err != nil {
   193  		c.Error(err)
   194  		return
   195  	}
   196  
   197  	id, err := ia.idm.CreateNewIdentity(*req.Passphrase)
   198  	if err != nil {
   199  		c.Error(apierror.Internal("Failed to create ID", contract.ErrCodeIDCreate))
   200  		return
   201  	}
   202  
   203  	idDTO := contract.NewIdentityDTO(id)
   204  	utils.WriteAsJSON(idDTO, c.Writer)
   205  }
   206  
   207  // swagger:operation POST /export export Identity
   208  //
   209  //	---
   210  //	summary: Exports a given identity
   211  //	description: Creates identity and stores in keystore encrypted with passphrase
   212  //	parameters:
   213  //	  - in: body
   214  //	    name: body
   215  //	    description: Parameter in body (passphrase) required for creating new identity
   216  //	    schema:
   217  //	      $ref: "#/definitions/IdentityExportRequestDTO"
   218  //	responses:
   219  //	  200:
   220  //	    description: Identity created
   221  //	    schema:
   222  //	      "$ref": "#/definitions/IdentityExportResponseDTO"
   223  //	  400:
   224  //	    description: Failed to parse or request validation failed
   225  //	    schema:
   226  //	      "$ref": "#/definitions/APIError"
   227  //	  500:
   228  //	    description: Internal server error
   229  //	    schema:
   230  //	      "$ref": "#/definitions/APIError"
   231  func (ia *identitiesAPI) Export(c *gin.Context) {
   232  	var req contract.IdentityExportRequest
   233  	err := json.NewDecoder(c.Request.Body).Decode(&req)
   234  	if err != nil {
   235  		c.Error(apierror.ParseFailed())
   236  		return
   237  	}
   238  
   239  	if err := req.Validate(); err != nil {
   240  		c.Error(err)
   241  		return
   242  	}
   243  
   244  	resp, err := ia.mover.Export(req.Identity, "", req.NewPassphrase)
   245  	if err != nil {
   246  		c.Error(err)
   247  		return
   248  	}
   249  	c.Writer.Write(resp)
   250  }
   251  
   252  // swagger:operation PUT /identities/{id}/unlock Identity unlockIdentity
   253  //
   254  //	---
   255  //	summary: Unlocks identity
   256  //	description: Uses passphrase to decrypt identity stored in keystore
   257  //	parameters:
   258  //	- in: path
   259  //	  name: id
   260  //	  description: Identity stored in keystore
   261  //	  type: string
   262  //	  required: true
   263  //	- in: body
   264  //	  name: body
   265  //	  description: Parameter in body (passphrase) required for unlocking identity
   266  //	  schema:
   267  //	    $ref: "#/definitions/IdentityUnlockRequestDTO"
   268  //	responses:
   269  //	  202:
   270  //	    description: Identity unlocked
   271  //	  400:
   272  //	    description: Failed to parse or request validation failed
   273  //	    schema:
   274  //	      "$ref": "#/definitions/APIError"
   275  //	  403:
   276  //	    description: Unlock failed
   277  //	    schema:
   278  //	      "$ref": "#/definitions/APIError"
   279  //	  404:
   280  //	    description: ID not found
   281  //	    schema:
   282  //	      "$ref": "#/definitions/APIError"
   283  func (ia *identitiesAPI) Unlock(c *gin.Context) {
   284  	address := c.Param("id")
   285  	id, err := ia.idm.GetIdentity(address)
   286  	if err != nil {
   287  		c.Error(apierror.NotFound("ID not found"))
   288  		return
   289  	}
   290  
   291  	var req contract.IdentityUnlockRequest
   292  	err = json.NewDecoder(c.Request.Body).Decode(&req)
   293  	if err != nil {
   294  		c.Error(apierror.ParseFailed())
   295  		return
   296  	}
   297  
   298  	if err := req.Validate(); err != nil {
   299  		c.Error(err)
   300  		return
   301  	}
   302  
   303  	chainID := config.GetInt64(config.FlagChainID)
   304  	err = ia.idm.Unlock(chainID, id.Address, *req.Passphrase)
   305  	if err != nil {
   306  		c.Error(apierror.Forbidden("Unlock failed", contract.ErrCodeIDUnlock))
   307  		return
   308  	}
   309  	c.Status(http.StatusAccepted)
   310  }
   311  
   312  // swagger:operation PUT /identities/{id}/balance/refresh Identity balance
   313  //
   314  //	---
   315  //	summary: Refresh balance of given identity
   316  //	description: Refresh balance of given identity
   317  //	parameters:
   318  //	  - in: path
   319  //	    name: id
   320  //	    description: hex address of identity
   321  //	    type: string
   322  //	    required: true
   323  //	responses:
   324  //	  200:
   325  //	    description: Updated balance
   326  //	    schema:
   327  //	      "$ref": "#/definitions/BalanceDTO"
   328  //	  404:
   329  //	    description: ID not found
   330  //	    schema:
   331  //	      "$ref": "#/definitions/APIError"
   332  func (ia *identitiesAPI) BalanceRefresh(c *gin.Context) {
   333  	address := c.Param("id")
   334  	id, err := ia.idm.GetIdentity(address)
   335  	if err != nil {
   336  		c.Error(apierror.NotFound("Identity not found"))
   337  		return
   338  	}
   339  	chainID := config.GetInt64(config.FlagChainID)
   340  	balance := ia.balanceProvider.ForceBalanceUpdateCached(chainID, id)
   341  	status := contract.BalanceDTO{
   342  		Balance:       balance,
   343  		BalanceTokens: contract.NewTokens(balance),
   344  	}
   345  	utils.WriteAsJSON(status, c.Writer)
   346  }
   347  
   348  // swagger:operation GET /identities/{id} Identity getIdentity
   349  //
   350  //	---
   351  //	summary: Get identity
   352  //	description: Provide identity details
   353  //	parameters:
   354  //	  - in: path
   355  //	    name: id
   356  //	    description: hex address of identity
   357  //	    type: string
   358  //	    required: true
   359  //	responses:
   360  //	  200:
   361  //	    description: Identity retrieved
   362  //	    schema:
   363  //	      "$ref": "#/definitions/IdentityRefDTO"
   364  //	  404:
   365  //	    description: ID not found
   366  //	    schema:
   367  //	      "$ref": "#/definitions/APIError"
   368  //	  500:
   369  //	    description: Internal server error
   370  //	    schema:
   371  //	      "$ref": "#/definitions/APIError"
   372  func (ia *identitiesAPI) Get(c *gin.Context) {
   373  	address := c.Param("id")
   374  	id, err := ia.idm.GetIdentity(address)
   375  	if err != nil {
   376  		c.Error(apierror.NotFound("Identity not found"))
   377  		return
   378  	}
   379  
   380  	chainID := config.GetInt64(config.FlagChainID)
   381  	regStatus, err := ia.registry.GetRegistrationStatus(chainID, id)
   382  	if err != nil {
   383  		c.Error(apierror.Internal("Failed to check ID registration status: "+err.Error(), contract.ErrCodeIDRegistrationCheck))
   384  		return
   385  	}
   386  
   387  	channelAddress, err := ia.addressProvider.GetActiveChannelAddress(chainID, id.ToCommonAddress())
   388  	if err != nil {
   389  		c.Error(apierror.Internal("Failed to calculate channel address: "+err.Error(), contract.ErrCodeIDCalculateAddress))
   390  		return
   391  	}
   392  
   393  	defaultHermesID, err := ia.addressProvider.GetActiveHermes(chainID)
   394  	if err != nil {
   395  		c.Error(apierror.Internal("Could not get active hermes: "+err.Error(), contract.ErrCodeActiveHermes))
   396  		return
   397  	}
   398  
   399  	var stake = new(big.Int)
   400  	if regStatus == registry.Registered {
   401  		data, err := ia.bc.GetProviderChannel(chainID, defaultHermesID, common.HexToAddress(address), false)
   402  		if err != nil {
   403  			c.Error(apierror.Internal("Failed to check blockchain registration status: "+err.Error(), contract.ErrCodeIDBlockchainRegistrationCheck))
   404  			return
   405  		}
   406  		stake = data.Stake
   407  	}
   408  
   409  	balance := ia.balanceProvider.GetBalance(chainID, id)
   410  	earnings := ia.earningsProvider.GetEarningsDetailed(chainID, id)
   411  
   412  	settlementsPerHermes := make(map[string]contract.EarningsDTO)
   413  	for h, earn := range earnings.PerHermes {
   414  		settlementsPerHermes[h.Hex()] = contract.EarningsDTO{
   415  			Earnings:      contract.NewTokens(earn.UnsettledBalance),
   416  			EarningsTotal: contract.NewTokens(earn.LifetimeBalance),
   417  		}
   418  	}
   419  
   420  	status := contract.IdentityDTO{
   421  		Address:             address,
   422  		RegistrationStatus:  regStatus.String(),
   423  		ChannelAddress:      channelAddress.Hex(),
   424  		Balance:             balance,
   425  		BalanceTokens:       contract.NewTokens(balance),
   426  		Earnings:            earnings.Total.UnsettledBalance,
   427  		EarningsTokens:      contract.NewTokens(earnings.Total.UnsettledBalance),
   428  		EarningsTotal:       earnings.Total.LifetimeBalance,
   429  		EarningsTotalTokens: contract.NewTokens(earnings.Total.LifetimeBalance),
   430  		Stake:               stake,
   431  		HermesID:            defaultHermesID.Hex(),
   432  		EarningsPerHermes:   contract.NewEarningsPerHermesDTO(earnings.PerHermes),
   433  	}
   434  	utils.WriteAsJSON(status, c.Writer)
   435  }
   436  
   437  // swagger:operation GET /identities/{id}/registration Identity identityRegistration
   438  //
   439  //	---
   440  //	summary: Provide identity registration status
   441  //	description: Provides registration status for given identity, if identity is not registered - provides additional data required for identity registration
   442  //	parameters:
   443  //	  - in: path
   444  //	    name: id
   445  //	    description: hex address of identity
   446  //	    type: string
   447  //	    required: true
   448  //	responses:
   449  //	  200:
   450  //	    description: Status retrieved
   451  //	    schema:
   452  //	      "$ref": "#/definitions/IdentityRegistrationResponseDTO"
   453  //	  404:
   454  //	    description: ID not found
   455  //	    schema:
   456  //	      "$ref": "#/definitions/APIError"
   457  //	  500:
   458  //	    description: Internal server error
   459  //	    schema:
   460  //	      "$ref": "#/definitions/APIError"
   461  func (ia *identitiesAPI) RegistrationStatus(c *gin.Context) {
   462  	address := c.Param("id")
   463  	id, err := ia.idm.GetIdentity(address)
   464  	if err != nil {
   465  		c.Error(apierror.NotFound("ID not found"))
   466  		return
   467  	}
   468  
   469  	regStatus, err := ia.registry.GetRegistrationStatus(config.GetInt64(config.FlagChainID), id)
   470  	if err != nil {
   471  		c.Error(apierror.Internal("Failed to check ID registration status", contract.ErrCodeIDRegistrationCheck))
   472  		return
   473  	}
   474  
   475  	registrationDataDTO := &contract.IdentityRegistrationResponse{
   476  		Status:     regStatus.String(),
   477  		Registered: regStatus.Registered(),
   478  	}
   479  	utils.WriteAsJSON(registrationDataDTO, c.Writer)
   480  }
   481  
   482  // swagger:operation GET /identities/{id}/beneficiary Identity beneficiary address
   483  //
   484  //	---
   485  //	summary: Provide identity beneficiary address
   486  //	description: Provides beneficiary address for given identity
   487  //	parameters:
   488  //	  - in: path
   489  //	    name: id
   490  //	    description: hex address of identity
   491  //	    type: string
   492  //	    required: true
   493  //	responses:
   494  //	  200:
   495  //	    description: Beneficiary retrieved
   496  //	    schema:
   497  //	      "$ref": "#/definitions/IdentityBeneficiaryResponseDTO"
   498  //	  500:
   499  //	    description: Internal server error
   500  //	    schema:
   501  //	      "$ref": "#/definitions/APIError"
   502  func (ia *identitiesAPI) Beneficiary(c *gin.Context) {
   503  	address := c.Param("id")
   504  	identity := common.HexToAddress(address)
   505  	beneficiary, err := ia.bprovider.GetBeneficiary(identity)
   506  	if err != nil {
   507  		utils.ForwardError(c, err, apierror.Internal("Failed to get beneficiary address", contract.ErrCodeBeneficiaryGet))
   508  		return
   509  	}
   510  
   511  	isChannel, err := isBenenficiarySetToChannel(ia.addressProvider, config.GetInt64(config.FlagChainID), identity, beneficiary)
   512  	if err != nil {
   513  		utils.ForwardError(c, err, apierror.Internal("Failed to check if beneficiary is set to channel address", contract.ErrCodeBeneficiaryGet))
   514  		return
   515  	}
   516  
   517  	registrationDataDTO := &contract.IdentityBeneficiaryResponse{
   518  		Beneficiary:      beneficiary.Hex(),
   519  		IsChannelAddress: isChannel,
   520  	}
   521  	utils.WriteAsJSON(registrationDataDTO, c.Writer)
   522  }
   523  
   524  // swagger:operation POST /identities-import Identities importIdentity
   525  //
   526  //	---
   527  //	summary: Imports a given identity.
   528  //	description: Imports a given identity returning it is a blob of text which can later be used to import it back.
   529  //	parameters:
   530  //	- in: body
   531  //	  name: body
   532  //	  description: Parameter in body used to import an identity.
   533  //	  schema:
   534  //	    $ref: "#/definitions/IdentityImportRequest"
   535  //	responses:
   536  //	  200:
   537  //	    description: Unlocked identity returned
   538  //	    schema:
   539  //	      "$ref": "#/definitions/IdentityRefDTO"
   540  //	  400:
   541  //	    description: Failed to parse or request validation failed
   542  //	    schema:
   543  //	      "$ref": "#/definitions/APIError"
   544  //	  422:
   545  //	    description: Unable to process the request at this point
   546  //	    schema:
   547  //	      "$ref": "#/definitions/APIError"
   548  //	  500:
   549  //	    description: Internal server error
   550  //	    schema:
   551  //	      "$ref": "#/definitions/APIError"
   552  func (ia *identitiesAPI) Import(c *gin.Context) {
   553  	var req contract.IdentityImportRequest
   554  	if err := json.NewDecoder(c.Request.Body).Decode(&req); err != nil {
   555  		c.Error(apierror.ParseFailed())
   556  		return
   557  	}
   558  
   559  	if err := req.Validate(); err != nil {
   560  		c.Error(err)
   561  		return
   562  	}
   563  
   564  	id, err := ia.mover.Import(req.Data, req.CurrentPassphrase, req.NewPassphrase)
   565  	if err != nil {
   566  		c.Error(apierror.Unprocessable(fmt.Sprintf("Failed to import identity: %s", err), contract.ErrCodeIDImport))
   567  		return
   568  	}
   569  
   570  	if req.SetDefault {
   571  		if err := ia.selector.SetDefault(id.Address); err != nil {
   572  			c.Error(apierror.Unprocessable(fmt.Sprintf("Failed to set default identity: %s", err), contract.ErrCodeIDSetDefault))
   573  			return
   574  		}
   575  	}
   576  
   577  	idDTO := contract.NewIdentityDTO(id)
   578  	utils.WriteAsJSON(idDTO, c.Writer)
   579  }
   580  
   581  // swagger:operation GET /identities/:id/beneficiary-async
   582  //
   583  //	---
   584  //	summary: Get beneficiary address
   585  //	description: Get beneficiary address stored locally
   586  //	parameters:
   587  //	- in: path
   588  //	  name: id
   589  //	  description: Identity stored in keystore
   590  //	  type: string
   591  //	  required: true
   592  //	responses:
   593  //	  200:
   594  //	    description: Local beneficiary address returned
   595  //	    schema:
   596  //	      "$ref": "#/definitions/BeneficiaryAddressRequest"
   597  //	  400:
   598  //	    description: Failed to parse or request validation failed
   599  //	    schema:
   600  //	      "$ref": "#/definitions/APIError"
   601  //	  500:
   602  //	    description: Internal server error
   603  //	    schema:
   604  //	      "$ref": "#/definitions/APIError"
   605  func (ia *identitiesAPI) GetBeneficiaryAddressAsync(c *gin.Context) {
   606  	id := c.Param("id")
   607  	addr, err := ia.beneficiaryStorage.Address(id)
   608  	if err != nil {
   609  		if errors.Is(err, beneficiary.ErrNotFound) {
   610  			utils.WriteAsJSON(contract.BeneficiaryAddressRequest{}, c.Writer)
   611  			return
   612  		}
   613  		c.Error(apierror.Internal("Failed to get beneficiary address", contract.ErrCodeIDGetBeneficiaryAddress))
   614  		return
   615  	}
   616  
   617  	utils.WriteAsJSON(contract.BeneficiaryAddressRequest{Address: addr}, c.Writer)
   618  }
   619  
   620  // swagger:operation POST /identities/:id/beneficiary-async
   621  //
   622  //	---
   623  //	summary: Save beneficiary address to local storage
   624  //	description: Stores beneficiary address locally
   625  //	parameters:
   626  //	- in: path
   627  //	  name: id
   628  //	  description: Identity stored in keystore
   629  //	  type: string
   630  //	  required: true
   631  //	- in: body
   632  //	  name: body
   633  //	  description: Beneficiary address request.
   634  //	  schema:
   635  //	    $ref: "#/definitions/BeneficiaryAddressRequest"
   636  //	responses:
   637  //	  200:
   638  //	    description: Local beneficiary address returned
   639  //	    schema:
   640  //	      "$ref": "#/definitions/BeneficiaryChangeRequest"
   641  //	  400:
   642  //	    description: Failed to parse or request validation failed
   643  //	    schema:
   644  //	      "$ref": "#/definitions/APIError"
   645  func (ia *identitiesAPI) SaveBeneficiaryAddressAsync(c *gin.Context) {
   646  	var par contract.BeneficiaryAddressRequest
   647  	if err := json.NewDecoder(c.Request.Body).Decode(&par); err != nil {
   648  		c.Error(apierror.ParseFailed())
   649  		return
   650  	}
   651  
   652  	id := c.Param("id")
   653  	err := ia.beneficiaryStorage.Save(id, par.Address)
   654  	if err != nil {
   655  		c.Error(apierror.BadRequest("Invalid address", contract.ErrCodeIDSaveBeneficiaryAddress))
   656  		return
   657  	}
   658  
   659  	utils.WriteAsJSON(par, c.Writer)
   660  }
   661  
   662  // swagger:operation POST /identities/:id/migrate-hermes
   663  //
   664  //	---
   665  //	summary: Migrate Hermes
   666  //	description: Migrate from old to new Hermes
   667  //	parameters:
   668  //	- in: path
   669  //	  name: id
   670  //	  description: Identity stored in keystore
   671  //	  type: string
   672  //	  required: true
   673  //	responses:
   674  //	  200:
   675  //	    description: Successfully migrated
   676  //	  403:
   677  //	    schema:
   678  //	      "$ref": "#/definitions/APIError"
   679  //	  500:
   680  //	    description: Internal server error
   681  //	    schema:
   682  //	      "$ref": "#/definitions/APIError"
   683  func (ia *identitiesAPI) MigrateHermes(c *gin.Context) {
   684  	id := c.Param("id")
   685  	if !ia.idm.IsUnlocked(id) {
   686  		c.Error(apierror.Forbidden("Identity is locked", contract.ErrCodeIDLocked))
   687  		return
   688  	}
   689  	err := ia.hermesMigrator.Start(id)
   690  	if err != nil {
   691  		c.Error(apierror.Internal(err.Error(), contract.ErrCodeHermesMigration))
   692  		log.Err(err).Msgf("could not migrate identity %s", id)
   693  		return
   694  	}
   695  
   696  	c.Status(http.StatusOK)
   697  }
   698  
   699  // swagger:operation GEY /identities/:id/migrate-hermes/status
   700  //
   701  //	---
   702  //	summary: Migration Hermes status
   703  //	description: Migrate from old to new Hermes
   704  //	parameters:
   705  //	- in: path
   706  //	  name: id
   707  //	  description: Identity stored in keystore
   708  //	  type: string
   709  //	  required: true
   710  //	responses:
   711  //	  200:
   712  //	    description: Successfully migrated
   713  //	    schema:
   714  //	      "$ref": "#/definitions/MigrationStatusResponse"
   715  //	  500:
   716  //	    description: Internal server error
   717  //	    schema:
   718  //	      "$ref": "#/definitions/APIError"
   719  func (ia *identitiesAPI) MigrationHermesStatus(c *gin.Context) {
   720  	id := c.Param("id")
   721  	r, err := ia.hermesMigrator.IsMigrationRequired(id)
   722  	if err != nil {
   723  		c.Error(apierror.Internal("Failed to check migration status", contract.ErrCodeCheckHermesMigrationStatus))
   724  		log.Err(err).Msgf("could not check Hermes migration status, id: %s", id)
   725  		return
   726  	}
   727  
   728  	var status contract.MigrationStatus
   729  	if r {
   730  		status = contract.MigrationStatusRequired
   731  	} else {
   732  		status = contract.MigrationStatusFinished
   733  	}
   734  
   735  	utils.WriteAsJSON(contract.MigrationStatusResponse{Status: status}, c.Writer)
   736  }
   737  
   738  func isBenenficiarySetToChannel(addressProvider addressProvider, chainID int64, identity, beneficiary common.Address) (bool, error) {
   739  	hermeses, err := addressProvider.GetKnownHermeses(chainID)
   740  	if err != nil {
   741  		return false, fmt.Errorf("failed to get list of known hermeses: %w", err)
   742  	}
   743  	for _, h := range hermeses {
   744  		channelAddr, err := addressProvider.GetHermesChannelAddress(chainID, identity, h)
   745  		if err != nil {
   746  			log.Err(err).Msgf("could not generate channel address for chain %d and hermes %s", chainID, h.Hex())
   747  			return false, fmt.Errorf("failed to generate channel address: %w", err)
   748  		}
   749  		if channelAddr == beneficiary {
   750  			return true, nil
   751  		}
   752  	}
   753  	return false, nil
   754  }
   755  
   756  // AddRoutesForIdentities creates /identities endpoint on tequilapi service
   757  func AddRoutesForIdentities(
   758  	idm identity.Manager,
   759  	selector identity_selector.Handler,
   760  	registry registry.IdentityRegistry,
   761  	balanceProvider balanceProvider,
   762  	addressProvider *client.MultiChainAddressProvider,
   763  	earningsProvider earningsProvider,
   764  	bc providerChannel,
   765  	transactor Transactor,
   766  	bprovider beneficiaryProvider,
   767  	mover identityMover,
   768  	addressStorage beneficiary.BeneficiaryStorage,
   769  	hermesMigrator *migration.HermesMigrator,
   770  ) func(*gin.Engine) error {
   771  	idAPI := &identitiesAPI{
   772  		mover:              mover,
   773  		idm:                idm,
   774  		selector:           selector,
   775  		registry:           registry,
   776  		balanceProvider:    balanceProvider,
   777  		addressProvider:    addressProvider,
   778  		earningsProvider:   earningsProvider,
   779  		bc:                 bc,
   780  		transactor:         transactor,
   781  		bprovider:          bprovider,
   782  		beneficiaryStorage: addressStorage,
   783  		hermesMigrator:     hermesMigrator,
   784  	}
   785  	return func(e *gin.Engine) error {
   786  		identityGroup := e.Group("/identities")
   787  		{
   788  			identityGroup.GET("", idAPI.List)
   789  			identityGroup.POST("", idAPI.Create)
   790  			identityGroup.PUT("/current", idAPI.Current)
   791  			identityGroup.GET("/:id", idAPI.Get)
   792  			identityGroup.GET("/:id/status", idAPI.Get)
   793  			identityGroup.PUT("/:id/unlock", idAPI.Unlock)
   794  			identityGroup.GET("/:id/registration", idAPI.RegistrationStatus)
   795  			identityGroup.GET("/:id/beneficiary", idAPI.Beneficiary)
   796  			identityGroup.GET("/:id/beneficiary-async", idAPI.GetBeneficiaryAddressAsync)
   797  			identityGroup.POST("/:id/beneficiary-async", idAPI.SaveBeneficiaryAddressAsync)
   798  			identityGroup.PUT("/:id/balance/refresh", idAPI.BalanceRefresh)
   799  			identityGroup.POST("/:id/migrate-hermes", idAPI.MigrateHermes)
   800  			identityGroup.GET("/:id/migrate-hermes/status", idAPI.MigrationHermesStatus)
   801  			identityGroup.POST("/export", middlewares.NewLocalhostOnlyFilter(), idAPI.Export)
   802  
   803  		}
   804  		e.POST("/identities-import", idAPI.Import)
   805  		return nil
   806  	}
   807  }