code.vegaprotocol.io/vega@v0.79.0/wallet/api/interactor/interaction.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package interactor
    17  
    18  import (
    19  	"encoding/json"
    20  	"time"
    21  
    22  	"github.com/mitchellh/mapstructure"
    23  )
    24  
    25  const (
    26  	CancelRequestName                       InteractionName = "CANCEL_REQUEST"
    27  	DecisionName                            InteractionName = "DECISION"
    28  	EnteredPassphraseName                   InteractionName = "ENTERED_PASSPHRASE"
    29  	ErrorOccurredName                       InteractionName = "ERROR_OCCURRED"
    30  	InteractionSessionBeganName             InteractionName = "INTERACTION_SESSION_BEGAN"
    31  	InteractionSessionEndedName             InteractionName = "INTERACTION_SESSION_ENDED"
    32  	LogName                                 InteractionName = "LOG"
    33  	RequestPassphraseName                   InteractionName = "REQUEST_PASSPHRASE"
    34  	RequestPermissionsReviewName            InteractionName = "REQUEST_PERMISSIONS_REVIEW"
    35  	RequestSucceededName                    InteractionName = "REQUEST_SUCCEEDED"
    36  	RequestTransactionReviewForSendingName  InteractionName = "REQUEST_TRANSACTION_REVIEW_FOR_SENDING"
    37  	RequestTransactionReviewForSigningName  InteractionName = "REQUEST_TRANSACTION_REVIEW_FOR_SIGNING"
    38  	RequestTransactionReviewForCheckingName InteractionName = "REQUEST_TRANSACTION_REVIEW_FOR_CHECKING"
    39  	RequestWalletConnectionReviewName       InteractionName = "REQUEST_WALLET_CONNECTION_REVIEW"
    40  	RequestWalletSelectionName              InteractionName = "REQUEST_WALLET_SELECTION"
    41  	SelectedWalletName                      InteractionName = "SELECTED_WALLET"
    42  	TransactionFailedName                   InteractionName = "TRANSACTION_FAILED"
    43  	TransactionSucceededName                InteractionName = "TRANSACTION_SUCCEEDED"
    44  	WalletConnectionDecisionName            InteractionName = "WALLET_CONNECTION_DECISION"
    45  )
    46  
    47  type InteractionName string
    48  
    49  // Interaction wraps the messages the JSON-RPC API sends to the wallet front-end
    50  // along with information about the context.
    51  type Interaction struct {
    52  	// TraceID is an identifier specifically made for client front-end to keep
    53  	// track of a transaction during all of its lifetime, from transaction
    54  	// review to sending confirmation and in-memory history.
    55  	// It shouldn't be confused with the transaction hash that is the
    56  	// transaction identifier.
    57  	TraceID string `json:"traceID"`
    58  
    59  	// Name is the name of the interaction. This helps to figure out how the
    60  	// data payload should be parsed.
    61  	Name InteractionName `json:"name"`
    62  
    63  	// Data is the generic field that hold the data of the specific interaction.
    64  	Data interface{} `json:"data"`
    65  }
    66  
    67  func (f *Interaction) UnmarshalJSON(data []byte) error {
    68  	input := &struct {
    69  		TraceID string                 `json:"traceID"`
    70  		Name    InteractionName        `json:"name"`
    71  		Data    map[string]interface{} `json:"data"`
    72  	}{}
    73  
    74  	if err := json.Unmarshal(data, &input); err != nil {
    75  		return err
    76  	}
    77  
    78  	f.TraceID = input.TraceID
    79  	f.Name = input.Name
    80  
    81  	switch input.Name {
    82  	case CancelRequestName:
    83  		data := CancelRequest{}
    84  		if err := mapstructure.Decode(input.Data, &data); err != nil {
    85  			return err
    86  		}
    87  		f.Data = data
    88  	case RequestWalletConnectionReviewName:
    89  		data := RequestWalletConnectionReview{}
    90  		if err := mapstructure.Decode(input.Data, &data); err != nil {
    91  			return err
    92  		}
    93  		f.Data = data
    94  	case RequestWalletSelectionName:
    95  		data := RequestWalletSelection{}
    96  		if err := mapstructure.Decode(input.Data, &data); err != nil {
    97  			return err
    98  		}
    99  		f.Data = data
   100  	case RequestPassphraseName:
   101  		data := RequestPassphrase{}
   102  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   103  			return err
   104  		}
   105  		f.Data = data
   106  	case RequestPermissionsReviewName:
   107  		data := RequestPermissionsReview{}
   108  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   109  			return err
   110  		}
   111  		f.Data = data
   112  	case RequestTransactionReviewForSendingName:
   113  		data := RequestTransactionReviewForSending{}
   114  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   115  			return err
   116  		}
   117  		f.Data = data
   118  	case RequestTransactionReviewForSigningName:
   119  		data := RequestTransactionReviewForSigning{}
   120  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   121  			return err
   122  		}
   123  		f.Data = data
   124  	case RequestTransactionReviewForCheckingName:
   125  		data := RequestTransactionReviewForChecking{}
   126  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   127  			return err
   128  		}
   129  		f.Data = data
   130  	case WalletConnectionDecisionName:
   131  		data := WalletConnectionDecision{}
   132  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   133  			return err
   134  		}
   135  		f.Data = data
   136  	case DecisionName:
   137  		data := Decision{}
   138  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   139  			return err
   140  		}
   141  		f.Data = data
   142  	case EnteredPassphraseName:
   143  		data := EnteredPassphrase{}
   144  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   145  			return err
   146  		}
   147  		f.Data = data
   148  	case SelectedWalletName:
   149  		data := SelectedWallet{}
   150  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   151  			return err
   152  		}
   153  		f.Data = data
   154  	case InteractionSessionBeganName:
   155  		data := InteractionSessionBegan{}
   156  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   157  			return err
   158  		}
   159  		f.Data = data
   160  	case InteractionSessionEndedName:
   161  		data := InteractionSessionEnded{}
   162  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   163  			return err
   164  		}
   165  		f.Data = data
   166  	case RequestSucceededName:
   167  		data := RequestSucceeded{}
   168  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   169  			return err
   170  		}
   171  		f.Data = data
   172  	case ErrorOccurredName:
   173  		data := ErrorOccurred{}
   174  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   175  			return err
   176  		}
   177  		f.Data = data
   178  	case TransactionSucceededName:
   179  		data := TransactionSucceeded{}
   180  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   181  			return err
   182  		}
   183  		f.Data = data
   184  	case TransactionFailedName:
   185  		data := TransactionFailed{}
   186  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   187  			return err
   188  		}
   189  		f.Data = data
   190  	case LogName:
   191  		data := Log{}
   192  		if err := mapstructure.Decode(input.Data, &data); err != nil {
   193  			return err
   194  		}
   195  		f.Data = data
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  // RequestWalletConnectionReview is a request emitted when a third-party
   202  // application wants to connect to a wallet.
   203  type RequestWalletConnectionReview struct {
   204  	Hostname string `json:"hostname"`
   205  
   206  	StepNumber uint8 `json:"stepNumber"`
   207  
   208  	// ResponseCh is the channel in which the response to that request is expected.
   209  	ResponseCh chan<- Interaction `json:"-"`
   210  
   211  	// ControlCh is the channel we use to communicate state of the request to
   212  	// the UI. If the request gets canceled on the third-party application side,
   213  	// we close this channel and the UI, that is listening to it, has to react
   214  	// accordingly.
   215  	ControlCh <-chan error `json:"-"`
   216  }
   217  
   218  // RequestWalletSelection is a request emitted when the service requires the user
   219  // to select a wallet. It is emitted after the user approved the wallet connection
   220  // from a third-party application.
   221  // It should be answered by an interactor.SelectedWallet response.
   222  type RequestWalletSelection struct {
   223  	Hostname         string   `json:"hostname"`
   224  	AvailableWallets []string `json:"availableWallets"`
   225  
   226  	StepNumber uint8 `json:"stepNumber"`
   227  
   228  	// ResponseCh is the channel in which the response to that request is expected.
   229  	ResponseCh chan<- Interaction `json:"-"`
   230  
   231  	// ControlCh is the channel we use to communicate state of the request to
   232  	// the UI. If the request gets canceled on the third-party application side,
   233  	// we close this channel and the UI, that is listening to it, has to react
   234  	// accordingly.
   235  	ControlCh chan error `json:"-"`
   236  }
   237  
   238  // RequestPassphrase is a request emitted when the service wants to confirm
   239  // the user has access to the wallet.
   240  // It should be answered by an interactor.EnteredPassphrase response.
   241  type RequestPassphrase struct {
   242  	Wallet string `json:"wallet"`
   243  	Reason string `json:"reason"`
   244  
   245  	StepNumber uint8 `json:"stepNumber"`
   246  
   247  	// ResponseCh is the channel in which the response to that request is expected.
   248  	ResponseCh chan<- Interaction `json:"-"`
   249  
   250  	// ControlCh is the channel we use to communicate state of the request to
   251  	// the UI. If the request gets canceled on the third-party application side,
   252  	// we close this channel and the UI, that is listening to it, has to react
   253  	// accordingly.
   254  	ControlCh chan error `json:"-"`
   255  }
   256  
   257  // RequestPermissionsReview is a review request emitted when a third-party
   258  // application performs an operation that requires an update to the permissions.
   259  type RequestPermissionsReview struct {
   260  	Hostname    string            `json:"hostname"`
   261  	Wallet      string            `json:"wallet"`
   262  	Permissions map[string]string `json:"permissions"`
   263  
   264  	StepNumber uint8 `json:"stepNumber"`
   265  
   266  	// ResponseCh is the channel in which the response to that request is expected.
   267  	ResponseCh chan<- Interaction `json:"-"`
   268  
   269  	// ControlCh is the channel we use to communicate state of the request to
   270  	// the UI. If the request gets canceled on the third-party application side,
   271  	// we close this channel and the UI, that is listening to it, has to react
   272  	// accordingly.
   273  	ControlCh chan error `json:"-"`
   274  }
   275  
   276  // RequestTransactionReviewForSending is a review request emitted when a third-party
   277  // application wants to send a transaction.
   278  type RequestTransactionReviewForSending struct {
   279  	Hostname    string    `json:"hostname"`
   280  	Wallet      string    `json:"wallet"`
   281  	PublicKey   string    `json:"publicKey"`
   282  	Transaction string    `json:"transaction"`
   283  	ReceivedAt  time.Time `json:"receivedAt"`
   284  
   285  	StepNumber uint8 `json:"stepNumber"`
   286  
   287  	// ResponseCh is the channel in which the response to that request is expected.
   288  	ResponseCh chan<- Interaction `json:"-"`
   289  
   290  	// ControlCh is the channel we use to communicate state of the request to
   291  	// the UI. If the request gets canceled on the third-party application side,
   292  	// we close this channel and the UI, that is listening to it, has to react
   293  	// accordingly.
   294  	ControlCh chan error `json:"-"`
   295  }
   296  
   297  // RequestTransactionReviewForChecking is a review request when a third-party
   298  // application wants the user to check a transaction.
   299  type RequestTransactionReviewForChecking struct {
   300  	Hostname    string    `json:"hostname"`
   301  	Wallet      string    `json:"wallet"`
   302  	PublicKey   string    `json:"publicKey"`
   303  	Transaction string    `json:"transaction"`
   304  	ReceivedAt  time.Time `json:"receivedAt"`
   305  
   306  	StepNumber uint8 `json:"stepNumber"`
   307  
   308  	// ResponseCh is the channel in which the response to that request is expected.
   309  	ResponseCh chan<- Interaction `json:"-"`
   310  
   311  	// ControlCh is the channel we use to communicate state of the request to
   312  	// the UI. If the request gets canceled on the third-party application side,
   313  	// we close this channel and the UI, that is listening to it, has to react
   314  	// accordingly.
   315  	ControlCh chan error `json:"-"`
   316  }
   317  
   318  // RequestTransactionReviewForSigning is a review request when a third-party
   319  // application wants to sign a transaction.
   320  type RequestTransactionReviewForSigning struct {
   321  	Hostname    string    `json:"hostname"`
   322  	Wallet      string    `json:"wallet"`
   323  	PublicKey   string    `json:"publicKey"`
   324  	Transaction string    `json:"transaction"`
   325  	ReceivedAt  time.Time `json:"receivedAt"`
   326  
   327  	StepNumber uint8 `json:"stepNumber"`
   328  
   329  	// ResponseCh is the channel in which the response to that request is expected.
   330  	ResponseCh chan<- Interaction `json:"-"`
   331  
   332  	// ControlCh is the channel we use to communicate state of the request to
   333  	// the UI. If the request gets canceled on the third-party application side,
   334  	// we close this channel and the UI, that is listening to it, has to react
   335  	// accordingly.
   336  	ControlCh chan error `json:"-"`
   337  }
   338  
   339  // WalletConnectionDecision is a specific response for interactor.RequestWalletConnectionReview.
   340  type WalletConnectionDecision struct {
   341  	// ConnectionApproval tells if the third-party application is authorized
   342  	// to connect to a wallet.
   343  	// The value is the string representation of a preferences.ConnectionApproval.
   344  	ConnectionApproval string `json:"connectionApproval"`
   345  }
   346  
   347  // Decision is a generic response for the following review requests:
   348  //   - RequestPermissionsReview
   349  //   - RequestTransactionReviewForSigning
   350  //   - RequestTransactionReviewForSending
   351  type Decision struct {
   352  	// Approved is set to true if the request is accepted by the user, false
   353  	// otherwise.
   354  	Approved bool `json:"approved"`
   355  }
   356  
   357  // EnteredPassphrase contains the passphrase of a given wallet the user
   358  // entered. It's a response to the interactor.RequestPassphrase.
   359  type EnteredPassphrase struct {
   360  	Passphrase string `json:"passphrase"`
   361  }
   362  
   363  // SelectedWallet contains the wallet chosen by the user for a given action.
   364  type SelectedWallet struct {
   365  	Wallet string `json:"wallet"`
   366  }
   367  
   368  // CancelRequest cancels a request that is waiting for user inputs.
   369  // It can't cancel a request when there is no response awaited.
   370  type CancelRequest struct{}
   371  
   372  // InteractionSessionBegan is a notification that is emitted when the interaction
   373  // session begin. It only carries informational value on a request lifecycle. This
   374  // is the first notification to be emitted and is always emitted when a request
   375  // comes in.
   376  type InteractionSessionBegan struct {
   377  	Workflow             string `json:"workflow"`
   378  	MaximumNumberOfSteps uint8  `json:"maximumNumberOfSteps"`
   379  }
   380  
   381  // InteractionSessionEnded is a notification that is emitted when the interaction
   382  // session ended. This is the last notification to be emitted and is always emitted,
   383  // regardless of the request status. It only carries informational value on a
   384  // request lifecycle. The success or failure status of a request is carried by
   385  // the interactor.RequestSucceeded and interactor.ErrorOccurred notifications,
   386  // respectively.
   387  // Nothing should be expected after receiving this notification.
   388  type InteractionSessionEnded struct{}
   389  
   390  // ErrorOccurred is a generic notification emitted when the something failed.
   391  // This notification can wrap an internal failure as much as a user input error.
   392  // Receiving this notification doesn't necessarily mean the overall
   393  // request failed. The request should be considered as failed when this notification
   394  // is followed by the interactor.InteractionSessionEnded notification.
   395  type ErrorOccurred struct {
   396  	// Type is an enumeration that gives information about the origin of the error.
   397  	// The value is the string representation of an api.ErrorType.
   398  	Type string `json:"type"`
   399  
   400  	// Error is the error message describing the reason of the failure.
   401  	Error string `json:"error"`
   402  }
   403  
   404  // RequestSucceeded is a generic notification emitted when the request succeeded,
   405  // meaning no error has been encountered.
   406  // This notification is emitted only once.
   407  type RequestSucceeded struct {
   408  	// Message can contain a custom success message.
   409  	Message string `json:"message"`
   410  
   411  	StepNumber uint8 `json:"stepNumber"`
   412  }
   413  
   414  // TransactionSucceeded is a notification sent when the sending of a
   415  // transaction succeeded. It replaces the RequestSucceeded notification as it
   416  // carries specific information that wallet front-ends may use for history.
   417  // This notification is emitted only once.
   418  type TransactionSucceeded struct {
   419  	// TxHash is the hash of the transaction that is used to uniquely identify
   420  	// a transaction. It can be used to retrieve a transaction in the explorer.
   421  	TxHash string `json:"txHash"`
   422  
   423  	// DeserializedInputData is the input data bundled in the transaction in a
   424  	// human-readable format.
   425  	DeserializedInputData string `json:"deserializedInputData"`
   426  
   427  	// Tx is the true representation of the transaction that is sent to the
   428  	// network.
   429  	Tx string `json:"tx"`
   430  
   431  	// SentAt is the time a which the transaction has been sent to the network.
   432  	// It's useful to build a list of the sending in a chronological order on
   433  	// the front-ends.
   434  	SentAt time.Time `json:"sentAt"`
   435  
   436  	// Node contains all the information related to the node selected for the
   437  	// sending of the transaction.
   438  	Node SelectedNode `json:"node"`
   439  
   440  	StepNumber uint8 `json:"stepNumber"`
   441  }
   442  
   443  // TransactionFailed is a notification sent when the sending of a
   444  // transaction failed for any reason. It replaces the ErrorOccurred notification
   445  // as it carries specific information that wallet front-ends may use for
   446  // investigation.
   447  // This notification is emitted only once.
   448  type TransactionFailed struct {
   449  	// DeserializedInputData is the input data bundled in the transaction in a
   450  	// human-readable format.
   451  	DeserializedInputData string `json:"deserializedInputData"`
   452  
   453  	// Tx is the true representation of the transaction that is sent to the
   454  	// network.
   455  	Tx string `json:"tx"`
   456  
   457  	// Error is the error message describing the reason of the failure.
   458  	Error error `json:"error"`
   459  
   460  	// SentAt is the time a which the transaction has been sent to the network.
   461  	// It's useful to build a list of the sending in a chronological order on
   462  	// the front-ends.
   463  	SentAt time.Time `json:"sentAt"`
   464  
   465  	// Node contains all the information related to the node selected for the
   466  	// sending of the transaction.
   467  	Node SelectedNode `json:"node"`
   468  
   469  	StepNumber uint8 `json:"stepNumber"`
   470  }
   471  
   472  type SelectedNode struct {
   473  	Host string `json:"host"`
   474  }
   475  
   476  // Log is a generic event that shouldn't be confused with a notification. A log
   477  // is conceptually different. Whatever the type (error, warning...), a log is just
   478  // an information about the internal processing that we think is worth to
   479  // broadcast to wallet front-ends. That said, it can be safely ignored if not
   480  // needed. That is where is differs from the notifications.
   481  type Log struct {
   482  	// Type is an enumeration that gives information about the level of log.
   483  	// The value is the string representation of an api.LogType.
   484  	Type string `json:"type"`
   485  
   486  	// Message is the log message itself.
   487  	Message string `json:"message"`
   488  }