1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 17:33:23 +01:00

First simple version of ui mocker

This commit is contained in:
Dan Finlay 2016-06-30 18:22:16 -07:00
parent 9a8ede2210
commit e55938ed15
22 changed files with 767 additions and 3 deletions

View File

@ -54,12 +54,14 @@ function setupControllerConnection (stream, cb) {
}
function getCurrentDomain (cb) {
const unknown = '<unknown>'
if (!chrome.tabs) return cb(null, unknown)
chrome.tabs.query({active: true, currentWindow: true}, function (results) {
var activeTab = results[0]
var currentUrl = activeTab && activeTab.url
var currentDomain = url.parse(currentUrl).host
if (!currentUrl) {
return cb(null, '<unknown>')
return cb(null, unknown)
}
cb(null, currentDomain)
})
@ -78,7 +80,7 @@ function setupApp (err, opts) {
alert(err.stack)
throw err
}
clearNotifications()
var container = document.getElementById('app-content')

29
development/beefy.js Normal file
View File

@ -0,0 +1,29 @@
const beefy = require('beefy')
const http = require('http')
const fs = require('fs')
const path = require('path')
const states = require('./states')
const statesPath = path.join(__dirname, 'states.js')
const statesJson = JSON.stringify(states)
fs.writeFileSync(statesPath, statesJson)
const port = 8124
const handler = beefy({
entries: ['mocker.js']
, cwd: __dirname
, live: true
, quiet: false
, bundlerFlags: ['-t', 'brfs']
})
console.dir(handler)
http.createServer(handler).listen(port)
console.log(`Now listening on port ${port}`)
function on404(req, resp) {
resp.writeHead(404, {})
resp.end('sorry folks!')
}

1
development/fonts Symbolic link
View File

@ -0,0 +1 @@
../app/fonts

18
development/genStates.js Normal file
View File

@ -0,0 +1,18 @@
const fs = require('fs')
const path = require('path')
const statesPath = path.join(__dirname, 'states')
const stateNames = fs.readdirSync(statesPath)
const states = stateNames.reduce((result, stateFileName) => {
const statePath = path.join(__dirname, 'states', stateFileName)
const stateFile = fs.readFileSync(statePath).toString()
const state = JSON.parse(stateFile)
result[stateFileName.split('.')[0].replace(/-/g, ' ', 'g')] = state
return result
}, {})
const result = `module.exports = ${JSON.stringify(states)}`
const statesJsonPath = path.join(__dirname, 'states.js')
fs.writeFileSync(statesJsonPath, result)

1
development/images Symbolic link
View File

@ -0,0 +1 @@
../app/images

37
development/index.html Normal file
View File

@ -0,0 +1,37 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>MetaMask</title>
</head>
<body>
<!-- app content -->
<div id="app-content"></div>
<script src="./bundle.js" type="text/javascript" charset="utf-8"></script>
<!-- design reference -->
<link rel="stylesheet" type="text/css" href="../ui/app/css/debug.css">
<div id="design-container">
<!-- persist scroll position on refresh -->
<script type="text/javascript">
var scrollElement = document.getElementById('design-container')
function getScrollPosition () {
var scrollTop = scrollElement.scrollTop, scrollLeft = scrollElement.scrollLeft
window.location.hash = 'scrollTop='+scrollTop+'&scrollLeft='+scrollLeft
}
window.onload = function () {
setInterval(getScrollPosition, 1000)
var hashLocation = window.location.hash.split('#')[1]
if (!hashLocation) return
var sections = hashLocation.split('&')
var scrollTop = sections[0].split('=')[1]
var scrollLeft = sections[1].split('=')[1]
scrollElement.scrollTop = scrollTop
scrollElement.scrollLeft = scrollLeft
}
</script>
</div>
</body>
</html>

18
development/mockStore.js Normal file
View File

@ -0,0 +1,18 @@
const createStore = require('redux').createStore
const applyMiddleware = require('redux').applyMiddleware
const thunkMiddleware = require('redux-thunk')
const createLogger = require('redux-logger')
const rootReducer = require('../ui/app/reducers')
module.exports = configureStore
const loggerMiddleware = createLogger()
const createStoreWithMiddleware = applyMiddleware(
thunkMiddleware,
loggerMiddleware
)(createStore)
function configureStore (initialState) {
return createStoreWithMiddleware(rootReducer, initialState)
}

67
development/mocker.js Normal file
View File

@ -0,0 +1,67 @@
const render = require('react-dom').render
const h = require('react-hyperscript')
const Root = require('../ui/app/root')
const configureStore = require('./mockStore')
const qs = require('qs')
const queryString = qs.parse(window.location.href)
let selectedView = queryString.view || 'account detail'
const MetaMaskUiCss = require('../ui/css')
const injectCss = require('inject-css')
const states = require('./states')
const firstState = states[selectedView]
updateQueryParams()
function updateQueryParams() {
const newParamsObj = {
view: selectedView,
}
const newQs = qs.stringify(newParamsObj)
//window.location.href = window.location.href.split('?')[0] + `?${newQs}`
}
const actions = {
_setAccountManager(){},
update: function(stateName) {
selectedView = stateName
updateQueryParams()
const newState = states[selectedView]
return {
type: 'GLOBAL_FORCE_UPDATE',
value: newState,
}
},
}
var css = MetaMaskUiCss()
injectCss(css)
const container = document.querySelector('#app-content')
// parse opts
var store = configureStore(states[selectedView])
// start app
render(
h('.super-dev-container', [
h('select', {
value: 'account-detail',
onChange:(event) => {
const selectedKey = event.target.value
store.dispatch(actions.update(selectedKey))
},
}, Object.keys(states).map((stateName) => {
return h('option', { value: stateName }, stateName)
})),
h(Root, {
store: store,
}),
]
), container)

1
development/states.js Normal file

File diff suppressed because one or more lines are too long

1
development/states.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,84 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"name": "Wallet 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"mayBeFauceting": false
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"name": "Wallet 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"mayBeFauceting": false
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"name": "Wallet 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"mayBeFauceting": false
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"name": "Wallet 4",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
}
},
"transactions": [],
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"network": "2",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail",
"detailView": null,
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "127.0.0.1:9966",
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

