edge-core-js 2.41.2 → 2.42.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.
@@ -7,9 +7,24 @@ class EdgeCoreModule: NSObject {
7
7
  @objc static func requiresMainQueueSetup() -> Bool { return false }
8
8
 
9
9
  @objc func constantsToExport() -> [AnyHashable: Any]! {
10
+ // Derive the app root URI using the same two-level Bundle lookup that
11
+ // plugin native modules use: find a .bundle resource, create a sub-Bundle,
12
+ // then call url(forResource:) on the sub-Bundle. This produces file:// URLs
13
+ // without /private, matching the plugin sourceUri values.
14
+ var rootBaseUri = Bundle.main.bundleURL.absoluteString
15
+ if let bundleUrl = Bundle.main.url(forResource: "edge-core-js", withExtension: "bundle"),
16
+ let bundle = Bundle(url: bundleUrl),
17
+ let coreUrl = bundle.url(forResource: "edge-core", withExtension: "js")
18
+ {
19
+ // Go up two levels: edge-core.js -> edge-core-js.bundle/ -> Edge.app/
20
+ rootBaseUri = coreUrl
21
+ .deletingLastPathComponent()
22
+ .deletingLastPathComponent()
23
+ .absoluteString
24
+ }
10
25
  return [
11
26
  "bundleBaseUri": BUNDLE_BASE_URI,
12
- "rootBaseUri": "file://\(Bundle.main.bundlePath)/",
27
+ "rootBaseUri": rootBaseUri,
13
28
  ]
14
29
  }
15
30
  }
@@ -28,6 +28,7 @@ import { AccountSync, fixUsername } from '../../client-side'
28
28
 
29
29
 
30
30
 
31
+ import { makeEdgeResult } from '../../util/edgeResult'
31
32
  import { base58 } from '../../util/encoding'
32
33
  import { getPublicWalletInfo } from '../currency/wallet/currency-wallet-pixie'
