dash-shielded-native 0.0.1
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 +5 -0
- package/LICENSE +27 -0
- package/README.md +36 -0
- package/android/build.gradle +56 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/app/edge/rndashshielded/RNDashShieldedModule.kt +215 -0
- package/android/src/main/java/app/edge/rndashshielded/RNDashShieldedPackage.kt +16 -0
- package/android/src/main/java/uniffi/dash/dash.kt +1665 -0
- package/android/src/main/jniLibs/arm64-v8a/libdashshielded.so +0 -0
- package/dash-shielded-native.podspec +32 -0
- package/ios/.uniffi-generated +1 -0
- package/ios/EdgeDashClient.swift +88 -0
- package/ios/RNDashShielded.m +83 -0
- package/ios/RNDashShielded.swift +214 -0
- package/ios/dash-shielded-native-Bridging-Header.h +2 -0
- package/ios/dash.swift +1124 -0
- package/ios/libdashshielded.xcframework/Info.plist +47 -0
- package/ios/libdashshielded.xcframework/ios-arm64/Headers/dashFFI.h +696 -0
- package/ios/libdashshielded.xcframework/ios-arm64/Headers/module.modulemap +7 -0
- package/ios/libdashshielded.xcframework/ios-arm64/libdashshielded.a +0 -0
- package/ios/libdashshielded.xcframework/ios-arm64-simulator/Headers/dashFFI.h +696 -0
- package/ios/libdashshielded.xcframework/ios-arm64-simulator/Headers/module.modulemap +7 -0
- package/ios/libdashshielded.xcframework/ios-arm64-simulator/libdashshielded.a +0 -0
- package/lib/load-addon.d.ts +34 -0
- package/lib/node.d.ts +50 -0
- package/lib/react-native.d.ts +31 -0
- package/lib/rndash.rn.js +267 -0
- package/lib/rndash.rn.js.map +1 -0
- package/lib/src/node.js +328 -0
- package/lib/src/node.js.map +1 -0
- package/lib/types.d.ts +73 -0
- package/node.d.ts +1 -0
- package/node.js +2 -0
- package/package.json +105 -0
- package/prebuilds/darwin-arm64/dashshielded.node +0 -0
- package/rust/Cargo.lock +6795 -0
- package/rust/Cargo.toml +63 -0
- package/rust/build.rs +7 -0
- package/rust/rust-toolchain.toml +3 -0
- package/rust/src/dash.udl +90 -0
- package/rust/src/lib.rs +16 -0
- package/rust/src/napi_api.rs +164 -0
- package/rust/src/uniffi_api.rs +116 -0
- package/rust/src/wallet.rs +888 -0
- package/rust/uniffi-bindgen.rs +3 -0
- package/src/load-addon.ts +99 -0
- package/src/node.ts +248 -0
- package/src/react-native.ts +212 -0
- package/src/types.ts +88 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync } from 'fs'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
|
|
4
|
+
export interface NativeDashAddon {
|
|
5
|
+
setDocumentDirectory: (path: string) => void
|
|
6
|
+
initialize: (
|
|
7
|
+
mnemonicSeed: string,
|
|
8
|
+
account: number,
|
|
9
|
+
alias: string,
|
|
10
|
+
networkName: string,
|
|
11
|
+
defaultHost: string,
|
|
12
|
+
defaultPort: number
|
|
13
|
+
) => Promise<void>
|
|
14
|
+
stop: (alias: string) => Promise<string>
|
|
15
|
+
startSync: (alias: string) => Promise<void>
|
|
16
|
+
stopSync: (alias: string) => Promise<void>
|
|
17
|
+
deriveShieldedAddress: (alias: string) => Promise<{ shieldedAddress: string }>
|
|
18
|
+
isValidAddress: (address: string, network: string) => boolean
|
|
19
|
+
deriveViewingKey: (mnemonicSeed: string, network: string) => string
|
|
20
|
+
warmUpProver: () => Promise<void>
|
|
21
|
+
isProverReady: () => boolean
|
|
22
|
+
poll: (
|
|
23
|
+
alias: string
|
|
24
|
+
) => Promise<{
|
|
25
|
+
alias: string
|
|
26
|
+
status: string
|
|
27
|
+
scanProgress: number
|
|
28
|
+
networkBlockHeight: number
|
|
29
|
+
availableCredits: string
|
|
30
|
+
totalCredits: string
|
|
31
|
+
transactions: Array<{
|
|
32
|
+
txid: string
|
|
33
|
+
blockTimeInSeconds: number
|
|
34
|
+
minedHeight: number
|
|
35
|
+
value: string
|
|
36
|
+
fee?: string
|
|
37
|
+
toAddress?: string
|
|
38
|
+
memos: string[]
|
|
39
|
+
}>
|
|
40
|
+
}>
|
|
41
|
+
proposeTransfer: (
|
|
42
|
+
alias: string,
|
|
43
|
+
amountCredits: string,
|
|
44
|
+
toAddress: string,
|
|
45
|
+
memo?: string
|
|
46
|
+
) => Promise<string>
|
|
47
|
+
createTransfer: (
|
|
48
|
+
alias: string,
|
|
49
|
+
proposalId: string,
|
|
50
|
+
mnemonicSeed: string
|
|
51
|
+
) => Promise<string>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function candidatePaths(): string[] {
|
|
55
|
+
const here = __dirname
|
|
56
|
+
const platform = `${process.platform}-${process.arch}`
|
|
57
|
+
const out: string[] = []
|
|
58
|
+
let dir = here
|
|
59
|
+
for (let i = 0; i < 6; i++) {
|
|
60
|
+
out.push(join(dir, 'prebuilds', platform, 'dashshielded.node'))
|
|
61
|
+
out.push(join(dir, 'rust', 'target', 'release', 'libdashshielded.dylib'))
|
|
62
|
+
out.push(join(dir, 'rust', 'target', 'release', 'libdashshielded.so'))
|
|
63
|
+
const parent = join(dir, '..')
|
|
64
|
+
if (parent === dir) break
|
|
65
|
+
dir = parent
|
|
66
|
+
}
|
|
67
|
+
return out
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let cached: NativeDashAddon | undefined
|
|
71
|
+
|
|
72
|
+
export function loadNativeAddon(): NativeDashAddon {
|
|
73
|
+
if (cached != null) return cached
|
|
74
|
+
|
|
75
|
+
const errors: string[] = []
|
|
76
|
+
const missing: string[] = []
|
|
77
|
+
for (const candidate of candidatePaths()) {
|
|
78
|
+
try {
|
|
79
|
+
if (!existsSync(candidate)) {
|
|
80
|
+
missing.push(candidate)
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
const mod = require(candidate) as NativeDashAddon
|
|
84
|
+
if (typeof mod.initialize !== 'function') continue
|
|
85
|
+
cached = mod
|
|
86
|
+
return cached
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
89
|
+
errors.push(`${candidate}: ${message}`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
throw new Error(
|
|
94
|
+
'dash-shielded-native Node addon not found. Run `npm run build-native-host`. ' +
|
|
95
|
+
(errors.length > 0
|
|
96
|
+
? errors.join('; ')
|
|
97
|
+
: `Looked in: ${missing.join(', ')}`)
|
|
98
|
+
)
|
|
99
|
+
}
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { mkdirSync } from 'fs'
|
|
2
|
+
|
|
3
|
+
import { loadNativeAddon, NativeDashAddon } from './load-addon'
|
|
4
|
+
import {
|
|
5
|
+
Addresses,
|
|
6
|
+
CreateTransferOpts,
|
|
7
|
+
InitializerConfig,
|
|
8
|
+
Network,
|
|
9
|
+
ProposalSuccess,
|
|
10
|
+
ProposeTransferOpts,
|
|
11
|
+
SpendFailure,
|
|
12
|
+
SpendSuccess,
|
|
13
|
+
SynchronizerCallbacks,
|
|
14
|
+
ViewingKeySet
|
|
15
|
+
} from './types'
|
|
16
|
+
|
|
17
|
+
export * from './types'
|
|
18
|
+
|
|
19
|
+
export interface MakeNodeDashShieldedOpts {
|
|
20
|
+
documentDirectory: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseJsonObject(value: string): unknown {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(value)
|
|
26
|
+
} catch {
|
|
27
|
+
return { errorMessage: value }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Tools = {
|
|
32
|
+
deriveViewingKey: (
|
|
33
|
+
mnemonicSeed: string,
|
|
34
|
+
network: Network
|
|
35
|
+
): Promise<ViewingKeySet> => {
|
|
36
|
+
const addon = loadNativeAddon()
|
|
37
|
+
return Promise.resolve({
|
|
38
|
+
fullViewingKey: addon.deriveViewingKey(mnemonicSeed, network)
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
deriveShieldedAddress: async (
|
|
42
|
+
mnemonicSeed: string,
|
|
43
|
+
network: Network,
|
|
44
|
+
account: number = 0
|
|
45
|
+
): Promise<string> => {
|
|
46
|
+
const addon = loadNativeAddon()
|
|
47
|
+
const alias = `tools-addr-${network}-${account}`
|
|
48
|
+
await addon.initialize(mnemonicSeed, account, alias, network, '', 0)
|
|
49
|
+
const { shieldedAddress } = await addon.deriveShieldedAddress(alias)
|
|
50
|
+
await addon.stop(alias)
|
|
51
|
+
return shieldedAddress
|
|
52
|
+
},
|
|
53
|
+
isValidAddress: (
|
|
54
|
+
address: string,
|
|
55
|
+
network: Network = 'mainnet'
|
|
56
|
+
): Promise<boolean> => {
|
|
57
|
+
const addon = loadNativeAddon()
|
|
58
|
+
return Promise.resolve(addon.isValidAddress(address, network))
|
|
59
|
+
},
|
|
60
|
+
warmUpProver: async (): Promise<void> => {
|
|
61
|
+
const addon = loadNativeAddon()
|
|
62
|
+
await addon.warmUpProver()
|
|
63
|
+
},
|
|
64
|
+
isProverReady: (): Promise<boolean> => {
|
|
65
|
+
const addon = loadNativeAddon()
|
|
66
|
+
return Promise.resolve(addon.isProverReady())
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class Synchronizer {
|
|
71
|
+
alias: string
|
|
72
|
+
network: Network
|
|
73
|
+
private readonly addon: NativeDashAddon
|
|
74
|
+
private timer?: ReturnType<typeof setTimeout>
|
|
75
|
+
private callbacks?: SynchronizerCallbacks
|
|
76
|
+
private lastStatus?: string
|
|
77
|
+
|
|
78
|
+
constructor(alias: string, network: Network, addon: NativeDashAddon) {
|
|
79
|
+
this.alias = alias
|
|
80
|
+
this.network = network
|
|
81
|
+
this.addon = addon
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async stop(): Promise<string> {
|
|
85
|
+
this.unsubscribe()
|
|
86
|
+
return this.addon.stop(this.alias)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async initialize(config: InitializerConfig): Promise<void> {
|
|
90
|
+
const seed = config.mnemonicSeed ?? config.seedHex
|
|
91
|
+
if (seed == null) throw new Error('Missing mnemonicSeed')
|
|
92
|
+
await this.addon.initialize(
|
|
93
|
+
seed,
|
|
94
|
+
config.account,
|
|
95
|
+
config.alias,
|
|
96
|
+
config.network,
|
|
97
|
+
config.defaultHost,
|
|
98
|
+
config.defaultPort
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async startSync(): Promise<void> {
|
|
103
|
+
await this.addon.startSync(this.alias)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async stopSync(): Promise<void> {
|
|
107
|
+
await this.addon.stopSync(this.alias)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async deriveShieldedAddress(): Promise<Addresses> {
|
|
111
|
+
return this.addon.deriveShieldedAddress(this.alias)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getBalance(): Promise<{
|
|
115
|
+
availableCredits: string
|
|
116
|
+
totalCredits: string
|
|
117
|
+
}> {
|
|
118
|
+
const snap = await this.addon.poll(this.alias)
|
|
119
|
+
return {
|
|
120
|
+
availableCredits: snap.availableCredits,
|
|
121
|
+
totalCredits: snap.totalCredits
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getTransactions(): Promise<
|
|
126
|
+
Array<{
|
|
127
|
+
txid: string
|
|
128
|
+
blockTimeInSeconds: number
|
|
129
|
+
minedHeight: number
|
|
130
|
+
value: string
|
|
131
|
+
fee?: string
|
|
132
|
+
toAddress?: string
|
|
133
|
+
memos: string[]
|
|
134
|
+
}>
|
|
135
|
+
> {
|
|
136
|
+
const snap = await this.addon.poll(this.alias)
|
|
137
|
+
return snap.transactions
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async proposeTransfer(opts: ProposeTransferOpts): Promise<ProposalSuccess> {
|
|
141
|
+
const raw = await this.addon.proposeTransfer(
|
|
142
|
+
this.alias,
|
|
143
|
+
opts.amountCredits,
|
|
144
|
+
opts.toAddress,
|
|
145
|
+
opts.memo
|
|
146
|
+
)
|
|
147
|
+
return parseJsonObject(raw) as ProposalSuccess
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async createTransfer(
|
|
151
|
+
opts: CreateTransferOpts
|
|
152
|
+
): Promise<SpendSuccess | SpendFailure> {
|
|
153
|
+
try {
|
|
154
|
+
const raw = await this.addon.createTransfer(
|
|
155
|
+
this.alias,
|
|
156
|
+
opts.proposalId,
|
|
157
|
+
opts.mnemonicSeed
|
|
158
|
+
)
|
|
159
|
+
return parseJsonObject(raw) as SpendSuccess
|
|
160
|
+
} catch (error) {
|
|
161
|
+
const errorMessage =
|
|
162
|
+
error instanceof Error ? error.message : String(error)
|
|
163
|
+
return { errorMessage }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
subscribe(callbacks: SynchronizerCallbacks): void {
|
|
168
|
+
this.callbacks = callbacks
|
|
169
|
+
this.pump().catch(error => {
|
|
170
|
+
callbacks.onError({
|
|
171
|
+
alias: this.alias,
|
|
172
|
+
level: 'error',
|
|
173
|
+
message: `event pump failed: ${String(error)}`
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
unsubscribe(): void {
|
|
179
|
+
if (this.timer != null) {
|
|
180
|
+
clearTimeout(this.timer)
|
|
181
|
+
this.timer = undefined
|
|
182
|
+
}
|
|
183
|
+
this.callbacks = undefined
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async pump(): Promise<void> {
|
|
187
|
+
if (this.callbacks == null) return
|
|
188
|
+
const snap = await this.addon.poll(this.alias)
|
|
189
|
+
const {
|
|
190
|
+
onBalanceChanged,
|
|
191
|
+
onStatusChanged,
|
|
192
|
+
onTransactionsChanged,
|
|
193
|
+
onUpdate
|
|
194
|
+
} = this.callbacks
|
|
195
|
+
|
|
196
|
+
onBalanceChanged({
|
|
197
|
+
availableCredits: snap.availableCredits,
|
|
198
|
+
totalCredits: snap.totalCredits
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
if (snap.status !== this.lastStatus) {
|
|
202
|
+
this.lastStatus = snap.status
|
|
203
|
+
onStatusChanged({
|
|
204
|
+
alias: this.alias,
|
|
205
|
+
name: snap.status as 'STOPPED' | 'DISCONNECTED' | 'SYNCING' | 'SYNCED'
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
onTransactionsChanged({ transactions: snap.transactions })
|
|
210
|
+
onUpdate({
|
|
211
|
+
alias: this.alias,
|
|
212
|
+
scanProgress: snap.scanProgress,
|
|
213
|
+
networkBlockHeight: snap.networkBlockHeight
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const delay = snap.status === 'SYNCING' ? 500 : 2000
|
|
217
|
+
this.timer = setTimeout(() => {
|
|
218
|
+
this.pump().catch(error => {
|
|
219
|
+
this.callbacks?.onError({
|
|
220
|
+
alias: this.alias,
|
|
221
|
+
level: 'error',
|
|
222
|
+
message: `event pump failed: ${String(error)}`
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
}, delay)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export const makeSynchronizer = async (
|
|
230
|
+
config: InitializerConfig
|
|
231
|
+
): Promise<Synchronizer> => {
|
|
232
|
+
const addon = loadNativeAddon()
|
|
233
|
+
const synchronizer = new Synchronizer(config.alias, config.network, addon)
|
|
234
|
+
await synchronizer.initialize(config)
|
|
235
|
+
return synchronizer
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function makeNodeDashShieldedModule(
|
|
239
|
+
opts: MakeNodeDashShieldedOpts
|
|
240
|
+
): {
|
|
241
|
+
Tools: typeof Tools
|
|
242
|
+
makeSynchronizer: typeof makeSynchronizer
|
|
243
|
+
} {
|
|
244
|
+
mkdirSync(opts.documentDirectory, { recursive: true })
|
|
245
|
+
const addon = loadNativeAddon()
|
|
246
|
+
addon.setDocumentDirectory(opts.documentDirectory)
|
|
247
|
+
return { Tools, makeSynchronizer }
|
|
248
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventSubscription,
|
|
3
|
+
NativeEventEmitter,
|
|
4
|
+
NativeModules
|
|
5
|
+
} from 'react-native'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Addresses,
|
|
9
|
+
CreateTransferOpts,
|
|
10
|
+
InitializerConfig,
|
|
11
|
+
Network,
|
|
12
|
+
ProposalSuccess,
|
|
13
|
+
ProposeTransferOpts,
|
|
14
|
+
SpendFailure,
|
|
15
|
+
SpendSuccess,
|
|
16
|
+
SynchronizerCallbacks,
|
|
17
|
+
ViewingKeySet
|
|
18
|
+
} from './types'
|
|
19
|
+
export * from './types'
|
|
20
|
+
|
|
21
|
+
const { RNDashShielded } = NativeModules
|
|
22
|
+
|
|
23
|
+
function parseJsonObject(value: string): unknown {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(value)
|
|
26
|
+
} catch {
|
|
27
|
+
return { errorMessage: value }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Tools = {
|
|
32
|
+
deriveViewingKey: async (
|
|
33
|
+
mnemonicSeed: string,
|
|
34
|
+
network: Network
|
|
35
|
+
): Promise<ViewingKeySet> => {
|
|
36
|
+
const fullViewingKey = await RNDashShielded.deriveViewingKey(
|
|
37
|
+
mnemonicSeed,
|
|
38
|
+
network
|
|
39
|
+
)
|
|
40
|
+
return { fullViewingKey }
|
|
41
|
+
},
|
|
42
|
+
deriveShieldedAddress: async (
|
|
43
|
+
mnemonicSeed: string,
|
|
44
|
+
network: Network,
|
|
45
|
+
account: number = 0
|
|
46
|
+
): Promise<string> => {
|
|
47
|
+
return RNDashShielded.deriveShieldedAddressFromSeed(
|
|
48
|
+
mnemonicSeed,
|
|
49
|
+
network,
|
|
50
|
+
account
|
|
51
|
+
)
|
|
52
|
+
},
|
|
53
|
+
isValidAddress: async (
|
|
54
|
+
address: string,
|
|
55
|
+
network: Network = 'mainnet'
|
|
56
|
+
): Promise<boolean> => {
|
|
57
|
+
return RNDashShielded.isValidAddress(address, network)
|
|
58
|
+
},
|
|
59
|
+
warmUpProver: async (): Promise<void> => {
|
|
60
|
+
await RNDashShielded.warmUpProver()
|
|
61
|
+
},
|
|
62
|
+
isProverReady: async (): Promise<boolean> => {
|
|
63
|
+
return RNDashShielded.isProverReady()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class Synchronizer {
|
|
68
|
+
eventEmitter: NativeEventEmitter
|
|
69
|
+
subscriptions: EventSubscription[]
|
|
70
|
+
alias: string
|
|
71
|
+
network: Network
|
|
72
|
+
private timer?: ReturnType<typeof setTimeout>
|
|
73
|
+
private callbacks?: SynchronizerCallbacks
|
|
74
|
+
private lastStatus?: string
|
|
75
|
+
|
|
76
|
+
constructor(alias: string, network: Network) {
|
|
77
|
+
this.eventEmitter = new NativeEventEmitter(RNDashShielded)
|
|
78
|
+
this.subscriptions = []
|
|
79
|
+
this.alias = alias
|
|
80
|
+
this.network = network
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async stop(): Promise<string> {
|
|
84
|
+
this.unsubscribe()
|
|
85
|
+
return RNDashShielded.stop(this.alias)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async initialize(config: InitializerConfig): Promise<void> {
|
|
89
|
+
const seed = config.mnemonicSeed ?? config.seedHex
|
|
90
|
+
if (seed == null) throw new Error('Missing mnemonicSeed')
|
|
91
|
+
await RNDashShielded.initialize(
|
|
92
|
+
seed,
|
|
93
|
+
config.account,
|
|
94
|
+
config.alias,
|
|
95
|
+
config.network,
|
|
96
|
+
config.defaultHost,
|
|
97
|
+
config.defaultPort
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async startSync(): Promise<void> {
|
|
102
|
+
await RNDashShielded.startSync(this.alias)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async stopSync(): Promise<void> {
|
|
106
|
+
await RNDashShielded.stopSync(this.alias)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async deriveShieldedAddress(): Promise<Addresses> {
|
|
110
|
+
return RNDashShielded.deriveShieldedAddress(this.alias)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async proposeTransfer(opts: ProposeTransferOpts): Promise<ProposalSuccess> {
|
|
114
|
+
const result = await RNDashShielded.proposeTransfer(
|
|
115
|
+
this.alias,
|
|
116
|
+
opts.amountCredits,
|
|
117
|
+
opts.toAddress,
|
|
118
|
+
opts.memo
|
|
119
|
+
)
|
|
120
|
+
return parseJsonObject(result) as ProposalSuccess
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async createTransfer(
|
|
124
|
+
opts: CreateTransferOpts
|
|
125
|
+
): Promise<SpendSuccess | SpendFailure> {
|
|
126
|
+
try {
|
|
127
|
+
const result = await RNDashShielded.createTransfer(
|
|
128
|
+
this.alias,
|
|
129
|
+
opts.proposalId,
|
|
130
|
+
opts.mnemonicSeed
|
|
131
|
+
)
|
|
132
|
+
return parseJsonObject(result) as SpendSuccess
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const errorMessage =
|
|
135
|
+
error instanceof Error ? error.message : String(error)
|
|
136
|
+
return { errorMessage }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
subscribe(callbacks: SynchronizerCallbacks): void {
|
|
141
|
+
this.callbacks = callbacks
|
|
142
|
+
this.pump().catch(error => {
|
|
143
|
+
callbacks.onError({
|
|
144
|
+
alias: this.alias,
|
|
145
|
+
level: 'error',
|
|
146
|
+
message: `event pump failed: ${String(error)}`
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
unsubscribe(): void {
|
|
152
|
+
if (this.timer != null) {
|
|
153
|
+
clearTimeout(this.timer)
|
|
154
|
+
this.timer = undefined
|
|
155
|
+
}
|
|
156
|
+
this.callbacks = undefined
|
|
157
|
+
this.subscriptions.forEach(subscription => {
|
|
158
|
+
subscription.remove()
|
|
159
|
+
})
|
|
160
|
+
this.subscriptions = []
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async pump(): Promise<void> {
|
|
164
|
+
if (this.callbacks == null) return
|
|
165
|
+
const snap = await RNDashShielded.poll(this.alias)
|
|
166
|
+
const {
|
|
167
|
+
onBalanceChanged,
|
|
168
|
+
onStatusChanged,
|
|
169
|
+
onTransactionsChanged,
|
|
170
|
+
onUpdate
|
|
171
|
+
} = this.callbacks
|
|
172
|
+
|
|
173
|
+
onBalanceChanged({
|
|
174
|
+
availableCredits: snap.availableCredits,
|
|
175
|
+
totalCredits: snap.totalCredits
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
if (snap.status !== this.lastStatus) {
|
|
179
|
+
this.lastStatus = snap.status
|
|
180
|
+
onStatusChanged({
|
|
181
|
+
alias: this.alias,
|
|
182
|
+
name: snap.status as 'STOPPED' | 'DISCONNECTED' | 'SYNCING' | 'SYNCED'
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
onTransactionsChanged({ transactions: snap.transactions })
|
|
187
|
+
onUpdate({
|
|
188
|
+
alias: this.alias,
|
|
189
|
+
scanProgress: snap.scanProgress,
|
|
190
|
+
networkBlockHeight: snap.networkBlockHeight
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const delay = snap.status === 'SYNCING' ? 500 : 2000
|
|
194
|
+
this.timer = setTimeout(() => {
|
|
195
|
+
this.pump().catch(error => {
|
|
196
|
+
this.callbacks?.onError({
|
|
197
|
+
alias: this.alias,
|
|
198
|
+
level: 'error',
|
|
199
|
+
message: `event pump failed: ${String(error)}`
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
}, delay)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const makeSynchronizer = async (
|
|
207
|
+
config: InitializerConfig
|
|
208
|
+
): Promise<Synchronizer> => {
|
|
209
|
+
const synchronizer = new Synchronizer(config.alias, config.network)
|
|
210
|
+
await synchronizer.initialize(config)
|
|
211
|
+
return synchronizer
|
|
212
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type Network = 'mainnet' | 'testnet'
|
|
2
|
+
|
|
3
|
+
export interface ViewingKeySet {
|
|
4
|
+
fullViewingKey: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface InitializerConfig {
|
|
8
|
+
network: Network
|
|
9
|
+
seedHex?: string
|
|
10
|
+
mnemonicSeed?: string
|
|
11
|
+
account: number
|
|
12
|
+
alias: string
|
|
13
|
+
dataDir: string
|
|
14
|
+
defaultHost: string
|
|
15
|
+
defaultPort: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProposeTransferOpts {
|
|
19
|
+
toAddress: string
|
|
20
|
+
amountCredits: string
|
|
21
|
+
memo?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CreateTransferOpts {
|
|
25
|
+
proposalId: string
|
|
26
|
+
mnemonicSeed: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ProposalSuccess {
|
|
30
|
+
proposalId: string
|
|
31
|
+
feeCredits: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SpendSuccess {
|
|
35
|
+
txid: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SpendFailure {
|
|
39
|
+
errorMessage?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface BalanceEvent {
|
|
43
|
+
availableCredits: string
|
|
44
|
+
totalCredits: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StatusEvent {
|
|
48
|
+
alias: string
|
|
49
|
+
name: 'STOPPED' | 'DISCONNECTED' | 'SYNCING' | 'SYNCED'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DashShieldedTx {
|
|
53
|
+
txid: string
|
|
54
|
+
blockTimeInSeconds: number
|
|
55
|
+
minedHeight: number
|
|
56
|
+
value: string
|
|
57
|
+
fee?: string
|
|
58
|
+
toAddress?: string
|
|
59
|
+
memos: string[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface TransactionEvent {
|
|
63
|
+
transactions: DashShieldedTx[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface UpdateEvent {
|
|
67
|
+
alias: string
|
|
68
|
+
scanProgress: number
|
|
69
|
+
networkBlockHeight: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ErrorEvent {
|
|
73
|
+
alias: string
|
|
74
|
+
level: 'critical' | 'error'
|
|
75
|
+
message: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SynchronizerCallbacks {
|
|
79
|
+
onBalanceChanged(balance: BalanceEvent): void
|
|
80
|
+
onStatusChanged(status: StatusEvent): void
|
|
81
|
+
onTransactionsChanged(transactions: TransactionEvent): void
|
|
82
|
+
onUpdate(event: UpdateEvent): void
|
|
83
|
+
onError(error: ErrorEvent): void
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface Addresses {
|
|
87
|
+
shieldedAddress: string
|
|
88
|
+
}
|