monero-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 +36 -0
- package/LICENSE +38 -0
- package/README.md +26 -0
- package/android/build.gradle +40 -0
- package/android/src/main/java/app/edge/rnmonero/RnMoneroModule.java +93 -0
- package/android/src/main/java/app/edge/rnmonero/RnMoneroPackage.java +21 -0
- package/android/src/main/jniLibs/arm64-v8a/librnmonero.so +0 -0
- package/ios/MoneroModule.h +5 -0
- package/ios/MoneroModule.mm +143 -0
- package/ios/MoneroModule.xcframework/Info.plist +43 -0
- package/ios/MoneroModule.xcframework/ios-arm64/libmonero-module.a +0 -0
- package/ios/MoneroModule.xcframework/ios-arm64-simulator/libmonero-module.a +0 -0
- package/lib/src/CppBridge.d.ts +181 -0
- package/lib/src/CppBridge.js +296 -0
- package/lib/src/index.d.ts +4 -0
- package/lib/src/index.js +27 -0
- package/lib/src/load-addon.d.ts +6 -0
- package/lib/src/load-addon.js +49 -0
- package/lib/src/node.d.ts +12 -0
- package/lib/src/node.js +23 -0
- package/lib/src/types.d.ts +120 -0
- package/lib/src/types.js +11 -0
- package/monero-native.podspec +28 -0
- package/node.d.ts +5 -0
- package/node.js +2 -0
- package/package.json +94 -0
- package/prebuilds/darwin-arm64/monero.node +0 -0
- package/src/monero-wrapper/monero-methods.hpp +25 -0
- package/src/node/monero-napi.cpp +248 -0
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "monero-native",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Native Monero wallet library for React Native and Node.js",
|
|
5
|
+
"homepage": "https://github.com/EdgeApp/monero-native",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git@github.com:EdgeApp/monero-native.git"
|
|
9
|
+
},
|
|
10
|
+
"license": "BSD-3-Clause",
|
|
11
|
+
"author": "Airbitz Inc dba Edge",
|
|
12
|
+
"main": "lib/src/index.js",
|
|
13
|
+
"types": "lib/src/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"/android/build.gradle",
|
|
16
|
+
"/android/src/",
|
|
17
|
+
"/CHANGELOG.md",
|
|
18
|
+
"/ios/",
|
|
19
|
+
"/lib/src/",
|
|
20
|
+
"/LICENSE",
|
|
21
|
+
"/monero-native.podspec",
|
|
22
|
+
"/node.d.ts",
|
|
23
|
+
"/node.js",
|
|
24
|
+
"/package.json",
|
|
25
|
+
"/prebuilds/",
|
|
26
|
+
"/README.md",
|
|
27
|
+
"/src/monero-wrapper/monero-methods.hpp",
|
|
28
|
+
"/src/node/",
|
|
29
|
+
"!/android/src/main/jniLibs/armeabi-v7a"
|
|
30
|
+
],
|
|
31
|
+
"lint-staged": {
|
|
32
|
+
"*.{js,ts}": "eslint"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build-native": "ZERO_AR_DATE=1 node -r sucrase/register ./scripts/build-native.ts",
|
|
36
|
+
"build-native-arm64": "ZERO_AR_DATE=1 node -r sucrase/register ./scripts/build-native.ts default-arm64",
|
|
37
|
+
"build-native-host": "ZERO_AR_DATE=1 node -r sucrase/register ./scripts/build-native-host.ts host",
|
|
38
|
+
"clean": "npm run clean:js && rm -rf tmp android/src/main/jniLibs ios/MoneroModule.xcframework",
|
|
39
|
+
"clean:js": "rm -rf node_modules lib",
|
|
40
|
+
"fix": "npm run lint -- --fix",
|
|
41
|
+
"lint": "eslint .",
|
|
42
|
+
"precommit": "npm run lint",
|
|
43
|
+
"prepare": "husky && rimraf lib && tsc",
|
|
44
|
+
"prepublishOnly": "npm run build-native-arm64 && npm run build-native-host",
|
|
45
|
+
"smoke-node": "node -r sucrase/register ./scripts/smoke-node.ts"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^24.10.0",
|
|
49
|
+
"eslint": "^9.39.1",
|
|
50
|
+
"eslint-config-standard-kit": "^1.0.0",
|
|
51
|
+
"husky": "^9.1.7",
|
|
52
|
+
"lint-staged": "^16.2.6",
|
|
53
|
+
"prettier": "^3.6.2",
|
|
54
|
+
"rimraf": "^6.1.0",
|
|
55
|
+
"sucrase": "^3.35.0",
|
|
56
|
+
"typescript": "^5.9.3",
|
|
57
|
+
"node-gyp": "^11.4.2"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"cleaners": "^0.3.17",
|
|
61
|
+
"node-addon-api": "^8.5.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"react-native": ">=0.47.0 <1.0.0"
|
|
65
|
+
},
|
|
66
|
+
"exports": {
|
|
67
|
+
".": {
|
|
68
|
+
"types": "./lib/src/index.d.ts",
|
|
69
|
+
"react-native": "./lib/src/index.js",
|
|
70
|
+
"default": "./lib/src/index.js"
|
|
71
|
+
},
|
|
72
|
+
"./node": {
|
|
73
|
+
"types": "./lib/src/node.d.ts",
|
|
74
|
+
"default": "./lib/src/node.js"
|
|
75
|
+
},
|
|
76
|
+
"./package.json": "./package.json",
|
|
77
|
+
"./lib/src/CppBridge": {
|
|
78
|
+
"types": "./lib/src/CppBridge.d.ts",
|
|
79
|
+
"default": "./lib/src/CppBridge.js"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"typesVersions": {
|
|
83
|
+
"*": {
|
|
84
|
+
"node": [
|
|
85
|
+
"lib/src/node.d.ts"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"peerDependenciesMeta": {
|
|
90
|
+
"react-native": {
|
|
91
|
+
"optional": true
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#ifndef MONERO_METHODS_HPP_INCLUDED
|
|
2
|
+
#define MONERO_METHODS_HPP_INCLUDED
|
|
3
|
+
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include <functional>
|
|
7
|
+
#include <mutex>
|
|
8
|
+
|
|
9
|
+
struct MoneroMethod {
|
|
10
|
+
const char *name;
|
|
11
|
+
int argc;
|
|
12
|
+
std::string (*method)(const std::vector<std::string> &args);
|
|
13
|
+
};
|
|
14
|
+
extern const MoneroMethod moneroMethods[];
|
|
15
|
+
extern const unsigned moneroMethodCount;
|
|
16
|
+
|
|
17
|
+
// Callback type for wallet events (walletId, eventName, jsonPayload)
|
|
18
|
+
using WalletEventCallback = std::function<void(
|
|
19
|
+
const std::string&, const std::string&, const std::string&)>;
|
|
20
|
+
|
|
21
|
+
// Set the global callback invoked by WalletListener on the SDK refresh thread.
|
|
22
|
+
// Thread-safe: the callback is guarded by a mutex.
|
|
23
|
+
void moneroSetEventCallback(WalletEventCallback cb);
|
|
24
|
+
|
|
25
|
+
#endif
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
|
|
3
|
+
#include "monero-methods.hpp"
|
|
4
|
+
|
|
5
|
+
#include <condition_variable>
|
|
6
|
+
#include <functional>
|
|
7
|
+
#include <mutex>
|
|
8
|
+
#include <queue>
|
|
9
|
+
#include <stdexcept>
|
|
10
|
+
#include <string>
|
|
11
|
+
#include <thread>
|
|
12
|
+
#include <utility>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
|
|
17
|
+
struct Job {
|
|
18
|
+
std::string method;
|
|
19
|
+
std::vector<std::string> args;
|
|
20
|
+
std::function<void(std::string result, std::string error)> complete;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
class SerialExecutor {
|
|
24
|
+
public:
|
|
25
|
+
SerialExecutor() { thread_ = std::thread([this] { run(); }); }
|
|
26
|
+
|
|
27
|
+
~SerialExecutor() {
|
|
28
|
+
{
|
|
29
|
+
std::lock_guard<std::mutex> lock(mu_);
|
|
30
|
+
stop_ = true;
|
|
31
|
+
}
|
|
32
|
+
cv_.notify_all();
|
|
33
|
+
if (thread_.joinable()) thread_.join();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void enqueue(Job job) {
|
|
37
|
+
{
|
|
38
|
+
std::lock_guard<std::mutex> lock(mu_);
|
|
39
|
+
jobs_.push(std::move(job));
|
|
40
|
+
}
|
|
41
|
+
cv_.notify_one();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private:
|
|
45
|
+
static std::string dispatch(
|
|
46
|
+
const std::string &method,
|
|
47
|
+
const std::vector<std::string> &args
|
|
48
|
+
) {
|
|
49
|
+
for (unsigned i = 0; i < moneroMethodCount; ++i) {
|
|
50
|
+
if (moneroMethods[i].name != method) continue;
|
|
51
|
+
if (
|
|
52
|
+
moneroMethods[i].argc != -1 &&
|
|
53
|
+
static_cast<int>(args.size()) != moneroMethods[i].argc
|
|
54
|
+
) {
|
|
55
|
+
throw std::runtime_error("monero incorrect C++ argument count");
|
|
56
|
+
}
|
|
57
|
+
return moneroMethods[i].method(args);
|
|
58
|
+
}
|
|
59
|
+
throw std::runtime_error("No monero C++ method " + method);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
void run() {
|
|
63
|
+
while (true) {
|
|
64
|
+
Job job;
|
|
65
|
+
{
|
|
66
|
+
std::unique_lock<std::mutex> lock(mu_);
|
|
67
|
+
cv_.wait(lock, [&] { return stop_ || !jobs_.empty(); });
|
|
68
|
+
if (stop_ && jobs_.empty()) return;
|
|
69
|
+
job = std::move(jobs_.front());
|
|
70
|
+
jobs_.pop();
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
job.complete(dispatch(job.method, job.args), "");
|
|
74
|
+
} catch (const std::exception &e) {
|
|
75
|
+
job.complete("", e.what());
|
|
76
|
+
} catch (...) {
|
|
77
|
+
job.complete("", "monero threw a C++ exception");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
std::mutex mu_;
|
|
83
|
+
std::condition_variable cv_;
|
|
84
|
+
std::queue<Job> jobs_;
|
|
85
|
+
std::thread thread_;
|
|
86
|
+
bool stop_ = false;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
SerialExecutor *g_mainQueue = nullptr;
|
|
90
|
+
SerialExecutor *g_nymQueue = nullptr;
|
|
91
|
+
Napi::ThreadSafeFunction *g_eventTsfn = nullptr;
|
|
92
|
+
std::mutex g_eventTsfnMutex;
|
|
93
|
+
|
|
94
|
+
bool isNymCompletionMethod(const std::string &method) {
|
|
95
|
+
return method == "resolveFetch" || method == "rejectFetch";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
struct CallResult {
|
|
99
|
+
Napi::Promise::Deferred deferred;
|
|
100
|
+
std::string value;
|
|
101
|
+
std::string error;
|
|
102
|
+
Napi::ThreadSafeFunction tsfn;
|
|
103
|
+
|
|
104
|
+
CallResult(Napi::Promise::Deferred deferred_, Napi::ThreadSafeFunction tsfn_)
|
|
105
|
+
: deferred(deferred_), tsfn(tsfn_) {}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
struct EventPayload {
|
|
109
|
+
std::string walletId;
|
|
110
|
+
std::string eventName;
|
|
111
|
+
std::string data;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
} // namespace
|
|
115
|
+
|
|
116
|
+
static Napi::Value CallMonero(const Napi::CallbackInfo &info) {
|
|
117
|
+
Napi::Env env = info.Env();
|
|
118
|
+
if (info.Length() < 2 || !info[0].IsString() || !info[1].IsArray()) {
|
|
119
|
+
Napi::TypeError::New(env, "callMonero(method, string[]) expected")
|
|
120
|
+
.ThrowAsJavaScriptException();
|
|
121
|
+
return env.Undefined();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const std::string method = info[0].As<Napi::String>().Utf8Value();
|
|
125
|
+
const Napi::Array arr = info[1].As<Napi::Array>();
|
|
126
|
+
std::vector<std::string> args;
|
|
127
|
+
args.reserve(arr.Length());
|
|
128
|
+
for (uint32_t i = 0; i < arr.Length(); ++i) {
|
|
129
|
+
Napi::Value value = arr.Get(i);
|
|
130
|
+
if (!value.IsString()) {
|
|
131
|
+
Napi::TypeError::New(env, "callMonero arguments must be strings")
|
|
132
|
+
.ThrowAsJavaScriptException();
|
|
133
|
+
return env.Undefined();
|
|
134
|
+
}
|
|
135
|
+
args.push_back(value.As<Napi::String>().Utf8Value());
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
139
|
+
Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New(
|
|
140
|
+
env,
|
|
141
|
+
Napi::Function::New(env, [](const Napi::CallbackInfo &) {}),
|
|
142
|
+
"monero-call-complete",
|
|
143
|
+
0,
|
|
144
|
+
1
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
auto *result = new CallResult(deferred, tsfn);
|
|
148
|
+
SerialExecutor *queue =
|
|
149
|
+
isNymCompletionMethod(method) ? g_nymQueue : g_mainQueue;
|
|
150
|
+
|
|
151
|
+
queue->enqueue(
|
|
152
|
+
Job{
|
|
153
|
+
method,
|
|
154
|
+
std::move(args),
|
|
155
|
+
[result](std::string value, std::string error) {
|
|
156
|
+
result->value = std::move(value);
|
|
157
|
+
result->error = std::move(error);
|
|
158
|
+
result->tsfn.BlockingCall(result, [](Napi::Env env, Napi::Function, CallResult *r) {
|
|
159
|
+
if (r->error.empty()) {
|
|
160
|
+
r->deferred.Resolve(Napi::String::New(env, r->value));
|
|
161
|
+
} else {
|
|
162
|
+
r->deferred.Reject(Napi::Error::New(env, r->error).Value());
|
|
163
|
+
}
|
|
164
|
+
r->tsfn.Release();
|
|
165
|
+
delete r;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
return deferred.Promise();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
static Napi::Value GetMethodNames(const Napi::CallbackInfo &info) {
|
|
175
|
+
Napi::Env env = info.Env();
|
|
176
|
+
Napi::Array out = Napi::Array::New(env, moneroMethodCount);
|
|
177
|
+
for (unsigned i = 0; i < moneroMethodCount; ++i) {
|
|
178
|
+
out.Set(i, Napi::String::New(env, moneroMethods[i].name));
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
static Napi::Value SetEventListener(const Napi::CallbackInfo &info) {
|
|
184
|
+
Napi::Env env = info.Env();
|
|
185
|
+
if (info.Length() < 1 || !info[0].IsFunction()) {
|
|
186
|
+
Napi::TypeError::New(env, "setEventListener(fn) expected")
|
|
187
|
+
.ThrowAsJavaScriptException();
|
|
188
|
+
return env.Undefined();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
std::lock_guard<std::mutex> lock(g_eventTsfnMutex);
|
|
192
|
+
if (g_eventTsfn != nullptr) {
|
|
193
|
+
g_eventTsfn->Abort();
|
|
194
|
+
g_eventTsfn->Release();
|
|
195
|
+
delete g_eventTsfn;
|
|
196
|
+
g_eventTsfn = nullptr;
|
|
197
|
+
}
|
|
198
|
+
g_eventTsfn = new Napi::ThreadSafeFunction(
|
|
199
|
+
Napi::ThreadSafeFunction::New(
|
|
200
|
+
env,
|
|
201
|
+
info[0].As<Napi::Function>(),
|
|
202
|
+
"monero-wallet-event",
|
|
203
|
+
0,
|
|
204
|
+
1
|
|
205
|
+
)
|
|
206
|
+
);
|
|
207
|
+
g_eventTsfn->Unref(env);
|
|
208
|
+
return env.Undefined();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
212
|
+
if (g_mainQueue == nullptr) g_mainQueue = new SerialExecutor();
|
|
213
|
+
if (g_nymQueue == nullptr) g_nymQueue = new SerialExecutor();
|
|
214
|
+
|
|
215
|
+
moneroSetEventCallback(
|
|
216
|
+
[](
|
|
217
|
+
const std::string &walletId,
|
|
218
|
+
const std::string &eventName,
|
|
219
|
+
const std::string &jsonPayload
|
|
220
|
+
) {
|
|
221
|
+
auto *payload = new EventPayload{walletId, eventName, jsonPayload};
|
|
222
|
+
std::lock_guard<std::mutex> lock(g_eventTsfnMutex);
|
|
223
|
+
if (g_eventTsfn == nullptr) {
|
|
224
|
+
delete payload;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
napi_status status = g_eventTsfn->NonBlockingCall(
|
|
228
|
+
payload,
|
|
229
|
+
[](Napi::Env env, Napi::Function jsCallback, EventPayload *p) {
|
|
230
|
+
jsCallback.Call(
|
|
231
|
+
{Napi::String::New(env, p->walletId),
|
|
232
|
+
Napi::String::New(env, p->eventName),
|
|
233
|
+
Napi::String::New(env, p->data)}
|
|
234
|
+
);
|
|
235
|
+
delete p;
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
if (status != napi_ok) delete payload;
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
exports.Set("callMonero", Napi::Function::New(env, CallMonero));
|
|
243
|
+
exports.Set("methodNames", Napi::Function::New(env, GetMethodNames));
|
|
244
|
+
exports.Set("setEventListener", Napi::Function::New(env, SetEventListener));
|
|
245
|
+
return exports;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
NODE_API_MODULE(monero, Init)
|