mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
meta - transactions - docs yo!
This commit is contained in:
parent
943eea043c
commit
eeb9390de8
92
app/scripts/controllers/transactions/README.md
Normal file
92
app/scripts/controllers/transactions/README.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Transaction Controller
|
||||||
|
|
||||||
|
Transaction Controller is an aggregate of sub-controllers and trackers
|
||||||
|
composing them in a way to be exposed to the metamask controller
|
||||||
|
|
||||||
|
- txStateManager
|
||||||
|
responsible for the state of a transaction and
|
||||||
|
storing the transaction
|
||||||
|
- pendingTxTracker
|
||||||
|
watching blocks for transactions to be include
|
||||||
|
and emitting confirmed events
|
||||||
|
- txGasUtil
|
||||||
|
gas calculations and safety buffering
|
||||||
|
- nonceTracker
|
||||||
|
calculating nonces
|
||||||
|
|
||||||
|
## flow digram of processing a transaction
|
||||||
|
|
||||||
|
![transaction-flow](../../../../docs/transaction-flow.png)
|
||||||
|
|
||||||
|
## txMeta's && txParams
|
||||||
|
|
||||||
|
A txMeta is the "meta" object it has all the random bits of info we need about a transaction on it. txParams are sacred every thing on txParams gets signed so it must
|
||||||
|
be a valid key and be hex prefixed except for the network number. Extra stuff must go on the txMeta!
|
||||||
|
|
||||||
|
Here is a txMeta too look at:
|
||||||
|
|
||||||
|
```js
|
||||||
|
txMeta = {
|
||||||
|
"id": 2828415030114568, // unique id for this txMeta used for look ups
|
||||||
|
"time": 1524094064821, // time of creation
|
||||||
|
"status": "confirmed",
|
||||||
|
"metamaskNetworkId": "1524091532133", //the network id for the transaction
|
||||||
|
"loadingDefaults": false, // used to tell the ui when we are done calculatyig gass defaults
|
||||||
|
"txParams": { // the txParams object
|
||||||
|
"from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
|
||||||
|
"to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
|
||||||
|
"value": "0x0",
|
||||||
|
"gasPrice": "0x3b9aca00",
|
||||||
|
"gas": "0x7b0c",
|
||||||
|
"nonce": "0x0"
|
||||||
|
},
|
||||||
|
"history": [{ //debug
|
||||||
|
"id": 2828415030114568,
|
||||||
|
"time": 1524094064821,
|
||||||
|
"status": "unapproved",
|
||||||
|
"metamaskNetworkId": "1524091532133",
|
||||||
|
"loadingDefaults": true,
|
||||||
|
"txParams": {
|
||||||
|
"from": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
|
||||||
|
"to": "0x8acce2391c0d510a6c5e5d8f819a678f79b7e675",
|
||||||
|
"value": "0x0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "add",
|
||||||
|
"path": "/txParams/gasPrice",
|
||||||
|
"value": "0x3b9aca00"
|
||||||
|
},
|
||||||
|
...], // I've removed most of history for this
|
||||||
|
"gasPriceSpecified": false, //weather or not the user/dapp has specified gasPrice
|
||||||
|
"gasLimitSpecified": false, //weather or not the user/dapp has specified gas
|
||||||
|
"estimatedGas": "5208",
|
||||||
|
"origin": "MetaMask", //debug
|
||||||
|
"nonceDetails": {
|
||||||
|
"params": {
|
||||||
|
"highestLocallyConfirmed": 0,
|
||||||
|
"highestSuggested": 0,
|
||||||
|
"nextNetworkNonce": 0
|
||||||
|
},
|
||||||
|
"local": {
|
||||||
|
"name": "local",
|
||||||
|
"nonce": 0,
|
||||||
|
"details": {
|
||||||
|
"startPoint": 0,
|
||||||
|
"highest": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"name": "network",
|
||||||
|
"nonce": 0,
|
||||||
|
"details": {
|
||||||
|
"baseCount": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rawTx": "0xf86980843b9aca00827b0c948acce2391c0d510a6c5e5d8f819a678f79b7e67580808602c5b5de66eea05c01a320b96ac730cb210ca56d2cb71fa360e1fc2c21fa5cf333687d18eb323fa02ed05987a6e5fd0f2459fcff80710b76b83b296454ad9a37594a0ccb4643ea90", // used for rebroadcast
|
||||||
|
"hash": "0xa45ba834b97c15e6ff4ed09badd04ecd5ce884b455eb60192cdc73bcc583972a",
|
||||||
|
"submittedTime": 1524094077902 // time of the attempt to submit the raw tx to the network, used in the ui to show the retry button
|
||||||
|
}
|
||||||
|
```
|
@ -25,14 +25,15 @@ const txUtils = require('./lib/util')
|
|||||||
|
|
||||||
|
|
||||||
@param {object} opts -
|
@param {object} opts -
|
||||||
- initState, initial transaction list default is an empty array<br>
|
@property {object} opts.initState initial transaction list default is an empty array
|
||||||
- networkStore, an observable store for network number<br>
|
@property {Object} opts.networkStore an observable store for network number
|
||||||
- blockTracker,<br>
|
@property {Object} opts.blockTracker
|
||||||
- provider,<br>
|
@property {Object} opts.provider
|
||||||
- signTransaction, function the signs an ethereumjs-tx<br>
|
@property {Object} opts.signTransaction function the signs an ethereumjs-tx
|
||||||
- getGasPrice, optional gas price calculator<br>
|
@property {function} opts.getGasPrice optional gas price calculator
|
||||||
- txHistoryLimit, number *optional* for limiting how many transactions are in state <br>
|
@property {function} opts.signTransaction ethTx signer that returns a rawTx
|
||||||
- preferencesStore,
|
@property {number} opts.txHistoryLimit number *optional* for limiting how many transactions are in state
|
||||||
|
@property {Object} opts.preferencesStore
|
||||||
@class
|
@class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -50,12 +51,12 @@ class TransactionController extends EventEmitter {
|
|||||||
this.query = new EthQuery(this.provider)
|
this.query = new EthQuery(this.provider)
|
||||||
this.txGasUtil = new TxGasUtil(this.provider)
|
this.txGasUtil = new TxGasUtil(this.provider)
|
||||||
|
|
||||||
|
this._mapMethods()
|
||||||
this.txStateManager = new TransactionStateManager({
|
this.txStateManager = new TransactionStateManager({
|
||||||
initState: opts.initState,
|
initState: opts.initState,
|
||||||
txHistoryLimit: opts.txHistoryLimit,
|
txHistoryLimit: opts.txHistoryLimit,
|
||||||
getNetwork: this.getNetwork.bind(this),
|
getNetwork: this.getNetwork.bind(this),
|
||||||
})
|
})
|
||||||
this._mapMethods()
|
|
||||||
this._onBootCleanUp()
|
this._onBootCleanUp()
|
||||||
|
|
||||||
this.store = this.txStateManager.store
|
this.store = this.txStateManager.store
|
||||||
@ -92,7 +93,10 @@ class TransactionController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds a tx to the txlist */
|
/**
|
||||||
|
Adds a tx to the txlist
|
||||||
|
@emits ${txMeta.id}:unapproved
|
||||||
|
*/
|
||||||
addTx (txMeta) {
|
addTx (txMeta) {
|
||||||
this.txStateManager.addTx(txMeta)
|
this.txStateManager.addTx(txMeta)
|
||||||
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
||||||
@ -172,6 +176,7 @@ add a new unapproved transaction to the pipeline
|
|||||||
async addTxGasDefaults (txMeta) {
|
async addTxGasDefaults (txMeta) {
|
||||||
const txParams = txMeta.txParams
|
const txParams = txMeta.txParams
|
||||||
// ensure value
|
// ensure value
|
||||||
|
txParams.value = txParams.value ? ethUtil.addHexPrefix(value) : '0x0',
|
||||||
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
|
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
|
||||||
let gasPrice = txParams.gasPrice
|
let gasPrice = txParams.gasPrice
|
||||||
if (!gasPrice) {
|
if (!gasPrice) {
|
||||||
@ -412,4 +417,4 @@ add a new unapproved transaction to the pipeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TransactionController
|
module.exports = TransactionController
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const jsonDiffer = require('fast-json-patch')
|
const jsonDiffer = require('fast-json-patch')
|
||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
|
/** @module*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
generateHistoryEntry,
|
generateHistoryEntry,
|
||||||
replayHistory,
|
replayHistory,
|
||||||
@ -8,7 +8,11 @@ module.exports = {
|
|||||||
migrateFromSnapshotsToDiffs,
|
migrateFromSnapshotsToDiffs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
converts non-initial history entries into diffs
|
||||||
|
@param longHistory {array}
|
||||||
|
@returns {array}
|
||||||
|
*/
|
||||||
function migrateFromSnapshotsToDiffs (longHistory) {
|
function migrateFromSnapshotsToDiffs (longHistory) {
|
||||||
return (
|
return (
|
||||||
longHistory
|
longHistory
|
||||||
@ -20,6 +24,17 @@ function migrateFromSnapshotsToDiffs (longHistory) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
generates an array of history objects sense the previous state.
|
||||||
|
The object has the keys opp(the operation preformed),
|
||||||
|
path(the key and if a nested object then each key will be seperated with a `/`)
|
||||||
|
value
|
||||||
|
with the first entry having the note
|
||||||
|
@param previousState {object} - the previous state of the object
|
||||||
|
@param newState {object} - the update object
|
||||||
|
@param note {string} - a optional note for the state change
|
||||||
|
@reurns {array}
|
||||||
|
*/
|
||||||
function generateHistoryEntry (previousState, newState, note) {
|
function generateHistoryEntry (previousState, newState, note) {
|
||||||
const entry = jsonDiffer.compare(previousState, newState)
|
const entry = jsonDiffer.compare(previousState, newState)
|
||||||
// Add a note to the first op, since it breaks if we append it to the entry
|
// Add a note to the first op, since it breaks if we append it to the entry
|
||||||
@ -27,11 +42,19 @@ function generateHistoryEntry (previousState, newState, note) {
|
|||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Recovers previous txMeta state obj
|
||||||
|
@return {object}
|
||||||
|
*/
|
||||||
function replayHistory (_shortHistory) {
|
function replayHistory (_shortHistory) {
|
||||||
const shortHistory = clone(_shortHistory)
|
const shortHistory = clone(_shortHistory)
|
||||||
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
|
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@param txMeta {object}
|
||||||
|
@returns {object} a clone object of the txMeta with out history
|
||||||
|
*/
|
||||||
function snapshotFromTxMeta (txMeta) {
|
function snapshotFromTxMeta (txMeta) {
|
||||||
// create txMeta snapshot for history
|
// create txMeta snapshot for history
|
||||||
const snapshot = clone(txMeta)
|
const snapshot = clone(txMeta)
|
||||||
|
@ -3,11 +3,15 @@ const {
|
|||||||
isValidAddress,
|
isValidAddress,
|
||||||
} = require('ethereumjs-util')
|
} = require('ethereumjs-util')
|
||||||
|
|
||||||
|
/**
|
||||||
|
@module
|
||||||
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
normalizeTxParams,
|
normalizeTxParams,
|
||||||
validateTxParams,
|
validateTxParams,
|
||||||
validateFrom,
|
validateFrom,
|
||||||
validateRecipient,
|
validateRecipient,
|
||||||
|
getFinalStates,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -16,22 +20,30 @@ const normalizers = {
|
|||||||
from: from => addHexPrefix(from).toLowerCase(),
|
from: from => addHexPrefix(from).toLowerCase(),
|
||||||
to: to => addHexPrefix(to).toLowerCase(),
|
to: to => addHexPrefix(to).toLowerCase(),
|
||||||
nonce: nonce => addHexPrefix(nonce),
|
nonce: nonce => addHexPrefix(nonce),
|
||||||
value: value => value ? addHexPrefix(value) : '0x0',
|
value: value => addHexPrefix(value),
|
||||||
data: data => addHexPrefix(data),
|
data: data => addHexPrefix(data),
|
||||||
gas: gas => addHexPrefix(gas),
|
gas: gas => addHexPrefix(gas),
|
||||||
gasPrice: gasPrice => addHexPrefix(gasPrice),
|
gasPrice: gasPrice => addHexPrefix(gasPrice),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
normalizes txParams
|
||||||
|
@param txParams {object}
|
||||||
|
@returns {object} normalized txParams
|
||||||
*/
|
*/
|
||||||
function normalizeTxParams (txParams) {
|
function normalizeTxParams (txParams) {
|
||||||
// apply only keys in the normalizers
|
// apply only keys in the normalizers
|
||||||
const normalizedTxParams = {}
|
const normalizedTxParams = {}
|
||||||
for (let key in normalizers) {
|
for (const key in normalizers) {
|
||||||
if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key])
|
if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key])
|
||||||
}
|
}
|
||||||
return normalizedTxParams
|
return normalizedTxParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
validates txParams
|
||||||
|
@param txParams {object}
|
||||||
|
*/
|
||||||
function validateTxParams (txParams) {
|
function validateTxParams (txParams) {
|
||||||
validateFrom(txParams)
|
validateFrom(txParams)
|
||||||
validateRecipient(txParams)
|
validateRecipient(txParams)
|
||||||
@ -47,11 +59,19 @@ function validateTxParams (txParams) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
validates the from field in txParams
|
||||||
|
@param txParams {object}
|
||||||
|
*/
|
||||||
function validateFrom (txParams) {
|
function validateFrom (txParams) {
|
||||||
if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`)
|
if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`)
|
||||||
if (!isValidAddress(txParams.from)) throw new Error('Invalid from address')
|
if (!isValidAddress(txParams.from)) throw new Error('Invalid from address')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
validates the to field in txParams
|
||||||
|
@param txParams {object}
|
||||||
|
*/
|
||||||
function validateRecipient (txParams) {
|
function validateRecipient (txParams) {
|
||||||
if (txParams.to === '0x' || txParams.to === null) {
|
if (txParams.to === '0x' || txParams.to === null) {
|
||||||
if (txParams.data) {
|
if (txParams.data) {
|
||||||
@ -64,3 +84,16 @@ function validateRecipient (txParams) {
|
|||||||
}
|
}
|
||||||
return txParams
|
return txParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@returns an {array} of states that can be considered final
|
||||||
|
*/
|
||||||
|
function getFinalStates () {
|
||||||
|
return [
|
||||||
|
'rejected', // the user has responded no!
|
||||||
|
'confirmed', // the tx has been included in a block.
|
||||||
|
'failed', // the tx failed for some reason, included on tx data.
|
||||||
|
'dropped', // the tx nonce was already used
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
const EthQuery = require('ethjs-query')
|
const EthQuery = require('ethjs-query')
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const Mutex = require('await-semaphore').Mutex
|
const Mutex = require('await-semaphore').Mutex
|
||||||
|
/**
|
||||||
|
@param opts {object} -
|
||||||
|
@property {Object} opts.provider a ethereum provider
|
||||||
|
@property {function} opts.getPendingTransactions a function that returns an array of txMeta
|
||||||
|
whos status is `submitted`
|
||||||
|
@property {function} opts.getConfirmedTransactions a function that returns an array of txMeta
|
||||||
|
whos status is `confirmed`
|
||||||
|
@class
|
||||||
|
*/
|
||||||
class NonceTracker {
|
class NonceTracker {
|
||||||
|
|
||||||
constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
|
constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
|
||||||
@ -12,6 +20,9 @@ class NonceTracker {
|
|||||||
this.lockMap = {}
|
this.lockMap = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@returns {object} with the key releaseLock (the gloabl mutex)
|
||||||
|
*/
|
||||||
async getGlobalLock () {
|
async getGlobalLock () {
|
||||||
const globalMutex = this._lookupMutex('global')
|
const globalMutex = this._lookupMutex('global')
|
||||||
// await global mutex free
|
// await global mutex free
|
||||||
@ -19,8 +30,19 @@ class NonceTracker {
|
|||||||
return { releaseLock }
|
return { releaseLock }
|
||||||
}
|
}
|
||||||
|
|
||||||
// releaseLock must be called
|
/**
|
||||||
// releaseLock must be called after adding signed tx to pending transactions (or discarding)
|
this will return an object with the `nextNonce` `nonceDetails` which is an
|
||||||
|
object with:
|
||||||
|
highestLocallyConfirmed (nonce),
|
||||||
|
highestSuggested (either the network nonce or the highestLocallyConfirmed nonce),
|
||||||
|
nextNetworkNonce (the nonce suggested by the network),
|
||||||
|
and the releaseLock
|
||||||
|
<br>note: releaseLock must be called after adding signed tx to pending transactions
|
||||||
|
(or discarding)<br>
|
||||||
|
|
||||||
|
@param address {string} the hex string for the address whos nonce we are calculating
|
||||||
|
@returns {object}
|
||||||
|
*/
|
||||||
async getNonceLock (address) {
|
async getNonceLock (address) {
|
||||||
// await global mutex free
|
// await global mutex free
|
||||||
await this._globalMutexFree()
|
await this._globalMutexFree()
|
||||||
|
@ -8,10 +8,10 @@ const EthQuery = require('ethjs-query')
|
|||||||
As well as continues broadcast while in the pending state
|
As well as continues broadcast while in the pending state
|
||||||
<br>
|
<br>
|
||||||
@param config {object} - non optional configuration object consists of:
|
@param config {object} - non optional configuration object consists of:
|
||||||
<br>provider
|
@property {Object} config.provider
|
||||||
<br>nonceTracker: see nonce tracker
|
@property {Object} config.nonceTracker see nonce tracker
|
||||||
<br>getPendingTransactions: a function for getting an array of transactions,
|
@property {function} config.getPendingTransactions a function for getting an array of transactions,
|
||||||
<br>publishTransaction: a async function for publishing raw transactions,
|
@property {function} config.publishTransaction a async function for publishing raw transactions,
|
||||||
|
|
||||||
|
|
||||||
@class
|
@class
|
||||||
@ -220,4 +220,4 @@ class PendingTransactionTracker extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PendingTransactionTracker
|
module.exports = PendingTransactionTracker
|
||||||
|
@ -11,6 +11,7 @@ const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
|
|||||||
tx-utils are utility methods for Transaction manager
|
tx-utils are utility methods for Transaction manager
|
||||||
its passed ethquery
|
its passed ethquery
|
||||||
and used to do things like calculate gas of a tx.
|
and used to do things like calculate gas of a tx.
|
||||||
|
@param provider {object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = class TxGasUtil {
|
module.exports = class TxGasUtil {
|
||||||
|
@ -1,22 +1,33 @@
|
|||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const EventEmitter = require('events')
|
const EventEmitter = require('events')
|
||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const createId = require('../../lib/random-id')
|
|
||||||
const ethUtil = require('ethereumjs-util')
|
const ethUtil = require('ethereumjs-util')
|
||||||
const txStateHistoryHelper = require('./lib/tx-state-history-helper')
|
const txStateHistoryHelper = require('./lib/tx-state-history-helper')
|
||||||
|
const createId = require('../../lib/random-id')
|
||||||
// STATUS METHODS
|
const { getFinalStates } = require('./lib/util')
|
||||||
// statuses:
|
/**
|
||||||
// - `'unapproved'` the user has not responded
|
TransactionStateManager is responsible for the state of a transaction and
|
||||||
// - `'rejected'` the user has responded no!
|
storing the transaction
|
||||||
// - `'approved'` the user has approved the tx
|
it also has some convenience methods for finding subsets of transactions
|
||||||
// - `'signed'` the tx is signed
|
*
|
||||||
// - `'submitted'` the tx is sent to a server
|
*STATUS METHODS
|
||||||
// - `'confirmed'` the tx has been included in a block.
|
<br>statuses:
|
||||||
// - `'failed'` the tx failed for some reason, included on tx data.
|
<br> - `'unapproved'` the user has not responded
|
||||||
// - `'dropped'` the tx nonce was already used
|
<br> - `'rejected'` the user has responded no!
|
||||||
|
<br> - `'approved'` the user has approved the tx
|
||||||
module.exports = class TransactionStateManager extends EventEmitter {
|
<br> - `'signed'` the tx is signed
|
||||||
|
<br> - `'submitted'` the tx is sent to a server
|
||||||
|
<br> - `'confirmed'` the tx has been included in a block.
|
||||||
|
<br> - `'failed'` the tx failed for some reason, included on tx data.
|
||||||
|
<br> - `'dropped'` the tx nonce was already used
|
||||||
|
@param opts {object} -
|
||||||
|
@property {object} opts.initState with the key transaction {array}
|
||||||
|
@property {number} opts.txHistoryLimit limit for how many finished
|
||||||
|
transactions can hang around in state
|
||||||
|
@property {function} opts.getNetwork return network number
|
||||||
|
@class
|
||||||
|
*/
|
||||||
|
class TransactionStateManager extends EventEmitter {
|
||||||
constructor ({ initState, txHistoryLimit, getNetwork }) {
|
constructor ({ initState, txHistoryLimit, getNetwork }) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
@ -28,6 +39,10 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
this.getNetwork = getNetwork
|
this.getNetwork = getNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@param opts {object} - the object to use when overwriting defaults
|
||||||
|
@returns {txMeta} the default txMeta object
|
||||||
|
*/
|
||||||
generateTxMeta (opts) {
|
generateTxMeta (opts) {
|
||||||
return extend({
|
return extend({
|
||||||
id: createId(),
|
id: createId(),
|
||||||
@ -38,17 +53,25 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
}, opts)
|
}, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@returns {array} of txMetas that have been filtered for only the current network
|
||||||
|
*/
|
||||||
getTxList () {
|
getTxList () {
|
||||||
const network = this.getNetwork()
|
const network = this.getNetwork()
|
||||||
const fullTxList = this.getFullTxList()
|
const fullTxList = this.getFullTxList()
|
||||||
return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network)
|
return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@returns {array} of all the txMetas in store
|
||||||
|
*/
|
||||||
getFullTxList () {
|
getFullTxList () {
|
||||||
return this.store.getState().transactions
|
return this.store.getState().transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the tx list
|
/**
|
||||||
|
@returns {array} the tx list whos status is unapproved
|
||||||
|
*/
|
||||||
getUnapprovedTxList () {
|
getUnapprovedTxList () {
|
||||||
const txList = this.getTxsByMetaData('status', 'unapproved')
|
const txList = this.getTxsByMetaData('status', 'unapproved')
|
||||||
return txList.reduce((result, tx) => {
|
return txList.reduce((result, tx) => {
|
||||||
@ -57,18 +80,35 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@param address {string} - hex prefixed address to sort the txMetas for [optional]
|
||||||
|
@returns {array} the tx list whos status is submitted
|
||||||
|
*/
|
||||||
getPendingTransactions (address) {
|
getPendingTransactions (address) {
|
||||||
const opts = { status: 'submitted' }
|
const opts = { status: 'submitted' }
|
||||||
if (address) opts.from = address
|
if (address) opts.from = address
|
||||||
return this.getFilteredTxList(opts)
|
return this.getFilteredTxList(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@param address {string} - hex prefixed address to sort the txMetas for [optional]
|
||||||
|
@returns {array} the tx list whos status is confirmed
|
||||||
|
*/
|
||||||
getConfirmedTransactions (address) {
|
getConfirmedTransactions (address) {
|
||||||
const opts = { status: 'confirmed' }
|
const opts = { status: 'confirmed' }
|
||||||
if (address) opts.from = address
|
if (address) opts.from = address
|
||||||
return this.getFilteredTxList(opts)
|
return this.getFilteredTxList(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Adds the txMeta to the list of transactions in the store.
|
||||||
|
if the list is over txHistoryLimit it will remove a transaction that
|
||||||
|
is in its final state
|
||||||
|
it will allso add the key `history` to the txMeta with the snap shot of the original
|
||||||
|
object
|
||||||
|
@param txMeta {object}
|
||||||
|
@returns {object} the txMeta
|
||||||
|
*/
|
||||||
addTx (txMeta) {
|
addTx (txMeta) {
|
||||||
this.once(`${txMeta.id}:signed`, function (txId) {
|
this.once(`${txMeta.id}:signed`, function (txId) {
|
||||||
this.removeAllListeners(`${txMeta.id}:rejected`)
|
this.removeAllListeners(`${txMeta.id}:rejected`)
|
||||||
@ -93,7 +133,7 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
// not tx's that are pending or unapproved
|
// not tx's that are pending or unapproved
|
||||||
if (txCount > txHistoryLimit - 1) {
|
if (txCount > txHistoryLimit - 1) {
|
||||||
const index = transactions.findIndex((metaTx) => {
|
const index = transactions.findIndex((metaTx) => {
|
||||||
return this.getFinalStates().includes(metaTx.status)
|
return getFinalStates().includes(metaTx.status)
|
||||||
})
|
})
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
transactions.splice(index, 1)
|
transactions.splice(index, 1)
|
||||||
@ -103,12 +143,21 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
this._saveTxList(transactions)
|
this._saveTxList(transactions)
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
// gets tx by Id and returns it
|
/**
|
||||||
|
@param txId {number}
|
||||||
|
@returns {object} the txMeta who matches the given id if none found
|
||||||
|
for the network returns undefined
|
||||||
|
*/
|
||||||
getTx (txId) {
|
getTx (txId) {
|
||||||
const txMeta = this.getTxsByMetaData('id', txId)[0]
|
const txMeta = this.getTxsByMetaData('id', txId)[0]
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
updates the txMeta in the list and adds a history entry
|
||||||
|
@param txMeta {object} - the txMeta to update
|
||||||
|
@param note {string} - a not about the update for history
|
||||||
|
*/
|
||||||
updateTx (txMeta, note) {
|
updateTx (txMeta, note) {
|
||||||
// validate txParams
|
// validate txParams
|
||||||
if (txMeta.txParams) {
|
if (txMeta.txParams) {
|
||||||
@ -136,15 +185,22 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// merges txParams obj onto txData.txParams
|
/**
|
||||||
// use extend to ensure that all fields are filled
|
merges txParams obj onto txMeta.txParams
|
||||||
|
use extend to ensure that all fields are filled
|
||||||
|
@param txId {number} - the id of the txMeta
|
||||||
|
@param txParams {object} - the updated txParams
|
||||||
|
*/
|
||||||
updateTxParams (txId, txParams) {
|
updateTxParams (txId, txParams) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.txParams = extend(txMeta.txParams, txParams)
|
txMeta.txParams = extend(txMeta.txParams, txParams)
|
||||||
this.updateTx(txMeta, `txStateManager#updateTxParams`)
|
this.updateTx(txMeta, `txStateManager#updateTxParams`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// validates txParams members by type
|
/**
|
||||||
|
validates txParams members by type
|
||||||
|
@param txParams {object} - txParams to validate
|
||||||
|
*/
|
||||||
validateTxParams (txParams) {
|
validateTxParams (txParams) {
|
||||||
Object.keys(txParams).forEach((key) => {
|
Object.keys(txParams).forEach((key) => {
|
||||||
const value = txParams[key]
|
const value = txParams[key]
|
||||||
@ -161,17 +217,18 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Takes an object of fields to search for eg:
|
@param opts {object} - an object of fields to search for eg:<br>
|
||||||
let thingsToLookFor = {
|
let <code>thingsToLookFor = {<br>
|
||||||
to: '0x0..',
|
to: '0x0..',<br>
|
||||||
from: '0x0..',
|
from: '0x0..',<br>
|
||||||
status: 'signed',
|
status: 'signed',<br>
|
||||||
err: undefined,
|
err: undefined,<br>
|
||||||
}
|
}<br></code>
|
||||||
and returns a list of tx with all
|
@returns a {array} of txMeta with all
|
||||||
options matching
|
options matching
|
||||||
|
*/
|
||||||
|
/*
|
||||||
****************HINT****************
|
****************HINT****************
|
||||||
| `err: undefined` is like looking |
|
| `err: undefined` is like looking |
|
||||||
| for a tx with no err |
|
| for a tx with no err |
|
||||||
@ -192,7 +249,14 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
})
|
})
|
||||||
return filteredTxList
|
return filteredTxList
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
|
||||||
|
@param key {string} - the key to check
|
||||||
|
@param value - the value your looking for
|
||||||
|
@param txList {array} - [optional] the list to search. default is the txList
|
||||||
|
from txStateManager#getTxList
|
||||||
|
@returns {array} a list of txMetas who matches the search params
|
||||||
|
*/
|
||||||
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
||||||
return txList.filter((txMeta) => {
|
return txList.filter((txMeta) => {
|
||||||
if (txMeta.txParams[key]) {
|
if (txMeta.txParams[key]) {
|
||||||
@ -205,33 +269,51 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
|
|
||||||
// get::set status
|
// get::set status
|
||||||
|
|
||||||
// should return the status of the tx.
|
/**
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
@return {string} the status of the tx.
|
||||||
|
*/
|
||||||
getTxStatus (txId) {
|
getTxStatus (txId) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
return txMeta.status
|
return txMeta.status
|
||||||
}
|
}
|
||||||
|
|
||||||
// should update the status of the tx to 'rejected'.
|
/**
|
||||||
|
should update the status of the tx to 'rejected'.
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
*/
|
||||||
setTxStatusRejected (txId) {
|
setTxStatusRejected (txId) {
|
||||||
this._setTxStatus(txId, 'rejected')
|
this._setTxStatus(txId, 'rejected')
|
||||||
}
|
}
|
||||||
|
|
||||||
// should update the status of the tx to 'unapproved'.
|
/**
|
||||||
|
should update the status of the tx to 'unapproved'.
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
*/
|
||||||
setTxStatusUnapproved (txId) {
|
setTxStatusUnapproved (txId) {
|
||||||
this._setTxStatus(txId, 'unapproved')
|
this._setTxStatus(txId, 'unapproved')
|
||||||
}
|
}
|
||||||
// should update the status of the tx to 'approved'.
|
/**
|
||||||
|
should update the status of the tx to 'approved'.
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
*/
|
||||||
setTxStatusApproved (txId) {
|
setTxStatusApproved (txId) {
|
||||||
this._setTxStatus(txId, 'approved')
|
this._setTxStatus(txId, 'approved')
|
||||||
}
|
}
|
||||||
|
|
||||||
// should update the status of the tx to 'signed'.
|
/**
|
||||||
|
should update the status of the tx to 'signed'.
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
*/
|
||||||
setTxStatusSigned (txId) {
|
setTxStatusSigned (txId) {
|
||||||
this._setTxStatus(txId, 'signed')
|
this._setTxStatus(txId, 'signed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// should update the status of the tx to 'submitted'.
|
/**
|
||||||
// and add a time stamp for when it was called
|
should update the status of the tx to 'submitted'.
|
||||||
|
and add a time stamp for when it was called
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
*/
|
||||||
setTxStatusSubmitted (txId) {
|
setTxStatusSubmitted (txId) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.submittedTime = (new Date()).getTime()
|
txMeta.submittedTime = (new Date()).getTime()
|
||||||
@ -239,17 +321,29 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
this._setTxStatus(txId, 'submitted')
|
this._setTxStatus(txId, 'submitted')
|
||||||
}
|
}
|
||||||
|
|
||||||
// should update the status of the tx to 'confirmed'.
|
/**
|
||||||
|
should update the status of the tx to 'confirmed'.
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
*/
|
||||||
setTxStatusConfirmed (txId) {
|
setTxStatusConfirmed (txId) {
|
||||||
this._setTxStatus(txId, 'confirmed')
|
this._setTxStatus(txId, 'confirmed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// should update the status dropped
|
/**
|
||||||
|
should update the status of the tx to 'dropped'.
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
*/
|
||||||
setTxStatusDropped (txId) {
|
setTxStatusDropped (txId) {
|
||||||
this._setTxStatus(txId, 'dropped')
|
this._setTxStatus(txId, 'dropped')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
should update the status of the tx to 'failed'.
|
||||||
|
and put the error on the txMeta
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
@param err {erroObject} - error object
|
||||||
|
*/
|
||||||
setTxStatusFailed (txId, err) {
|
setTxStatusFailed (txId, err) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.err = {
|
txMeta.err = {
|
||||||
@ -260,16 +354,11 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
this._setTxStatus(txId, 'failed')
|
this._setTxStatus(txId, 'failed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns an array of states that can be considered final
|
/**
|
||||||
getFinalStates () {
|
Removes transaction from the given address for the current network
|
||||||
return [
|
from the txList
|
||||||
'rejected', // the user has responded no!
|
@param address {string} - hex string of the from address on the txParams to remove
|
||||||
'confirmed', // the tx has been included in a block.
|
*/
|
||||||
'failed', // the tx failed for some reason, included on tx data.
|
|
||||||
'dropped', // the tx nonce was already used
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
wipeTransactions (address) {
|
wipeTransactions (address) {
|
||||||
// network only tx
|
// network only tx
|
||||||
const txs = this.getFullTxList()
|
const txs = this.getFullTxList()
|
||||||
@ -295,6 +384,14 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
// - `'confirmed'` the tx has been included in a block.
|
// - `'confirmed'` the tx has been included in a block.
|
||||||
// - `'failed'` the tx failed for some reason, included on tx data.
|
// - `'failed'` the tx failed for some reason, included on tx data.
|
||||||
// - `'dropped'` the tx nonce was already used
|
// - `'dropped'` the tx nonce was already used
|
||||||
|
|
||||||
|
/**
|
||||||
|
@param txId {number} - the txMeta Id
|
||||||
|
@param status {string} - the status to set on the txMeta
|
||||||
|
@emits tx:status-update - passes txId and status
|
||||||
|
@emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
|
||||||
|
@emits update:badge
|
||||||
|
*/
|
||||||
_setTxStatus (txId, status) {
|
_setTxStatus (txId, status) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.status = status
|
txMeta.status = status
|
||||||
@ -307,9 +404,14 @@ module.exports = class TransactionStateManager extends EventEmitter {
|
|||||||
this.emit('update:badge')
|
this.emit('update:badge')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saves the new/updated txList.
|
/**
|
||||||
|
Saves the new/updated txList.
|
||||||
|
@param transactions {array} - the list of transactions to save
|
||||||
|
*/
|
||||||
// Function is intended only for internal use
|
// Function is intended only for internal use
|
||||||
_saveTxList (transactions) {
|
_saveTxList (transactions) {
|
||||||
this.store.updateState({ transactions })
|
this.store.updateState({ transactions })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = TransactionStateManager
|
||||||
|
BIN
docs/transaction-flow.png
Normal file
BIN
docs/transaction-flow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 138 KiB |
Loading…
Reference in New Issue
Block a user