33
34
  import {
@@ -502,7 +503,42 @@ export function makeAccountApi(ai, accountId) {
502
503
  walletId,
503
504
  newWalletType
504
505
  ) {
505
- return await splitWalletInfo(ai, accountId, walletId, newWalletType)
506
+ const { allWalletInfosFull } = accountState()
507
+ const walletInfo = allWalletInfosFull.find(
508
+ walletInfo => walletInfo.id === walletId
509
+ )
510
+ if (walletInfo == null) throw new Error(`Invalid wallet id ${walletId}`)
511
+ const existingWallet =
512
+ _optionalChain([ai, 'access', _2 => _2.props, 'access', _3 => _3.output, 'optionalAccess', _4 => _4.currency, 'optionalAccess', _5 => _5.wallets, 'access', _6 => _6[walletInfo.id], 'optionalAccess', _7 => _7.walletApi])
513
+
514
+ // The following check has not been needed since about 2021,
515
+ // when the currency plugins became responsible for listing
516
+ // their own splittable types, but keep it for safety:
517
+ if (
518
+ walletInfo.type === 'wallet:bitcoin' &&
519
+ walletInfo.keys.format === 'bip49' &&
520
+ newWalletType === 'wallet:bitcoincash'
521
+ ) {
522
+ throw new Error(
523
+ 'Cannot split segwit-format Bitcoin wallets to Bitcoin Cash'
524
+ )
525
+ }
526
+
527
+ const [result] = await splitWalletInfo(
528
+ ai,
529
+ accountId,
530
+ walletInfo,
531
+ [
532
+ {
533
+ walletType: newWalletType,
534
+ name: _nullishCoalesce(_optionalChain([existingWallet, 'optionalAccess', _8 => _8.name]), () => ( undefined)),
535
+ fiatCurrencyCode: _optionalChain([existingWallet, 'optionalAccess', _9 => _9.fiatCurrencyCode])
536
+ }
537
+ ],
538
+ true
539
+ )
540
+ if (result.ok) return result.result.id
541
+ throw result.error
506
542
  },
507
543
 
508
544
  async listSplittableWalletTypes(walletId) {
@@ -750,7 +786,7 @@ export function makeAccountApi(ai, accountId) {
750
786
  throw new Error(
751
787
  `activateWallet unsupported by walletId ${activateWalletId}`
752
788
  )
753
- const walletId = _nullishCoalesce(_optionalChain([paymentInfo, 'optionalAccess', _2 => _2.walletId]), () => ( ''))
789
+ const walletId = _nullishCoalesce(_optionalChain([paymentInfo, 'optionalAccess', _10 => _10.walletId]), () => ( ''))
754
790
  const wallet = currencyWallets[walletId]
755
791
 
756
792
  if (wallet == null) {
@@ -769,7 +805,7 @@ export function makeAccountApi(ai, accountId) {
769
805
 
770
806
  // Added for backward compatibility for plugins using core 1.x
771
807
  // @ts-expect-error
772
- paymentTokenId: _optionalChain([paymentInfo, 'optionalAccess', _3 => _3.tokenId]),
808
+ paymentTokenId: _optionalChain([paymentInfo, 'optionalAccess', _11 => _11.tokenId]),
773
809
  paymentWallet: wallet
774
810
  })
775
811
 
@@ -828,11 +864,3 @@ function getRawPrivateKey(
828
864
  }
829
865
  return info
830
866
  }
831
-
832
- async function makeEdgeResult(promise) {
833
- try {
834
- return { ok: true, result: await promise }
835
- } catch (error) {
836
- return { ok: false, error }
837
- }
838
- }
@@ -11,6 +11,8 @@ import { bridgifyObject, close, update, watchMethod } from 'yaob'
11
11
 
12
12
 
13
13
 
14
+
15
+
14
16
  import { makePeriodicTask, } from '../../util/periodic-task'
15
17
  import { snooze } from '../../util/snooze'
16
18
  import { getMaxSpendableInner } from '../currency/wallet/max-spend'
@@ -43,7 +45,7 @@ export const makeMemoryWalletInner = async (
43
45
  const log = makeLog(ai.props.logBackend, `${walletId}-${walletType}`)
44
46
  let balanceMap = new Map()
45
47
  let detectedTokenIds = []
46
- let syncRatio = 0
48
+ let syncStatus = { totalRatio: 0 }
47
49
 
48
50
  let needsUpdate = false
49
51
  const updateWallet = () => {
@@ -58,51 +60,54 @@ export const makeMemoryWalletInner = async (
58
60
  }, 0)
59
61
 
60
62
  const plugin = ai.props.state.plugins.currency[config.currencyInfo.pluginId]
61
- const engine = await plugin.makeCurrencyEngine(walletInfo, {
62
- callbacks: {
63
- onAddressChanged: () => {},
64
- onAddressesChecked: (progressRatio) => {
65
- if (out.syncRatio === 1) return
66
-
67
- if (progressRatio === 1) {
68
- syncRatio = progressRatio
69
- needsUpdate = true
70
- }
71
- },
72
- onNewTokens: (tokenIds) => {
73
- const sortedTokenIds = tokenIds.sort((a, b) => a.localeCompare(b))
63
+ const callbacks = {
64
+ onAddressChanged: () => {},
65
+ onAddressesChecked(totalRatio) {
66
+ callbacks.onSyncStatusChanged({ totalRatio })
67
+ },
68
+ onNewTokens: (tokenIds) => {
69
+ const sortedTokenIds = tokenIds.sort((a, b) => a.localeCompare(b))
74
70
 
75
- if (detectedTokenIds.length !== sortedTokenIds.length) {
71
+ if (detectedTokenIds.length !== sortedTokenIds.length) {
72
+ detectedTokenIds = sortedTokenIds
73
+ needsUpdate = true
74
+ return
75
+ }
76
+ for (let i = 0; i < sortedTokenIds.length; i++) {
77
+ if (detectedTokenIds[i] !== sortedTokenIds[i]) {
76
78
  detectedTokenIds = sortedTokenIds
77
79
  needsUpdate = true
78
80
  return
79
81
  }
80
- for (let i = 0; i < sortedTokenIds.length; i++) {
81
- if (detectedTokenIds[i] !== sortedTokenIds[i]) {
82
- detectedTokenIds = sortedTokenIds
83
- needsUpdate = true
84
- return
85
- }
86
- }
87
- },
88
- onSeenTxCheckpoint: () => {},
89
- onStakingStatusChanged: () => {},
90
- onSubscribeAddresses: () => {},
91
- onTokenBalanceChanged: (tokenId, balance) => {
92
- if (balanceMap.get(tokenId) === balance) return
93
-
94
- balanceMap = new Map(balanceMap)
95
- balanceMap.set(tokenId, balance)
82
+ }
83
+ },
84
+ onSeenTxCheckpoint: () => {},
85
+ onStakingStatusChanged: () => {},
86
+ onSubscribeAddresses: () => {},
87
+ onSyncStatusChanged(status) {
88
+ if (syncStatus.totalRatio === 1) return
89
+ if (status.totalRatio === 1) {
90
+ syncStatus = status
96
91
  needsUpdate = true
97
- },
98
- onTransactions: () => {},
99
- onTransactionsChanged: () => {},
100
- onTxidsChanged: () => {},
101
- onUnactivatedTokenIdsChanged: () => {},
102
- onWcNewContractCall: () => {},
103
- onBlockHeightChanged: () => {},
104
- onBalanceChanged: () => {}
92
+ }
105
93
  },
94
+ onTokenBalanceChanged: (tokenId, balance) => {
95
+ if (balanceMap.get(tokenId) === balance) return
96
+
97
+ balanceMap = new Map(balanceMap)
98
+ balanceMap.set(tokenId, balance)
99
+ needsUpdate = true
100
+ },
101
+ onTransactions: () => {},
102
+ onTransactionsChanged: () => {},
103
+ onTxidsChanged: () => {},
104
+ onUnactivatedTokenIdsChanged: () => {},
105
+ onWcNewContractCall: () => {},
106
+ onBlockHeightChanged: () => {},
107
+ onBalanceChanged: () => {}
108
+ }
109
+ const engine = await plugin.makeCurrencyEngine(walletInfo, {
110
+ callbacks,
106
111
  customTokens: { ...config.customTokens },
107
112
  enabledTokenIds: [...Object.keys(config.allTokens)],
108
113
  lightMode: true,
@@ -151,7 +156,10 @@ export const makeMemoryWalletInner = async (
151
156
  return detectedTokenIds
152
157
  },
153
158
  get syncRatio() {
154
- return syncRatio
159
+ return syncStatus.totalRatio
160
+ },
161
+ get syncStatus() {
162
+ return syncStatus
155
163
  },
156
164
  async changeEnabledTokenIds(tokenIds) {
157
165
  if (engine.changeEnabledTokenIds != null) {
@@ -35,11 +35,15 @@ import {
35
35
 
36
36
 
37
37
 
38
+
39
+
40
+
38
41
 
39
42
 
40
43
 
41
44
 
42
45
  import { makeMetaTokens } from '../../account/custom-tokens'
46
+ import { splitWalletInfo } from '../../login/splitting'
43
47
  import { toApiInput } from '../../root-pixie'
44
48
  import { makeStorageWalletApi } from '../../storage/storage-api'
45
49
  import { getCurrencyMultiplier } from '../currency-selectors'
@@ -201,7 +205,10 @@ export function makeCurrencyWalletApi(
201
205
  return skipBlockHeight ? 0 : input.props.walletState.height
202
206
  },
203
207
  get syncRatio() {
204
- return input.props.walletState.syncRatio
208
+ return input.props.walletState.syncStatus.totalRatio
209
+ },
210
+ get syncStatus() {
211
+ return input.props.walletState.syncStatus
205
212
  },
206
213
  get unactivatedTokenIds() {
207
214
  return input.props.walletState.unactivatedTokenIds
@@ -727,6 +734,18 @@ export function makeCurrencyWalletApi(
727
734
  emit(out, 'transactionsRemoved', undefined)
728
735
  },
729
736
 
737
+ async split(
738
+ splitWallets
739
+ ) {
740
+ return await splitWalletInfo(
741
+ ai,
742
+ accountId,
743
+ walletInfo,
744
+ splitWallets,
745
+ false
746
+ )
747
+ },
748
+
730
749
  // URI handling:
731
750
  async encodeUri(options) {
732
751
  return await tools.encodeUri(
@@ -16,6 +16,7 @@ import {
16
16
 
17
17
 
18
18
 
19
+
19
20
  import { compare } from '../../../util/compare'
20
21
  import { enableTestMode, pushUpdate } from '../../../util/updateQueue'
21
22
  import {
@@ -118,11 +119,11 @@ export function makeCurrencyWalletCallbacks(
118
119
  onAddressesChecked(ratio) {
119
120
  pushUpdate({
120
121
  id: walletId,
121
- action: 'onAddressesChecked',
122
+ action: 'onSyncStatusChanged',
122
123
  updateFunc: () => {
123
124
  input.props.dispatch({
124
- type: 'CURRENCY_ENGINE_CHANGED_SYNC_RATIO',
125
- payload: { ratio, walletId }
125
+ type: 'CURRENCY_ENGINE_CHANGED_SYNC_STATUS',
126
+ payload: { status: { totalRatio: ratio }, walletId }
126
127
  })
127
128
  }
128
129
  })
@@ -317,6 +318,19 @@ export function makeCurrencyWalletCallbacks(
317
318
  })
318
319
  },
319
320
 
321
+ onSyncStatusChanged(syncStatus) {
322
+ pushUpdate({
323
+ id: walletId,
324
+ action: 'onSyncStatusChanged',
325
+ updateFunc: () => {
326
+ input.props.dispatch({
327
+ type: 'CURRENCY_ENGINE_CHANGED_SYNC_STATUS',
328
+ payload: { status: syncStatus, walletId }
329
+ })
330
+ }
331
+ })
332
+ },
333
+
320
334
  onTransactions(txEvents) {
321
335
  const { accountId, currencyInfo, pluginId } = input.props.walletState
322
336
  const allTokens =
@@ -14,6 +14,7 @@ import { buildReducer, filterReducer, memoizeReducer } from 'redux-keto'
14
14
 
15
15
 
16
16
 
17
+
17
18
  import { compare } from '../../../util/compare'
18
19
 
19
20
  import { findCurrencyPluginId } from '../../plugins/plugins-selectors'
@@ -120,6 +121,10 @@ import { uniqueStrings } from './enabled-tokens'
120
121
  // Used for detectedTokenIds & enabledTokenIds:
121
122
  export const initialTokenIds = []
122
123
 
124
+ export const initialSyncStatus = {
125
+ totalRatio: 0
126
+ }
127
+
123
128
  const currencyWalletInner = buildReducer
124
129
 
125
130
 
@@ -314,13 +319,13 @@ const currencyWalletInner = buildReducer
314
319
  return state
315
320
  },
316
321
 
317
- syncRatio(state = 0, action) {
322
+ syncStatus(state = initialSyncStatus, action) {
318
323
  switch (action.type) {
319
- case 'CURRENCY_ENGINE_CHANGED_SYNC_RATIO': {
320
- return action.payload.ratio
324
+ case 'CURRENCY_ENGINE_CHANGED_SYNC_STATUS': {
325
+ return action.payload.status
321
326
  }
322
327
  case 'CURRENCY_ENGINE_CLEARED': {
323
- return 0
328
+ return initialSyncStatus
324
329
  }
325
330
  }
326
331
  return state
@@ -1,4 +1,4 @@
1
- import { asMaybe } from 'cleaners'
1
+ function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }import { asMaybe } from 'cleaners'
2
2
  import { base64 } from 'rfc4648'
3
3
 
4
4
 
@@ -7,7 +7,11 @@ import { base64 } from 'rfc4648'
7
7
 
8
8
 
9
9
 
10
+
11
+
12
+
10
13
  import { hmacSha256 } from '../../util/crypto/hashes'
14
+ import { makeEdgeResult } from '../../util/edgeResult'
11
15
  import { utf8 } from '../../util/encoding'
12
16
  import { changeWalletStates } from '../account/account-files'
13
17
  import { waitForCurrencyWallet } from '../currency/currency-selectors'
@@ -102,80 +106,135 @@ export function makeSplitWalletInfo(
102
106
  export async function splitWalletInfo(
103
107
  ai,
104
108
  accountId,
105
- walletId,
106
- newWalletType
109
+ walletInfo,
110
+ splitWallets,
111
+ rejectDupes
107
112
  ) {
108
113
  const accountState = ai.props.state.accounts[accountId]
109
114
  const { allWalletInfosFull, sessionKey } = accountState
110
115
 
111
- // Find the wallet we are going to split:
112
- const walletInfo = allWalletInfosFull.find(
113
- walletInfo => walletInfo.id === walletId
114
- )
115
- if (walletInfo == null) throw new Error(`Invalid wallet id ${walletId}`)
116
-
117
- // Handle BCH / BTC+segwit special case:
118
- if (
119
- newWalletType === 'wallet:bitcoincash' &&
120
- walletInfo.type === 'wallet:bitcoin' &&
121
- walletInfo.keys.format === 'bip49'
122
- ) {
123
- throw new Error(
124
- 'Cannot split segwit-format Bitcoin wallets to Bitcoin Cash'
125
- )
116
+ // Validate the wallet types:
117
+ const plugins = ai.props.state.plugins.currency
118
+ const splitInfos = new Map()
119
+ for (const item of splitWallets) {
120
+ const { walletType } = item
121
+ const pluginId = maybeFindCurrencyPluginId(plugins, item.walletType)
122
+ if (pluginId == null) {
123
+ throw new Error(`Cannot find plugin for wallet type "${walletType}"`)
124
+ }
125
+ if (splitInfos.has(walletType)) {
126
+ throw new Error(`Duplicate wallet type "${walletType}"`)
127
+ }
128
+ splitInfos.set(walletType, makeSplitWalletInfo(walletInfo, walletType))
126
129
  }
127
130
 
128
- // Handle BitcoinABC/SV replay protection:
131
+ // Do we need BitcoinABC/SV replay protection?
129
132
  const needsProtection =
130
- newWalletType === 'wallet:bitcoinsv' &&
131
- walletInfo.type === 'wallet:bitcoincash'
133
+ walletInfo.type === 'wallet:bitcoincash' &&
134
+ // We can re-protect a wallet by doing a repeated split,
135
+ // so don't check if the wallet already exists:
136
+ splitInfos.has('wallet:bitcoinsv')
132
137
  if (needsProtection) {
133
- const oldWallet = ai.props.output.currency.wallets[walletId].walletApi
134
- if (oldWallet == null) throw new Error('Missing Wallet')
135
- await protectBchWallet(oldWallet)
138
+ const existingWallet =
139
+ _optionalChain([ai, 'access', _ => _.props, 'access', _2 => _2.output, 'optionalAccess', _3 => _3.currency, 'optionalAccess', _4 => _4.wallets, 'access', _5 => _5[walletInfo.id], 'optionalAccess', _6 => _6.walletApi])
140
+ if (existingWallet == null) {
141
+ throw new Error(`Cannot find wallet ${walletInfo.id}`)
142
+ }
143
+ await protectBchWallet(existingWallet)
136
144
  }
137
145
 
138
- // See if the wallet has already been split:
139
- const newWalletInfo = makeSplitWalletInfo(walletInfo, newWalletType)
140
- const existingWalletInfo = allWalletInfosFull.find(
141
- walletInfo => walletInfo.id === newWalletInfo.id
142
- )
143
- if (existingWalletInfo != null) {
144
- if (existingWalletInfo.archived || existingWalletInfo.deleted) {
145
- // Simply undelete the existing wallet:
146
- const walletInfos = {}
147
- walletInfos[newWalletInfo.id] = {
148
- archived: false,
149
- deleted: false,
150
- migratedFromWalletId: existingWalletInfo.migratedFromWalletId
146
+ // Sort the wallet infos into two categories:
147
+ const toRestore = []
148
+ const toCreate = []
149
+ for (const newWalletInfo of splitInfos.values()) {
150
+ const existingWalletInfo = allWalletInfosFull.find(
151
+ info => info.id === newWalletInfo.id
152
+ )
153
+ if (existingWalletInfo == null) {
154
+ toCreate.push(newWalletInfo)
155
+ } else {
156
+ if (existingWalletInfo.archived || existingWalletInfo.deleted) {
157
+ toRestore.push(existingWalletInfo)
158
+ } else if (rejectDupes) {
159
+ if (
160
+ // It's OK to re-split if we are adding protection:
161
+ walletInfo.type !== 'wallet:bitcoincash' ||
162
+ newWalletInfo.type !== 'wallet:bitcoinsv'
163
+ ) {
164
+ throw new Error(
165
+ `This wallet has already been split (${newWalletInfo.type})`
166
+ )
167
+ }
151
168
  }
152
- await changeWalletStates(ai, accountId, walletInfos)
153
- return newWalletInfo.id
154
169
  }
155
- if (needsProtection) return newWalletInfo.id
156
- throw new Error('This wallet has already been split')
170
+ }
171
+
172
+ // Restore anything that has simply been deleted:
173
+ if (toRestore.length > 0) {
174
+ const newStates = {}
175
+ let hasChanges = false
176
+ for (const existingWalletInfo of toRestore) {
177
+ if (existingWalletInfo.archived || existingWalletInfo.deleted) {
178
+ hasChanges = true
179
+ newStates[existingWalletInfo.id] = {
180
+ archived: false,
181
+ deleted: false,
182
+ migratedFromWalletId: existingWalletInfo.migratedFromWalletId
183
+ }
184
+ }
185
+ }
186
+ if (hasChanges) await changeWalletStates(ai, accountId, newStates)
157
187
  }
158
188
 
159
189
  // Add the keys to the login:
160
- const kit = makeKeysKit(ai, sessionKey, [newWalletInfo], true)
161
- await applyKit(ai, sessionKey, kit)
190
+ if (toCreate.length > 0) {
191
+ const kit = makeKeysKit(ai, sessionKey, toCreate, true)
192
+ await applyKit(ai, sessionKey, kit)
193
+ }
194
+
195
+ // Wait for the new wallets to load:
196
+ const out = await Promise.all(
197
+ splitWallets.map(async splitInfo => {
198
+ const walletInfo = splitInfos.get(splitInfo.walletType)
199
+ if (walletInfo == null) {
200
+ throw new Error(`Missing wallet info for ${splitInfo.walletType}`)
201
+ }
202
+ return await makeEdgeResult(
203
+ finishWalletSplitting(
204
+ ai,
205
+ walletInfo.id,
206
+ toCreate.find(info => info.type === splitInfo.walletType) != null
207
+ ? splitInfo
208
+ : undefined
209
+ )
210
+ )
211
+ })
212
+ )
213
+
214
+ return out
215
+ }
216
+
217
+ async function finishWalletSplitting(
218
+ ai,
219
+ walletId,
220
+ item
221
+ ) {
222
+ const wallet = await waitForCurrencyWallet(ai, walletId)
162
223
 
163
224
  // Try to copy metadata on a best-effort basis.
164
225
  // In the future we should clone the repo instead:
165
- try {
166
- const wallet = await waitForCurrencyWallet(ai, newWalletInfo.id)
167
- const oldWallet = ai.props.output.currency.wallets[walletId].walletApi
168
- if (oldWallet != null) {
169
- if (oldWallet.name != null) await wallet.renameWallet(oldWallet.name)
170
- if (oldWallet.fiatCurrencyCode != null) {
171
- await wallet.setFiatCurrencyCode(oldWallet.fiatCurrencyCode)
172
- }
173
- }
174
- } catch (error) {
175
- ai.props.onError(error)
226
+ if (_optionalChain([item, 'optionalAccess', _7 => _7.name]) != null) {
227
+ await wallet
228
+ .renameWallet(item.name)
229
+ .catch((error) => ai.props.onError(error))
230
+ }
231
+ if (_optionalChain([item, 'optionalAccess', _8 => _8.fiatCurrencyCode]) != null) {
232
+ await wallet
233
+ .setFiatCurrencyCode(item.fiatCurrencyCode)
234
+ .catch((error) => ai.props.onError(error))
176
235
  }
177
236
 
178
- return newWalletInfo.id
237
+ return wallet
179
238
  }
180
239
 
181
240
  async function protectBchWallet(wallet) {