View File

@ -0,0 +1,85 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"name": "Wallet 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"mayBeFauceting": false
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"name": "Wallet 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"mayBeFauceting": false
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"name": "Wallet 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"mayBeFauceting": false
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"name": "Wallet 4",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"balance": "0x0",
"code": "0x",
"nonce": "0x0",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
}
},
"transactions": [],
"network": "2",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"seedWords": null
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"currentDomain": "extensions",
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": true
},
"identities": {}
}

View File

@ -0,0 +1,85 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6": {
"name": "Wallet 1",
"address": "0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6",
"mayBeFauceting": false
},
"0x843963b837841dad3b0f5969ff271108776616df": {
"name": "Wallet 2",
"address": "0x843963b837841dad3b0f5969ff271108776616df",
"mayBeFauceting": false
},
"0x2cb215323857bec1c91e5db10fe87379a5cf129a": {
"name": "Wallet 3",
"address": "0x2cb215323857bec1c91e5db10fe87379a5cf129a",
"mayBeFauceting": false
},
"0xc5091450b7548b0dce3a76b8d325929c39e648d1": {
"name": "Wallet 4",
"address": "0xc5091450b7548b0dce3a76b8d325929c39e648d1",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6"
},
"0x843963b837841dad3b0f5969ff271108776616df": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x843963b837841dad3b0f5969ff271108776616df"
},
"0x2cb215323857bec1c91e5db10fe87379a5cf129a": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x2cb215323857bec1c91e5db10fe87379a5cf129a"
},
"0xc5091450b7548b0dce3a76b8d325929c39e648d1": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0xc5091450b7548b0dce3a76b8d325929c39e648d1"
}
},
"transactions": [],
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
"network": "2",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x843963b837841dad3b0f5969ff271108776616df",
"seedWords": null
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"currentDomain": "testfaucet.metamask.io",
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": true
},
"identities": {}
}

View File

@ -0,0 +1,35 @@
{
"metamask": {
"isInitialized": false,
"isUnlocked": false,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {},
"unconfTxs": {},
"accounts": {},
"transactions": [],
"network": "2",
"seedWords": null,
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
}
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts",
"detailView": null
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "extensions",
"transForward": false,
"isLoading": false,
"warning": null
},
"identities": {}
}

View File

