@exodus/headless 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +150 -0
- package/package.json +51 -0
- package/src/api.js +34 -0
- package/src/application.js +246 -0
- package/src/dependencies/adapters.js +6 -0
- package/src/dependencies/atoms.js +15 -0
- package/src/dependencies/configs.js +14 -0
- package/src/dependencies/index.js +44 -0
- package/src/dependencies/modules.js +46 -0
- package/src/dependencies/utils.js +14 -0
- package/src/index.js +38 -0
- package/src/ioc.js +23 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## 1.1.0 (2023-03-28)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- add headless module ([#996](https://github.com/ExodusMovement/exodus-hydra/issues/996)) ([0ea954e](https://github.com/ExodusMovement/exodus-hydra/commit/0ea954e378f1afd1881ddcdde63546772157327e))
|
package/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# @exodus/headless
|
|
2
|
+
|
|
3
|
+
The headless Exodus wallet SDK
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import createExodus from '@exodus/headless'
|
|
9
|
+
import Emitter from '@exodus/wild-emitter'
|
|
10
|
+
|
|
11
|
+
// 1. Create port. Acts as an event bus between headless wallet and client
|
|
12
|
+
const port = new Emitter()
|
|
13
|
+
|
|
14
|
+
// 2. Instantiate client-specific adapters and config. More details below
|
|
15
|
+
const adapters = {}
|
|
16
|
+
const config = {}
|
|
17
|
+
|
|
18
|
+
// 3. Create Exodus container
|
|
19
|
+
const container = createExodus({ port, adapters, config })
|
|
20
|
+
|
|
21
|
+
// 4. Register external modules. Does not support overriding modules at the moment.
|
|
22
|
+
container.register({
|
|
23
|
+
definition: {
|
|
24
|
+
id: 'remoteConfig',
|
|
25
|
+
factory: createRemoteConfig,
|
|
26
|
+
dependencies: ['config', 'fetch', 'logger'],
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// 5. Resolve exodus instance
|
|
31
|
+
const exodus = container.resolve()
|
|
32
|
+
|
|
33
|
+
// 6. Use it!
|
|
34
|
+
await exodus.wallet.create({ passphrase: 'my-super-secure-passphrase' })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Port
|
|
38
|
+
|
|
39
|
+
Event bus between headless wallet and client
|
|
40
|
+
|
|
41
|
+
| Method | Type | Description |
|
|
42
|
+
| -------------------- | ------------------------------------------ | ------------------------------------------ |
|
|
43
|
+
| subscribe | `({ type: string, payload: any }) => void` | Subscribe to receive events. |
|
|
44
|
+
| unsubscribe(handler) | `({ type: string, payload: any }) => void` | Unsubscribe handler from receiving events. |
|
|
45
|
+
| emit | `(type: string, payload: any) => void` | Emit an event. |
|
|
46
|
+
|
|
47
|
+
## Adapters
|
|
48
|
+
|
|
49
|
+
Adapters are client-specific implementations of defined APIs needed for the wallet to function.
|
|
50
|
+
|
|
51
|
+
New adapters will be required by this module as we migrate more modules into it. Below are explained the ones that this module requires at the date.
|
|
52
|
+
|
|
53
|
+
### assetsModule
|
|
54
|
+
|
|
55
|
+
`AssetsModule` implementation. Currently being injected as each of our clients have different implementations.
|
|
56
|
+
|
|
57
|
+
### createLogger
|
|
58
|
+
|
|
59
|
+
A function that returns an instance of the Logger class with the given namespace.
|
|
60
|
+
|
|
61
|
+
| Parameter | Type | Description |
|
|
62
|
+
| --------- | -------- | ----------------------------- |
|
|
63
|
+
| namespace | `string` | The namespace for the logger. |
|
|
64
|
+
|
|
65
|
+
The returned Logger instance has the following methods:
|
|
66
|
+
|
|
67
|
+
| Method | Type | Description |
|
|
68
|
+
| ------ | -------------------------- | ----------------------------------- |
|
|
69
|
+
| trace | `(...args: any[]) => void` | Log a message with the trace level. |
|
|
70
|
+
| debug | `(...args: any[]) => void` | Log a message with the debug level. |
|
|
71
|
+
| log | `(...args: any[]) => void` | Log a message with the log level. |
|
|
72
|
+
| info | `(...args: any[]) => void` | Log a message with the info level. |
|
|
73
|
+
| warn | `(...args: any[]) => void` | Log a message with the warn level. |
|
|
74
|
+
| error | `(...args: any[]) => void` | Log a message with the error level. |
|
|
75
|
+
|
|
76
|
+
Each method takes any number of arguments, which will be logged as a message with the corresponding log level. The message will be prefixed with the logger's namespace.
|
|
77
|
+
|
|
78
|
+
### legacyPrivToPub
|
|
79
|
+
|
|
80
|
+
Legacy private to public key transformation function.
|
|
81
|
+
|
|
82
|
+
An object where each key is an asset name and the value is a function that takes a `Buffer` and returns a `Buffer`.
|
|
83
|
+
|
|
84
|
+
### unsafeStorage
|
|
85
|
+
|
|
86
|
+
Unsafe (raw text) storage instance. Used internally by modules to persist data in client underlying storage layer.
|
|
87
|
+
|
|
88
|
+
In most cases you probably want to use encrypted storage instead (not yet migrated).
|
|
89
|
+
|
|
90
|
+
Check [storage-spec](https://github.com/ExodusMovement/exodus-hydra/tree/master/modules/storage-spec) for more details about Storage API
|
|
91
|
+
|
|
92
|
+
### seco
|
|
93
|
+
|
|
94
|
+
An object with methods to encrypt and decrypt strings.
|
|
95
|
+
|
|
96
|
+
| Method | Type | Description |
|
|
97
|
+
| ------------- | -------------------------------------------------------------- | --------------- |
|
|
98
|
+
| encryptString | `({ data: string, key: string, encoding?: string }) => string` | Encrypt string. |
|
|
99
|
+
| decryptString | `({ data: string, key: string, encoding?: string }) => string` | Decrypt string. |
|
|
100
|
+
|
|
101
|
+
### passphraseCache
|
|
102
|
+
|
|
103
|
+
An instance of the PassphraseCache class.
|
|
104
|
+
|
|
105
|
+
| Method | Type | Description |
|
|
106
|
+
| ------------- | ------------------------------ | ------------------------------------------------------------ |
|
|
107
|
+
| set | `(passphrase: string) => void` | Set the passphrase with the given value. |
|
|
108
|
+
| get | `() => string` | Get the current passphrase value. |
|
|
109
|
+
| changeTtl | `(ttl: number) => void` | Change the time-to-live (TTL) value of the passphrase cache. |
|
|
110
|
+
| clear | `() => void` | Clear the passphrase cache. |
|
|
111
|
+
| scheduleClear | `() => void` | |
|
|
112
|
+
|
|
113
|
+
## Config
|
|
114
|
+
|
|
115
|
+
An object with additional configuration options for the Exodus instance.
|
|
116
|
+
|
|
117
|
+
Currently not used.
|
|
118
|
+
|
|
119
|
+
## Headless API
|
|
120
|
+
|
|
121
|
+
## wallet
|
|
122
|
+
|
|
123
|
+
> Type: object
|
|
124
|
+
|
|
125
|
+
| Method | Type | Description |
|
|
126
|
+
| ------------------------ | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
127
|
+
| exists | `async () => boolean` | Checks if a wallet exists on the device. |
|
|
128
|
+
| create | `async ({ passphrase?: string }) => void` | Creates new wallet |
|
|
129
|
+
| import | `async ({ mnemonic: string, passphrase?: string }) => void` | Imports existing wallet |
|
|
130
|
+
| delete | `async ({ passphrase?: string }) => void` | Deletes wallet. Passphrase is mandatory if wallet was created with one |
|
|
131
|
+
| lock | `async () => void` | Locks wallet |
|
|
132
|
+
| unlock | `async ({ passphrase?: string }) => void` | Unlocks wallet. Passphrase is mandatory if wallet was created with one |
|
|
133
|
+
| isLocked | `async () => boolean` | Checks if wallet is locked |
|
|
134
|
+
| getMnemonic | `async () => void` | Sets wallet as backed up |
|
|
135
|
+
| changePassphrase | `async ({ currentPassphrase?: string. newPassphrase: string }) => void` | Change passphrase. Current passphrase is mandatory if wallet was created with one |
|
|
136
|
+
| restoreFromCurrentPhrase | `async ({ passphrase?: string }) => void` | Restore current wallet. Passphrase is mandatory if wallet was created with one |
|
|
137
|
+
| load | `async () => void` | Loads UI by rehydratating data through port (`BE Only`) |
|
|
138
|
+
| unload | `async () => void` | Unloads UI by rehydratating data through port (`BE Only`) |
|
|
139
|
+
| changeLockTimer | `async ({ ttl: number }) => void` | Change auto unlock ttl (`BE Only`) |
|
|
140
|
+
|
|
141
|
+
## isMnemonicValid
|
|
142
|
+
|
|
143
|
+
> Type: function
|
|
144
|
+
|
|
145
|
+
| Argument | Type | Description |
|
|
146
|
+
| -------- | ---------- | ---------------------------- |
|
|
147
|
+
| mnemonic | `string` | Mnemonic to check validity. |
|
|
148
|
+
| wordlist | `string[]` | Words list to check against. |
|
|
149
|
+
|
|
150
|
+
**Returns**: `boolean`
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@exodus/headless",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "The headless Exodus wallet SDK",
|
|
5
|
+
"author": "Exodus Movement Inc",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ExodusMovement/exodus-hydra.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/ExodusMovement/exodus-hydra/tree/master/modules/headless",
|
|
12
|
+
"license": "UNLICENSED",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ExodusMovement/exodus-hydra/issues?q=is%3Aissue+is%3Aopen+label%3Aheadless"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"README.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
20
|
+
"!**/__tests__",
|
|
21
|
+
"!**/*.test.js"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"lint": "eslint . --ignore-path ../../.gitignore",
|
|
25
|
+
"lint:fix": "yarn lint --fix",
|
|
26
|
+
"test": "jest"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@exodus/atoms": "^2.9.0",
|
|
30
|
+
"@exodus/dependency-injection": "^1.2.0",
|
|
31
|
+
"@exodus/dependency-preprocessors": "^2.0.2",
|
|
32
|
+
"@exodus/fetch": "^1.2.1",
|
|
33
|
+
"@exodus/key-identifier-provider": "^1.1.1",
|
|
34
|
+
"@exodus/keychain": "^3.2.1",
|
|
35
|
+
"@exodus/module": "^1.0.0",
|
|
36
|
+
"@exodus/wallet": "4.1.0",
|
|
37
|
+
"@exodus/wallet-compatibility-modes": "1.0.1",
|
|
38
|
+
"bip39": "2.6.0",
|
|
39
|
+
"lodash": "https://registry.yarnpkg.com/@exodus/lodash/-/lodash-4.17.21-exodus.2.tgz",
|
|
40
|
+
"minimalistic-assert": "^1.0.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@exodus/ethereum-lib": "^2.22.2",
|
|
44
|
+
"@exodus/solana-lib": "^1.3.11",
|
|
45
|
+
"@exodus/storage-memory": "^1.1.0",
|
|
46
|
+
"@exodus/wild-emitter": "^1.0.0",
|
|
47
|
+
"eslint": "^8.33.0",
|
|
48
|
+
"jest": "^29.1.2"
|
|
49
|
+
},
|
|
50
|
+
"gitHead": "e5126f0fa052ef08113c4eb66c450172dbfa3012"
|
|
51
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { validateMnemonic as isMnemonicValid } from 'bip39'
|
|
2
|
+
|
|
3
|
+
const createApi = ({ ioc }) => {
|
|
4
|
+
const { passphraseCache } = ioc.getByType('adapter')
|
|
5
|
+
|
|
6
|
+
const { application, wallet } = ioc.getByType('module')
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
wallet: {
|
|
10
|
+
exists: () => wallet.exists(),
|
|
11
|
+
load: application.load,
|
|
12
|
+
unload: application.unload,
|
|
13
|
+
create: application.create,
|
|
14
|
+
lock: application.lock,
|
|
15
|
+
unlock: application.unlock,
|
|
16
|
+
import: application.import,
|
|
17
|
+
delete: application.delete,
|
|
18
|
+
getMnemonic: application.getMnemonic,
|
|
19
|
+
setBackedUp: application.setBackedUp,
|
|
20
|
+
changePassphrase: application.changePassphrase,
|
|
21
|
+
restoreFromCurrentPhrase: async ({ passphrase } = {}) => {
|
|
22
|
+
if (!passphrase) passphrase = await passphraseCache.get()
|
|
23
|
+
const mnemonic = await application.getMnemonic({ passphrase })
|
|
24
|
+
|
|
25
|
+
await application.import({ passphrase, mnemonic })
|
|
26
|
+
},
|
|
27
|
+
changeLockTimer: application.changeLockTimer,
|
|
28
|
+
isLocked: () => wallet.isLocked(),
|
|
29
|
+
},
|
|
30
|
+
isMnemonicValid,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default createApi
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-for-loop */
|
|
2
|
+
|
|
3
|
+
import ExodusModule from '@exodus/module'
|
|
4
|
+
import assert from 'minimalistic-assert'
|
|
5
|
+
|
|
6
|
+
// Because we are forced to restart wallet after deleting/importing, we need to store certain flags to persist state between executions
|
|
7
|
+
|
|
8
|
+
// Triggers deletetion logic when wallet starts.
|
|
9
|
+
// Set as true on delete method is called, and set to false when wallet is restarted
|
|
10
|
+
const DELETE_FLAG = 'deleteFlag'
|
|
11
|
+
|
|
12
|
+
// Triggers import logic when wallet starts. Used when overwriting existing wallet (not importing one from scratch)
|
|
13
|
+
// Set as true on import method is called, and set to false when wallet is restarted
|
|
14
|
+
const IMPORT_FLAG = 'importFlag'
|
|
15
|
+
|
|
16
|
+
// Triggers restore logic. We need to do it after any import, no matter if there was a previous wallet or not
|
|
17
|
+
// Set as true on import method is called, and set to false after restore is completed
|
|
18
|
+
const RESTORE_FLAG = 'restoreFlag'
|
|
19
|
+
|
|
20
|
+
const HOOKS = Object.freeze({
|
|
21
|
+
lock: 'lock',
|
|
22
|
+
unlock: 'unlock',
|
|
23
|
+
clear: 'clear',
|
|
24
|
+
import: 'import',
|
|
25
|
+
migrate: 'migrate',
|
|
26
|
+
start: 'start',
|
|
27
|
+
load: 'load',
|
|
28
|
+
unload: 'unload',
|
|
29
|
+
create: 'create',
|
|
30
|
+
backup: 'backup',
|
|
31
|
+
restore: 'restore',
|
|
32
|
+
'restore-completed': 'restore-completed',
|
|
33
|
+
'assets-synced': 'assets-synced',
|
|
34
|
+
'change-passphrase': 'change-passphrase',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
class Application extends ExodusModule {
|
|
38
|
+
#hooks = {}
|
|
39
|
+
#wallet = null
|
|
40
|
+
#storage = null
|
|
41
|
+
#passphraseCache = null
|
|
42
|
+
#applicationStarted = null
|
|
43
|
+
#resolveStart = null
|
|
44
|
+
|
|
45
|
+
constructor({ wallet, unsafeStorage, passphraseCache, logger }) {
|
|
46
|
+
super({ name: 'Application', logger })
|
|
47
|
+
|
|
48
|
+
this.#wallet = wallet
|
|
49
|
+
this.#passphraseCache = passphraseCache
|
|
50
|
+
this.#storage = unsafeStorage.namespace('flags')
|
|
51
|
+
|
|
52
|
+
this.#applicationStarted = new Promise((resolve) => (this.#resolveStart = resolve))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
start = async () => {
|
|
56
|
+
const [deleteFlag, importFlag] = await this.#storage.batchGet([DELETE_FLAG, IMPORT_FLAG])
|
|
57
|
+
|
|
58
|
+
const isDeleting = !!deleteFlag
|
|
59
|
+
const isImporting = !!importFlag
|
|
60
|
+
|
|
61
|
+
if (isDeleting) await this.#wallet.clear()
|
|
62
|
+
|
|
63
|
+
if (isDeleting || isImporting) {
|
|
64
|
+
await this.#storage.batchDelete([DELETE_FLAG, IMPORT_FLAG])
|
|
65
|
+
await this.#passphraseCache.clear()
|
|
66
|
+
await this.fire(HOOKS.clear)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isImporting) await this.fire(HOOKS.import)
|
|
70
|
+
|
|
71
|
+
await this.fire(HOOKS.start)
|
|
72
|
+
await this.#autoUnlock()
|
|
73
|
+
|
|
74
|
+
const locked = await this.#wallet.isLocked()
|
|
75
|
+
this._logger.log('wallet is locked', locked)
|
|
76
|
+
|
|
77
|
+
this.#resolveStart()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
load = async () => {
|
|
81
|
+
await this.#applicationStarted
|
|
82
|
+
|
|
83
|
+
const [walletExists, hasPassphraseSet, isLocked, isBackedUp] = await Promise.all([
|
|
84
|
+
this.#wallet.exists(),
|
|
85
|
+
this.#wallet.hasPassphraseSet(),
|
|
86
|
+
this.#wallet.isLocked(),
|
|
87
|
+
this.#wallet.isBackedUp(),
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
await this.fire(HOOKS.load, { walletExists, hasPassphraseSet, isLocked, isBackedUp })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
unload = async () => {
|
|
94
|
+
await this.#applicationStarted
|
|
95
|
+
await this.#passphraseCache.scheduleClear()
|
|
96
|
+
await this.fire(HOOKS.unload)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
hook = (hookName, listener) => {
|
|
100
|
+
assert(HOOKS[hookName], `no such hook: ${hookName}`)
|
|
101
|
+
|
|
102
|
+
if (!this.#hooks[hookName]) {
|
|
103
|
+
this.#hooks[hookName] = []
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.#hooks[hookName].push(listener)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fire = async (hookName, params) => {
|
|
110
|
+
assert(HOOKS[hookName], `no such hook: ${hookName}`)
|
|
111
|
+
this._logger.debug('firing hooks', hookName)
|
|
112
|
+
|
|
113
|
+
const hooks = this.#hooks[hookName] || []
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
116
|
+
await hooks[i](params)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.emit(hookName, params)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
create = async (opts) => {
|
|
123
|
+
this._logger.log('creating wallet')
|
|
124
|
+
|
|
125
|
+
await this.#applicationStarted
|
|
126
|
+
await this.#wallet.create(opts)
|
|
127
|
+
|
|
128
|
+
await this.fire(HOOKS.create, { hasPassphraseSet: !!opts?.passphrase })
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
import = async (opts) => {
|
|
132
|
+
this._logger.log('importing wallet')
|
|
133
|
+
|
|
134
|
+
await this.#storage.set(RESTORE_FLAG, true)
|
|
135
|
+
|
|
136
|
+
await this.#applicationStarted
|
|
137
|
+
|
|
138
|
+
if (await this.#wallet.exists()) {
|
|
139
|
+
await this.#wallet.import(opts)
|
|
140
|
+
await this.#storage.set(IMPORT_FLAG, true)
|
|
141
|
+
|
|
142
|
+
this.emit('restart')
|
|
143
|
+
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await this.#wallet.import(opts)
|
|
148
|
+
await this.fire(HOOKS.import)
|
|
149
|
+
|
|
150
|
+
this._logger.log('wallet imported')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getMnemonic = async (opts) => this.#wallet.getMnemonic(opts)
|
|
154
|
+
|
|
155
|
+
setBackedUp = async () => {
|
|
156
|
+
await this.#wallet.setBackedUp()
|
|
157
|
+
await this.fire(HOOKS.backup)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
lock = async (opts) => {
|
|
161
|
+
this._logger.log('locking')
|
|
162
|
+
|
|
163
|
+
await this.#applicationStarted
|
|
164
|
+
await this.#wallet.lock(opts)
|
|
165
|
+
await this.#passphraseCache.clear()
|
|
166
|
+
await this.fire(HOOKS.lock)
|
|
167
|
+
|
|
168
|
+
this._logger.log('locked')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#restoreIfNeeded = async () => {
|
|
172
|
+
const isRestoring = await this.isRestoring()
|
|
173
|
+
|
|
174
|
+
if (isRestoring) {
|
|
175
|
+
await this.fire(HOOKS.restore)
|
|
176
|
+
await this.#storage.delete(RESTORE_FLAG)
|
|
177
|
+
await this.fire(HOOKS['restore-completed'])
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.fire(HOOKS['assets-synced'])
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#autoUnlock = async () => {
|
|
184
|
+
const walletLocked = await this.#wallet.isLocked()
|
|
185
|
+
const passphrase = await this.#passphraseCache.get()
|
|
186
|
+
|
|
187
|
+
if (!walletLocked || !passphrase) return
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
this._logger.log('unlocking with cache')
|
|
191
|
+
|
|
192
|
+
await this.#wallet.unlock({ passphrase })
|
|
193
|
+
await this.fire(HOOKS.unlock)
|
|
194
|
+
|
|
195
|
+
this.#restoreIfNeeded()
|
|
196
|
+
|
|
197
|
+
this._logger.log('unlocked with cache')
|
|
198
|
+
} catch (err) {
|
|
199
|
+
this._logger.error('failed to unlock, outdated cached passphrase?', err)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
unlock = async ({ passphrase } = {}) => {
|
|
204
|
+
this._logger.log('unlocking')
|
|
205
|
+
|
|
206
|
+
await this.#applicationStarted
|
|
207
|
+
await this.#wallet.unlock({ passphrase })
|
|
208
|
+
|
|
209
|
+
await this.fire(HOOKS.migrate)
|
|
210
|
+
await this.fire(HOOKS.unlock)
|
|
211
|
+
|
|
212
|
+
this.#restoreIfNeeded()
|
|
213
|
+
|
|
214
|
+
if (passphrase) this.#passphraseCache.set(passphrase)
|
|
215
|
+
|
|
216
|
+
this._logger.log('unlocked')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
changePassphrase = async ({ currentPassphrase, newPassphrase }) => {
|
|
220
|
+
this._logger.log('changing passphrase')
|
|
221
|
+
|
|
222
|
+
await this.#applicationStarted
|
|
223
|
+
await this.#wallet.changePassphrase({ currentPassphrase, newPassphrase })
|
|
224
|
+
await this.#passphraseCache.set(newPassphrase)
|
|
225
|
+
await this.fire(HOOKS['change-passphrase'])
|
|
226
|
+
|
|
227
|
+
this._logger.log('passphrase changed')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
delete = async () => {
|
|
231
|
+
await this.#storage.set(DELETE_FLAG, true)
|
|
232
|
+
this.emit('restart')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
changeLockTimer = async ({ ttl }) => {
|
|
236
|
+
await this.#passphraseCache.changeTtl(ttl)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
isRestoring = async () => {
|
|
240
|
+
return this.#storage.get(RESTORE_FLAG)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const createApplication = (args = {}) => new Application({ ...args })
|
|
245
|
+
|
|
246
|
+
export default createApplication
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createInMemoryAtom } from '@exodus/atoms'
|
|
2
|
+
import { withType } from './utils'
|
|
3
|
+
|
|
4
|
+
const createAtomDependencies = () =>
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
definition: {
|
|
8
|
+
id: 'lockedAtom',
|
|
9
|
+
factory: () => createInMemoryAtom({ defaultValue: true }),
|
|
10
|
+
dependencies: [],
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
].map(withType('atom'))
|
|
14
|
+
|
|
15
|
+
export default createAtomDependencies
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* eslint-disable unicorn/prefer-array-flat */
|
|
2
|
+
/* eslint-disable unicorn/prefer-spread */
|
|
3
|
+
|
|
4
|
+
import assert from 'minimalistic-assert'
|
|
5
|
+
|
|
6
|
+
import createConfigDependencies from './configs'
|
|
7
|
+
import createAdapterDependencies from './adapters'
|
|
8
|
+
import createAtomDependencies from './atoms'
|
|
9
|
+
import createModuleDependencies from './modules'
|
|
10
|
+
import { wrapConstant } from './utils'
|
|
11
|
+
|
|
12
|
+
const adapterKeys = [
|
|
13
|
+
// ...
|
|
14
|
+
'assetsModule',
|
|
15
|
+
'createLogger',
|
|
16
|
+
'legacyPrivToPub',
|
|
17
|
+
'seco',
|
|
18
|
+
'unsafeStorage',
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const createDependencies = ({ adapters, config }) => {
|
|
22
|
+
assert(config, 'expected config object')
|
|
23
|
+
|
|
24
|
+
adapterKeys.forEach((key) => assert(adapters[key], `expected adapter "${key}"`))
|
|
25
|
+
|
|
26
|
+
const logger = adapters.createLogger('exodus')
|
|
27
|
+
|
|
28
|
+
const configs = createConfigDependencies({ adapters, config })
|
|
29
|
+
|
|
30
|
+
const modules = createModuleDependencies({ adapters, config })
|
|
31
|
+
|
|
32
|
+
const atoms = createAtomDependencies({ adapters, config })
|
|
33
|
+
|
|
34
|
+
const adaptersTree = createAdapterDependencies({ adapters, config })
|
|
35
|
+
|
|
36
|
+
return []
|
|
37
|
+
.concat(adaptersTree)
|
|
38
|
+
.concat(configs)
|
|
39
|
+
.concat(modules)
|
|
40
|
+
.concat(atoms)
|
|
41
|
+
.concat(wrapConstant({ id: 'logger', type: 'module', value: logger }))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default createDependencies
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createKeychain } from '@exodus/keychain'
|
|
2
|
+
import walletDefinition from '@exodus/wallet/module'
|
|
3
|
+
import createKeyIdentifierProvider from '@exodus/key-identifier-provider'
|
|
4
|
+
import walletCompatibilityModesDefinition from '@exodus/wallet-compatibility-modes/module'
|
|
5
|
+
|
|
6
|
+
import createApplication from '../application'
|
|
7
|
+
import { withType } from './utils'
|
|
8
|
+
|
|
9
|
+
const createModuleDependencies = () =>
|
|
10
|
+
[
|
|
11
|
+
{
|
|
12
|
+
definition: {
|
|
13
|
+
id: 'application',
|
|
14
|
+
factory: createApplication,
|
|
15
|
+
dependencies: ['unsafeStorage', 'passphraseCache', 'wallet', 'logger'],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
definition: {
|
|
20
|
+
id: 'keyIdentifierProvider',
|
|
21
|
+
factory: createKeyIdentifierProvider,
|
|
22
|
+
dependencies: [],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
definition: {
|
|
27
|
+
id: 'keychain',
|
|
28
|
+
factory: createKeychain,
|
|
29
|
+
dependencies: ['legacyPrivToPub'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
definition: walletDefinition,
|
|
34
|
+
aliases: [
|
|
35
|
+
{
|
|
36
|
+
implementationId: 'unsafeStorage',
|
|
37
|
+
interfaceId: 'storage',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
definition: walletCompatibilityModesDefinition,
|
|
43
|
+
},
|
|
44
|
+
].map(withType('module'))
|
|
45
|
+
|
|
46
|
+
export default createModuleDependencies
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const wrapConstant = ({ id, type, value }) => ({
|
|
2
|
+
definition: { id, type, factory: () => value },
|
|
3
|
+
})
|
|
4
|
+
|
|
5
|
+
export const insertIf = (module, enabled) => {
|
|
6
|
+
return enabled ? [module] : []
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const withType =
|
|
10
|
+
(type) =>
|
|
11
|
+
({ definition, ...rest }) => ({
|
|
12
|
+
...rest,
|
|
13
|
+
definition: { type, ...definition },
|
|
14
|
+
})
|
package/src/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import createIOC from './ioc'
|
|
2
|
+
import createApi from './api'
|
|
3
|
+
|
|
4
|
+
const createExodus = ({ adapters, config, port }) => {
|
|
5
|
+
const ioc = createIOC({ adapters, config })
|
|
6
|
+
|
|
7
|
+
const resolve = () => {
|
|
8
|
+
ioc.resolve()
|
|
9
|
+
|
|
10
|
+
const { assetsModule } = ioc.getByType('adapter')
|
|
11
|
+
|
|
12
|
+
const { application } = ioc.getByType('module')
|
|
13
|
+
|
|
14
|
+
application.hook('start', () => {
|
|
15
|
+
port.emit('start')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
application.hook('unlock', async () => {
|
|
19
|
+
await assetsModule.load()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
application.hook('clear', async () => {
|
|
23
|
+
await Promise.all([assetsModule.clear()])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
application.on('restart', () => {
|
|
27
|
+
port.emit('restart')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
application.start()
|
|
31
|
+
|
|
32
|
+
return createApi({ ioc })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { ...ioc, resolve }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default createExodus
|
package/src/ioc.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import createIocContainer from '@exodus/dependency-injection'
|
|
2
|
+
import preprocess from '@exodus/dependency-preprocessors'
|
|
3
|
+
import alias from '@exodus/dependency-preprocessors/src/preprocessors/alias'
|
|
4
|
+
import logify from '@exodus/dependency-preprocessors/src/preprocessors/logify'
|
|
5
|
+
import namespaceConfig from '@exodus/dependency-preprocessors/src/preprocessors/namespace-config'
|
|
6
|
+
|
|
7
|
+
import createDependencies from './dependencies'
|
|
8
|
+
|
|
9
|
+
const createIOC = ({ adapters, config }) => {
|
|
10
|
+
const { createLogger } = adapters
|
|
11
|
+
|
|
12
|
+
const logger = createLogger('exodus:ioc')
|
|
13
|
+
const dependencies = createDependencies({ adapters, config })
|
|
14
|
+
const preprocessors = [logify({ createLogger }), namespaceConfig(), alias()]
|
|
15
|
+
const definitions = preprocess({ dependencies, preprocessors })
|
|
16
|
+
const ioc = createIocContainer({ logger })
|
|
17
|
+
|
|
18
|
+
ioc.registerMultiple(definitions)
|
|
19
|
+
|
|
20
|
+
return ioc
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default createIOC
|