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.
Files changed (49) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +27 -0
  3. package/README.md +36 -0
  4. package/android/build.gradle +56 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/app/edge/rndashshielded/RNDashShieldedModule.kt +215 -0
  7. package/android/src/main/java/app/edge/rndashshielded/RNDashShieldedPackage.kt +16 -0
  8. package/android/src/main/java/uniffi/dash/dash.kt +1665 -0
  9. package/android/src/main/jniLibs/arm64-v8a/libdashshielded.so +0 -0
  10. package/dash-shielded-native.podspec +32 -0
  11. package/ios/.uniffi-generated +1 -0
  12. package/ios/EdgeDashClient.swift +88 -0
  13. package/ios/RNDashShielded.m +83 -0
  14. package/ios/RNDashShielded.swift +214 -0
  15. package/ios/dash-shielded-native-Bridging-Header.h +2 -0
  16. package/ios/dash.swift +1124 -0
  17. package/ios/libdashshielded.xcframework/Info.plist +47 -0
  18. package/ios/libdashshielded.xcframework/ios-arm64/Headers/dashFFI.h +696 -0
  19. package/ios/libdashshielded.xcframework/ios-arm64/Headers/module.modulemap +7 -0
  20. package/ios/libdashshielded.xcframework/ios-arm64/libdashshielded.a +0 -0
  21. package/ios/libdashshielded.xcframework/ios-arm64-simulator/Headers/dashFFI.h +696 -0
  22. package/ios/libdashshielded.xcframework/ios-arm64-simulator/Headers/module.modulemap +7 -0
  23. package/ios/libdashshielded.xcframework/ios-arm64-simulator/libdashshielded.a +0 -0
  24. package/lib/load-addon.d.ts +34 -0
  25. package/lib/node.d.ts +50 -0
  26. package/lib/react-native.d.ts +31 -0
  27. package/lib/rndash.rn.js +267 -0
  28. package/lib/rndash.rn.js.map +1 -0
  29. package/lib/src/node.js +328 -0
  30. package/lib/src/node.js.map +1 -0
  31. package/lib/types.d.ts +73 -0
  32. package/node.d.ts +1 -0
  33. package/node.js +2 -0
  34. package/package.json +105 -0
  35. package/prebuilds/darwin-arm64/dashshielded.node +0 -0
  36. package/rust/Cargo.lock +6795 -0
  37. package/rust/Cargo.toml +63 -0
  38. package/rust/build.rs +7 -0
  39. package/rust/rust-toolchain.toml +3 -0
  40. package/rust/src/dash.udl +90 -0
  41. package/rust/src/lib.rs +16 -0
  42. package/rust/src/napi_api.rs +164 -0
  43. package/rust/src/uniffi_api.rs +116 -0
  44. package/rust/src/wallet.rs +888 -0
  45. package/rust/uniffi-bindgen.rs +3 -0
  46. package/src/load-addon.ts +99 -0
  47. package/src/node.ts +248 -0
  48. package/src/react-native.ts +212 -0
  49. package/src/types.ts +88 -0
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ uniffi::uniffi_bindgen_main()
3
+ }
@@ -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
+ }