@ -0,0 +1,85 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"name": "Wallet 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"mayBeFauceting": false
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"name": "Wallet 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"mayBeFauceting": false
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"name": "Wallet 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"mayBeFauceting": false
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"name": "Wallet 4",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
},
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
"code": "0x",
"nonce": "0x0",
"balance": "0x0",
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
}
},
"transactions": [],
"network": "2",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"seedWords": null
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "info"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"currentDomain": "extensions",
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": true
},
"identities": {}
}

View File

@ -0,0 +1,84 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6": {
"name": "Wallet 1",
"address": "0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6",
"mayBeFauceting": false
},
"0x843963b837841dad3b0f5969ff271108776616df": {
"name": "Wallet 2",
"address": "0x843963b837841dad3b0f5969ff271108776616df",
"mayBeFauceting": false
},
"0x2cb215323857bec1c91e5db10fe87379a5cf129a": {
"name": "Wallet 3",
"address": "0x2cb215323857bec1c91e5db10fe87379a5cf129a",
"mayBeFauceting": false
},
"0xc5091450b7548b0dce3a76b8d325929c39e648d1": {
"name": "Wallet 4",
"address": "0xc5091450b7548b0dce3a76b8d325929c39e648d1",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x5f11b68b7d41633e74c6b18d8b8d147da52aedd6"
},
"0x843963b837841dad3b0f5969ff271108776616df": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x843963b837841dad3b0f5969ff271108776616df"
},
"0x2cb215323857bec1c91e5db10fe87379a5cf129a": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x2cb215323857bec1c91e5db10fe87379a5cf129a"
},
"0xc5091450b7548b0dce3a76b8d325929c39e648d1": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0xc5091450b7548b0dce3a76b8d325929c39e648d1"
}
},
"transactions": [],
"selectedAddress": "0x843963b837841dad3b0f5969ff271108776616df",
"network": "2",
"isConfirmed": true,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
},
"selectedAccount": "0x843963b837841dad3b0f5969ff271108776616df"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail"
},
"accountDetail": {
"subview": "transactions",
"accountExport": "none",
"privateKey": ""
},
"currentDomain": "testfaucet.metamask.io",
"transForward": false,
"isLoading": false,
"warning": null,
"scrollToBottom": false
},
"identities": {}
}

View File

@ -0,0 +1,35 @@
{
"metamask": {
"isInitialized": false,
"isUnlocked": false,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {},
"unconfTxs": {},
"accounts": {},
"transactions": [],
"network": "2",
"seedWords": null,
"isConfirmed": false,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
}
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accounts",
"detailView": null
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "extensions",
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

View File

@ -0,0 +1,70 @@
{
"metamask": {
"isInitialized": false,
"isUnlocked": true,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"name": "Wallet 1",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
"mayBeFauceting": false
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"name": "Wallet 2",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
"mayBeFauceting": false
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"name": "Wallet 3",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
"mayBeFauceting": false
}
},
"unconfTxs": {},
"accounts": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
},
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
"balance": "0x0",
"nonce": "0x0",
"code": "0x",
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
}
},
"transactions": [],
"network": "2",
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium",
"isConfirmed": false,
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
}
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "createVaultComplete",
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "extensions",
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

View File

@ -0,0 +1,21 @@
{
"metamask": {
"accounts": {},
"transactions": [],
"identities": {},
"network": "2",
"isInitialized": false,
"isUnlocked": false,
"seedWords": null,
"isConfirmed": false,
"unconfTxs": {},
"unconfMsgs": {},
"messages": [],
"provider": {
"type": "testnet"
}
},
"appState": {
"currentDomain": "extensions"
}
}

View File

@ -93,6 +93,7 @@
"mocha-eslint": "^2.1.1",
"mocha-jsdom": "^1.1.0",
"mocha-sinon": "^1.1.5",
"qs": "^6.2.0",
"sinon": "^1.17.3",
"tape": "^4.5.1",
"uglifyify": "^3.0.1",

View File

@ -17,7 +17,7 @@ function InfoScreen () {
InfoScreen.prototype.render = function () {
var state = this.props
var manifest = chrome.runtime.getManifest()
var manifest = chrome ? chrome.runtime.getManifest() : { version: '2.0.0' }
return (
h('.flex-column.flex-grow', [

View File

@ -13,6 +13,10 @@ function rootReducer (state, action) {
// clone
state = extend(state)
if (action.type === 'GLOBAL_FORCE_UPDATE') {
return action.value
}
//
// Identities
//