stellar-shade 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/README.md +69 -0
- package/dist/index.cjs +838 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +159 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.js +817 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var utils = require('@noble/hashes/utils');
|
|
4
|
+
var utils$1 = require('@noble/curves/abstract/utils');
|
|
5
|
+
var sha256 = require('@noble/hashes/sha256');
|
|
6
|
+
var ed25519 = require('@noble/curves/ed25519');
|
|
7
|
+
var sha512 = require('@noble/hashes/sha512');
|
|
8
|
+
var bip39 = require('@scure/bip39');
|
|
9
|
+
var english = require('@scure/bip39/wordlists/english');
|
|
10
|
+
var StellarSdk = require('@stellar/stellar-sdk');
|
|
11
|
+
|
|
12
|
+
function _interopNamespace(e) {
|
|
13
|
+
if (e && e.__esModule) return e;
|
|
14
|
+
var n = Object.create(null);
|
|
15
|
+
if (e) {
|
|
16
|
+
Object.keys(e).forEach(function (k) {
|
|
17
|
+
if (k !== 'default') {
|
|
18
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return e[k]; }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
n.default = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var StellarSdk__namespace = /*#__PURE__*/_interopNamespace(StellarSdk);
|
|
31
|
+
|
|
32
|
+
// ../crypto/src/errors.ts
|
|
33
|
+
var PointAtInfinity = class extends Error {
|
|
34
|
+
constructor(operation) {
|
|
35
|
+
super(`Point at infinity encountered in ${operation}`);
|
|
36
|
+
this.name = "PointAtInfinity";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var InvalidPublicKey = class extends Error {
|
|
40
|
+
constructor(message = "Invalid public key: not on curve") {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "InvalidPublicKey";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var InvalidScalar = class extends Error {
|
|
46
|
+
constructor(message = "Invalid scalar value") {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "InvalidScalar";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var InvalidMetaAddress = class extends Error {
|
|
52
|
+
constructor(message = "Invalid meta-address format") {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = "InvalidMetaAddress";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var L = 2n ** 252n + 27742317777372353535851937790883648493n;
|
|
58
|
+
function validatePoint(point) {
|
|
59
|
+
if (point.length !== 32) {
|
|
60
|
+
throw new InvalidPublicKey("Invalid point length");
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
ed25519.ed25519.ExtendedPoint.fromHex(point);
|
|
64
|
+
return true;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
if (e instanceof InvalidPublicKey) throw e;
|
|
67
|
+
throw new InvalidPublicKey("Point not on curve");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function pointAdd(p1, p2) {
|
|
71
|
+
if (p1.length !== 32) throw new InvalidPublicKey("Invalid p1 length");
|
|
72
|
+
if (p2.length !== 32) throw new InvalidPublicKey("Invalid p2 length");
|
|
73
|
+
validatePoint(p1);
|
|
74
|
+
validatePoint(p2);
|
|
75
|
+
const point1 = ed25519.ed25519.ExtendedPoint.fromHex(p1);
|
|
76
|
+
const point2 = ed25519.ed25519.ExtendedPoint.fromHex(p2);
|
|
77
|
+
const result = point1.add(point2);
|
|
78
|
+
if (result.equals(ed25519.ed25519.ExtendedPoint.ZERO)) {
|
|
79
|
+
throw new PointAtInfinity("pointAdd");
|
|
80
|
+
}
|
|
81
|
+
return result.toRawBytes();
|
|
82
|
+
}
|
|
83
|
+
function scalarMultBase(scalar, allowZero = false) {
|
|
84
|
+
if (scalar.length !== 32) throw new InvalidScalar("Invalid scalar length");
|
|
85
|
+
const s = utils$1.bytesToNumberLE(scalar) % L;
|
|
86
|
+
if (s === 0n) {
|
|
87
|
+
if (!allowZero) {
|
|
88
|
+
throw new InvalidScalar("Zero scalar not allowed");
|
|
89
|
+
}
|
|
90
|
+
return ed25519.ed25519.ExtendedPoint.ZERO.toRawBytes();
|
|
91
|
+
}
|
|
92
|
+
const result = ed25519.ed25519.ExtendedPoint.BASE.multiply(s);
|
|
93
|
+
return result.toRawBytes();
|
|
94
|
+
}
|
|
95
|
+
function scalarMult(scalar, point, allowZero = false) {
|
|
96
|
+
if (scalar.length !== 32) throw new InvalidScalar("Invalid scalar length");
|
|
97
|
+
if (point.length !== 32) throw new InvalidPublicKey("Invalid point length");
|
|
98
|
+
validatePoint(point);
|
|
99
|
+
const s = utils$1.bytesToNumberLE(scalar) % L;
|
|
100
|
+
if (s === 0n) {
|
|
101
|
+
if (!allowZero) {
|
|
102
|
+
throw new InvalidScalar("Zero scalar not allowed");
|
|
103
|
+
}
|
|
104
|
+
return ed25519.ed25519.ExtendedPoint.ZERO.toRawBytes();
|
|
105
|
+
}
|
|
106
|
+
const p = ed25519.ed25519.ExtendedPoint.fromHex(point);
|
|
107
|
+
const result = p.multiply(s);
|
|
108
|
+
if (result.equals(ed25519.ed25519.ExtendedPoint.ZERO)) {
|
|
109
|
+
throw new PointAtInfinity("scalarMult");
|
|
110
|
+
}
|
|
111
|
+
return result.toRawBytes();
|
|
112
|
+
}
|
|
113
|
+
function scalarAdd(s1, s2) {
|
|
114
|
+
if (s1.length !== 32) throw new InvalidScalar("Invalid s1 length");
|
|
115
|
+
if (s2.length !== 32) throw new InvalidScalar("Invalid s2 length");
|
|
116
|
+
const a = utils$1.bytesToNumberLE(s1) % L;
|
|
117
|
+
const b = utils$1.bytesToNumberLE(s2) % L;
|
|
118
|
+
const result = (a + b) % L;
|
|
119
|
+
return utils$1.numberToBytesLE(result, 32);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ../crypto/src/keys.ts
|
|
123
|
+
function generateMetaAddress() {
|
|
124
|
+
const spendPrivKey = generateRandomScalar();
|
|
125
|
+
const viewPrivKey = generateRandomScalar();
|
|
126
|
+
const spendPubKey = scalarMultBase(spendPrivKey);
|
|
127
|
+
const viewPubKey = scalarMultBase(viewPrivKey);
|
|
128
|
+
return {
|
|
129
|
+
spendPrivKey,
|
|
130
|
+
viewPrivKey,
|
|
131
|
+
metaAddress: {
|
|
132
|
+
spendPubKey,
|
|
133
|
+
viewPubKey
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function generateRandomScalar() {
|
|
138
|
+
const bytes = utils.randomBytes(32);
|
|
139
|
+
const scalar = utils$1.bytesToNumberLE(bytes) % L;
|
|
140
|
+
return utils$1.numberToBytesLE(scalar, 32);
|
|
141
|
+
}
|
|
142
|
+
function encodeMetaAddress(meta) {
|
|
143
|
+
if (meta.spendPubKey.length !== 32) {
|
|
144
|
+
throw new InvalidMetaAddress("Invalid spend public key length");
|
|
145
|
+
}
|
|
146
|
+
if (meta.viewPubKey.length !== 32) {
|
|
147
|
+
throw new InvalidMetaAddress("Invalid view public key length");
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
validatePoint(meta.spendPubKey);
|
|
151
|
+
validatePoint(meta.viewPubKey);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
if (e instanceof InvalidPublicKey) {
|
|
154
|
+
throw new InvalidMetaAddress(`Invalid public key: ${e.message}`);
|
|
155
|
+
}
|
|
156
|
+
throw e;
|
|
157
|
+
}
|
|
158
|
+
const payload = new Uint8Array(64);
|
|
159
|
+
payload.set(meta.spendPubKey, 0);
|
|
160
|
+
payload.set(meta.viewPubKey, 32);
|
|
161
|
+
const hash = sha256.sha256(payload);
|
|
162
|
+
const checksum = hash.slice(28, 32);
|
|
163
|
+
const combined = new Uint8Array(68);
|
|
164
|
+
combined.set(payload, 0);
|
|
165
|
+
combined.set(checksum, 64);
|
|
166
|
+
const hex = Array.from(combined).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
167
|
+
return `st:stellar:${hex}`;
|
|
168
|
+
}
|
|
169
|
+
function decodeMetaAddress(encoded) {
|
|
170
|
+
if (!encoded.startsWith("st:stellar:")) {
|
|
171
|
+
throw new InvalidMetaAddress("Invalid meta-address prefix");
|
|
172
|
+
}
|
|
173
|
+
const hex = encoded.slice(11);
|
|
174
|
+
if (hex.length !== 136) {
|
|
175
|
+
throw new InvalidMetaAddress("Invalid meta-address length");
|
|
176
|
+
}
|
|
177
|
+
const combined = new Uint8Array(68);
|
|
178
|
+
for (let i = 0; i < 68; i++) {
|
|
179
|
+
const byte = parseInt(hex.substr(i * 2, 2), 16);
|
|
180
|
+
if (isNaN(byte)) {
|
|
181
|
+
throw new InvalidMetaAddress("Invalid hex encoding");
|
|
182
|
+
}
|
|
183
|
+
combined[i] = byte;
|
|
184
|
+
}
|
|
185
|
+
const payload = combined.slice(0, 64);
|
|
186
|
+
const checksum = combined.slice(64, 68);
|
|
187
|
+
const hash = sha256.sha256(payload);
|
|
188
|
+
const expectedChecksum = hash.slice(28, 32);
|
|
189
|
+
let checksumValid = true;
|
|
190
|
+
for (let i = 0; i < 4; i++) {
|
|
191
|
+
if (checksum[i] !== expectedChecksum[i]) {
|
|
192
|
+
checksumValid = false;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!checksumValid) {
|
|
197
|
+
throw new InvalidMetaAddress("Invalid checksum");
|
|
198
|
+
}
|
|
199
|
+
const spendPubKey = payload.slice(0, 32);
|
|
200
|
+
const viewPubKey = payload.slice(32, 64);
|
|
201
|
+
try {
|
|
202
|
+
validatePoint(spendPubKey);
|
|
203
|
+
validatePoint(viewPubKey);
|
|
204
|
+
} catch (e) {
|
|
205
|
+
if (e instanceof InvalidPublicKey) {
|
|
206
|
+
throw new InvalidMetaAddress(`Invalid public key in meta-address: ${e.message}`);
|
|
207
|
+
}
|
|
208
|
+
throw e;
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
spendPubKey,
|
|
212
|
+
viewPubKey
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function hashToScalar(data) {
|
|
216
|
+
const hash = sha256.sha256(data);
|
|
217
|
+
const scalar = utils$1.bytesToNumberLE(hash) % L;
|
|
218
|
+
if (scalar === 0n) {
|
|
219
|
+
throw new InvalidScalar("Hash produced zero scalar");
|
|
220
|
+
}
|
|
221
|
+
return utils$1.numberToBytesLE(scalar, 32);
|
|
222
|
+
}
|
|
223
|
+
function viewTag(sharedSecret) {
|
|
224
|
+
if (sharedSecret.length !== 32) {
|
|
225
|
+
throw new Error("Invalid shared secret length");
|
|
226
|
+
}
|
|
227
|
+
const hash = sha256.sha256(sharedSecret);
|
|
228
|
+
return hash[0];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ../crypto/src/stellar-keys.ts
|
|
232
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
233
|
+
var VERSION_BYTE_PUBLIC_KEY = 6 << 3;
|
|
234
|
+
function crc16XModem(data) {
|
|
235
|
+
let crc = 0;
|
|
236
|
+
for (let i = 0; i < data.length; i++) {
|
|
237
|
+
crc ^= data[i] << 8;
|
|
238
|
+
for (let j = 0; j < 8; j++) {
|
|
239
|
+
if (crc & 32768) {
|
|
240
|
+
crc = crc << 1 ^ 4129;
|
|
241
|
+
} else {
|
|
242
|
+
crc = crc << 1;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return crc & 65535;
|
|
247
|
+
}
|
|
248
|
+
function base32Encode(data) {
|
|
249
|
+
let bits = 0;
|
|
250
|
+
let value = 0;
|
|
251
|
+
let output = "";
|
|
252
|
+
for (let i = 0; i < data.length; i++) {
|
|
253
|
+
value = value << 8 | data[i];
|
|
254
|
+
bits += 8;
|
|
255
|
+
while (bits >= 5) {
|
|
256
|
+
output += ALPHABET[value >>> bits - 5 & 31];
|
|
257
|
+
bits -= 5;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (bits > 0) {
|
|
261
|
+
output += ALPHABET[value << 5 - bits & 31];
|
|
262
|
+
}
|
|
263
|
+
return output;
|
|
264
|
+
}
|
|
265
|
+
function encodePublicKey(pubKey) {
|
|
266
|
+
if (pubKey.length !== 32) {
|
|
267
|
+
throw new Error("Public key must be 32 bytes");
|
|
268
|
+
}
|
|
269
|
+
const versionedPayload = new Uint8Array(33);
|
|
270
|
+
versionedPayload[0] = VERSION_BYTE_PUBLIC_KEY;
|
|
271
|
+
versionedPayload.set(pubKey, 1);
|
|
272
|
+
const checksum = crc16XModem(versionedPayload);
|
|
273
|
+
const checksumBytes = new Uint8Array(2);
|
|
274
|
+
checksumBytes[0] = checksum & 255;
|
|
275
|
+
checksumBytes[1] = checksum >>> 8 & 255;
|
|
276
|
+
const fullPayload = new Uint8Array(35);
|
|
277
|
+
fullPayload.set(versionedPayload, 0);
|
|
278
|
+
fullPayload.set(checksumBytes, 33);
|
|
279
|
+
return base32Encode(fullPayload);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ../crypto/src/scan.ts
|
|
283
|
+
function checkViewTag(viewPrivKey, ephemeralPubKey, expectedTag) {
|
|
284
|
+
if (viewPrivKey.length !== 32) {
|
|
285
|
+
throw new Error("Invalid view private key length");
|
|
286
|
+
}
|
|
287
|
+
if (ephemeralPubKey.length !== 32) {
|
|
288
|
+
throw new Error("Invalid ephemeral public key length");
|
|
289
|
+
}
|
|
290
|
+
if (expectedTag < 0 || expectedTag > 255) {
|
|
291
|
+
throw new Error("Invalid view tag");
|
|
292
|
+
}
|
|
293
|
+
const S = scalarMult(viewPrivKey, ephemeralPubKey);
|
|
294
|
+
const computedTag = viewTag(S);
|
|
295
|
+
if (computedTag === expectedTag) {
|
|
296
|
+
return { matches: true, sharedSecret: S };
|
|
297
|
+
}
|
|
298
|
+
return { matches: false };
|
|
299
|
+
}
|
|
300
|
+
function scanAnnouncements(viewPrivKey, spendPubKey, announcements) {
|
|
301
|
+
if (viewPrivKey.length !== 32) {
|
|
302
|
+
throw new Error("Invalid view private key length");
|
|
303
|
+
}
|
|
304
|
+
if (spendPubKey.length !== 32) {
|
|
305
|
+
throw new Error("Invalid spend public key length");
|
|
306
|
+
}
|
|
307
|
+
const results = [];
|
|
308
|
+
const tagMatches = [];
|
|
309
|
+
for (const announcement of announcements) {
|
|
310
|
+
const tagResult = checkViewTag(viewPrivKey, announcement.ephemeralPubKey, announcement.viewTag);
|
|
311
|
+
if (tagResult.matches && tagResult.sharedSecret) {
|
|
312
|
+
tagMatches.push({ announcement, sharedSecret: tagResult.sharedSecret });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
for (const { announcement, sharedSecret } of tagMatches) {
|
|
316
|
+
const s = hashToScalar(sharedSecret);
|
|
317
|
+
const sG = scalarMultBase(s);
|
|
318
|
+
const P = pointAdd(spendPubKey, sG);
|
|
319
|
+
const computedAddress = encodePublicKey(P);
|
|
320
|
+
if (computedAddress === announcement.stealthAddress) {
|
|
321
|
+
results.push({
|
|
322
|
+
publicKey: P,
|
|
323
|
+
address: computedAddress
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return results;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ../crypto/src/recover.ts
|
|
331
|
+
function recoverStealthPrivateKey(spendPrivKey, viewPrivKey, ephemeralPubKey) {
|
|
332
|
+
if (spendPrivKey.length !== 32) {
|
|
333
|
+
throw new Error("Invalid spend private key length");
|
|
334
|
+
}
|
|
335
|
+
if (viewPrivKey.length !== 32) {
|
|
336
|
+
throw new Error("Invalid view private key length");
|
|
337
|
+
}
|
|
338
|
+
if (ephemeralPubKey.length !== 32) {
|
|
339
|
+
throw new Error("Invalid ephemeral public key length");
|
|
340
|
+
}
|
|
341
|
+
const S = scalarMult(viewPrivKey, ephemeralPubKey);
|
|
342
|
+
const s = hashToScalar(S);
|
|
343
|
+
const stealthPrivKey = scalarAdd(spendPrivKey, s);
|
|
344
|
+
return stealthPrivKey;
|
|
345
|
+
}
|
|
346
|
+
function signWithStealthKey(message, privateScalar) {
|
|
347
|
+
if (privateScalar.length !== 32) {
|
|
348
|
+
throw new Error("Invalid stealth private key length");
|
|
349
|
+
}
|
|
350
|
+
if (message.length === 0) {
|
|
351
|
+
throw new Error("Message cannot be empty");
|
|
352
|
+
}
|
|
353
|
+
const a = utils$1.bytesToNumberLE(privateScalar) % L;
|
|
354
|
+
const pubKeyBytes = scalarMultBase(privateScalar);
|
|
355
|
+
const nonceInput = new Uint8Array(32 + message.length);
|
|
356
|
+
nonceInput.set(privateScalar, 0);
|
|
357
|
+
nonceInput.set(message, 32);
|
|
358
|
+
const r = utils$1.bytesToNumberLE(sha512.sha512(nonceInput)) % L;
|
|
359
|
+
const R = ed25519.ed25519.ExtendedPoint.BASE.multiply(r);
|
|
360
|
+
const Rbytes = R.toRawBytes();
|
|
361
|
+
const challengeInput = new Uint8Array(32 + 32 + message.length);
|
|
362
|
+
challengeInput.set(Rbytes, 0);
|
|
363
|
+
challengeInput.set(pubKeyBytes, 32);
|
|
364
|
+
challengeInput.set(message, 64);
|
|
365
|
+
const k = utils$1.bytesToNumberLE(sha512.sha512(challengeInput)) % L;
|
|
366
|
+
const S = (r + k * a) % L;
|
|
367
|
+
const sig = new Uint8Array(64);
|
|
368
|
+
sig.set(Rbytes, 0);
|
|
369
|
+
sig.set(utils$1.numberToBytesLE(S, 32), 32);
|
|
370
|
+
return sig;
|
|
371
|
+
}
|
|
372
|
+
function deriveStealthAddressWithSecret(spendPubKey, viewPubKey, ephemeralPrivKey) {
|
|
373
|
+
const R = scalarMultBase(ephemeralPrivKey);
|
|
374
|
+
const S = scalarMult(ephemeralPrivKey, viewPubKey);
|
|
375
|
+
const s = hashToScalar(S);
|
|
376
|
+
const sG = scalarMultBase(s);
|
|
377
|
+
const P = pointAdd(spendPubKey, sG);
|
|
378
|
+
const tag = viewTag(S);
|
|
379
|
+
return {
|
|
380
|
+
stealthPubKey: P,
|
|
381
|
+
stealthAddress: encodePublicKey(P),
|
|
382
|
+
ephemeralPubKey: R,
|
|
383
|
+
viewTag: tag,
|
|
384
|
+
ephemeralPrivKey,
|
|
385
|
+
sharedSecret: S
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
var textEncoder = new TextEncoder();
|
|
389
|
+
function generateMnemonic() {
|
|
390
|
+
return bip39.generateMnemonic(english.wordlist);
|
|
391
|
+
}
|
|
392
|
+
function mnemonicToStealthKeys(mnemonic, passphrase) {
|
|
393
|
+
if (!bip39.validateMnemonic(mnemonic, english.wordlist)) {
|
|
394
|
+
throw new Error("Invalid mnemonic phrase");
|
|
395
|
+
}
|
|
396
|
+
const seed = bip39.mnemonicToSeedSync(mnemonic, "");
|
|
397
|
+
const spendTag = textEncoder.encode("stealth-spend");
|
|
398
|
+
const viewTag2 = textEncoder.encode("stealth-view");
|
|
399
|
+
const spendInput = new Uint8Array(spendTag.length + seed.length);
|
|
400
|
+
spendInput.set(spendTag, 0);
|
|
401
|
+
spendInput.set(seed, spendTag.length);
|
|
402
|
+
const viewInput = new Uint8Array(viewTag2.length + seed.length);
|
|
403
|
+
viewInput.set(viewTag2, 0);
|
|
404
|
+
viewInput.set(seed, viewTag2.length);
|
|
405
|
+
const spendPrivKey = hashToScalar(spendInput);
|
|
406
|
+
const viewPrivKey = hashToScalar(viewInput);
|
|
407
|
+
seed.fill(0);
|
|
408
|
+
spendInput.fill(0);
|
|
409
|
+
viewInput.fill(0);
|
|
410
|
+
const spendPubKey = scalarMultBase(spendPrivKey);
|
|
411
|
+
const viewPubKey = scalarMultBase(viewPrivKey);
|
|
412
|
+
return {
|
|
413
|
+
spendPrivKey,
|
|
414
|
+
viewPrivKey,
|
|
415
|
+
metaAddress: { spendPubKey, viewPubKey }
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function getNetworkConfig(network) {
|
|
419
|
+
const networkPassphrase = network === "local" ? StellarSdk.Networks.STANDALONE : StellarSdk.Networks.TESTNET;
|
|
420
|
+
const rpcUrl = network === "local" ? "http://localhost:8000/soroban/rpc" : "https://soroban-testnet.stellar.org";
|
|
421
|
+
const server = new StellarSdk__namespace.rpc.Server(rpcUrl, {
|
|
422
|
+
allowHttp: network === "local"
|
|
423
|
+
});
|
|
424
|
+
return { networkPassphrase, rpcUrl, server };
|
|
425
|
+
}
|
|
426
|
+
function createSimulationTx(operation, networkPassphrase) {
|
|
427
|
+
return new StellarSdk.TransactionBuilder(
|
|
428
|
+
new StellarSdk__namespace.Account("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF", "0"),
|
|
429
|
+
{ fee: "100", networkPassphrase }
|
|
430
|
+
).addOperation(operation).setTimeout(30).build();
|
|
431
|
+
}
|
|
432
|
+
async function simulateReadOnly(contractId, method, args, server, networkPassphrase) {
|
|
433
|
+
const contract = new StellarSdk.Contract(contractId);
|
|
434
|
+
const op = contract.call(method, ...args);
|
|
435
|
+
const sim = await server.simulateTransaction(
|
|
436
|
+
createSimulationTx(op, networkPassphrase)
|
|
437
|
+
);
|
|
438
|
+
if (StellarSdk__namespace.rpc.Api.isSimulationSuccess(sim) && sim.result?.retval) {
|
|
439
|
+
return StellarSdk__namespace.scValToNative(sim.result.retval);
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
function resolveTokenAddress(assetArg, networkPassphrase) {
|
|
444
|
+
if (!assetArg || assetArg === "native" || assetArg === "XLM") {
|
|
445
|
+
return StellarSdk.Asset.native().contractId(networkPassphrase);
|
|
446
|
+
}
|
|
447
|
+
const parts = assetArg.split(":");
|
|
448
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
449
|
+
throw new Error('Invalid asset format. Use CODE:ISSUER or "native"');
|
|
450
|
+
}
|
|
451
|
+
return new StellarSdk.Asset(parts[0], parts[1]).contractId(networkPassphrase);
|
|
452
|
+
}
|
|
453
|
+
async function waitForTransaction(server, hash) {
|
|
454
|
+
let attempts = 0;
|
|
455
|
+
while (attempts < 30) {
|
|
456
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
457
|
+
try {
|
|
458
|
+
const result = await server.getTransaction(hash);
|
|
459
|
+
if (result.status === "SUCCESS") return;
|
|
460
|
+
if (result.status === "FAILED") throw new Error("Transaction failed on-chain");
|
|
461
|
+
} catch (e) {
|
|
462
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
463
|
+
if (msg === "Transaction failed on-chain") throw e;
|
|
464
|
+
}
|
|
465
|
+
attempts++;
|
|
466
|
+
}
|
|
467
|
+
throw new Error("Transaction confirmation timed out");
|
|
468
|
+
}
|
|
469
|
+
async function fetchAnnouncements(contractId, server, networkPassphrase) {
|
|
470
|
+
const result = await simulateReadOnly(
|
|
471
|
+
contractId,
|
|
472
|
+
"get_announcements",
|
|
473
|
+
[StellarSdk.nativeToScVal(0, { type: "u64" }), StellarSdk.nativeToScVal(1e3, { type: "u64" })],
|
|
474
|
+
server,
|
|
475
|
+
networkPassphrase
|
|
476
|
+
);
|
|
477
|
+
if (!result || !Array.isArray(result)) return [];
|
|
478
|
+
return result.map((ann) => {
|
|
479
|
+
const a = ann;
|
|
480
|
+
const stealthPk = new Uint8Array(a.stealth_pk);
|
|
481
|
+
return {
|
|
482
|
+
ephemeralPubKey: new Uint8Array(a.ephemeral_pk),
|
|
483
|
+
viewTag: a.view_tag,
|
|
484
|
+
stealthPubKey: stealthPk,
|
|
485
|
+
stealthAddress: StellarSdk.StrKey.encodeEd25519PublicKey(Buffer.from(stealthPk)),
|
|
486
|
+
token: a.token?.toString?.() || "unknown",
|
|
487
|
+
amount: BigInt(a.amount || 0)
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
async function queryBalance(contractId, stealthPk, tokenAddress, server, networkPassphrase) {
|
|
492
|
+
const result = await simulateReadOnly(
|
|
493
|
+
contractId,
|
|
494
|
+
"get_balance",
|
|
495
|
+
[StellarSdk.nativeToScVal(Buffer.from(stealthPk)), new StellarSdk__namespace.Address(tokenAddress).toScVal()],
|
|
496
|
+
server,
|
|
497
|
+
networkPassphrase
|
|
498
|
+
);
|
|
499
|
+
if (result !== null) return BigInt(result);
|
|
500
|
+
return 0n;
|
|
501
|
+
}
|
|
502
|
+
async function queryNonce(contractId, stealthPk, server, networkPassphrase) {
|
|
503
|
+
const result = await simulateReadOnly(
|
|
504
|
+
contractId,
|
|
505
|
+
"get_nonce",
|
|
506
|
+
[StellarSdk.nativeToScVal(Buffer.from(stealthPk))],
|
|
507
|
+
server,
|
|
508
|
+
networkPassphrase
|
|
509
|
+
);
|
|
510
|
+
if (result !== null) return BigInt(result);
|
|
511
|
+
return 0n;
|
|
512
|
+
}
|
|
513
|
+
function i128ToBigEndian(value) {
|
|
514
|
+
const buf = new Uint8Array(16);
|
|
515
|
+
const dv = new DataView(buf.buffer);
|
|
516
|
+
dv.setBigInt64(0, value >> 64n);
|
|
517
|
+
dv.setBigUint64(8, value & 0xFFFFFFFFFFFFFFFFn);
|
|
518
|
+
return buf;
|
|
519
|
+
}
|
|
520
|
+
function u64ToBigEndian(value) {
|
|
521
|
+
const buf = new Uint8Array(8);
|
|
522
|
+
const dv = new DataView(buf.buffer);
|
|
523
|
+
dv.setBigUint64(0, value);
|
|
524
|
+
return buf;
|
|
525
|
+
}
|
|
526
|
+
function buildWithdrawMessage(stealthPk, tokenAddress, amount, destination, nonce) {
|
|
527
|
+
const tokenBytes = Buffer.from(tokenAddress, "utf-8");
|
|
528
|
+
const destBytes = Buffer.from(destination, "utf-8");
|
|
529
|
+
if (tokenBytes.length !== 56) throw new Error(`Token address must be 56 bytes StrKey, got ${tokenBytes.length}`);
|
|
530
|
+
if (destBytes.length !== 56) throw new Error(`Destination must be 56 bytes StrKey, got ${destBytes.length}`);
|
|
531
|
+
const amountBytes = i128ToBigEndian(amount);
|
|
532
|
+
const nonceBytes = u64ToBigEndian(nonce);
|
|
533
|
+
const msg = new Uint8Array(32 + 56 + 16 + 56 + 8);
|
|
534
|
+
let offset = 0;
|
|
535
|
+
msg.set(stealthPk, offset);
|
|
536
|
+
offset += 32;
|
|
537
|
+
msg.set(tokenBytes, offset);
|
|
538
|
+
offset += 56;
|
|
539
|
+
msg.set(amountBytes, offset);
|
|
540
|
+
offset += 16;
|
|
541
|
+
msg.set(destBytes, offset);
|
|
542
|
+
offset += 56;
|
|
543
|
+
msg.set(nonceBytes, offset);
|
|
544
|
+
return sha256.sha256(msg);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/client.ts
|
|
548
|
+
var DEFAULT_CONTRACTS = {
|
|
549
|
+
local: "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGABAX"
|
|
550
|
+
};
|
|
551
|
+
var StealthClient = class {
|
|
552
|
+
contractId;
|
|
553
|
+
networkPassphrase;
|
|
554
|
+
server;
|
|
555
|
+
constructor(config) {
|
|
556
|
+
this.contractId = config.contractId || DEFAULT_CONTRACTS[config.network] || "";
|
|
557
|
+
const netConfig = getNetworkConfig(config.network);
|
|
558
|
+
this.networkPassphrase = netConfig.networkPassphrase;
|
|
559
|
+
this.server = netConfig.server;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Generate a new random stealth key pair.
|
|
563
|
+
* No network connection needed.
|
|
564
|
+
*/
|
|
565
|
+
static keygen() {
|
|
566
|
+
const keys = generateMetaAddress();
|
|
567
|
+
const metaAddress = encodeMetaAddress(keys.metaAddress);
|
|
568
|
+
return {
|
|
569
|
+
metaAddress,
|
|
570
|
+
spendPubKey: Buffer.from(keys.metaAddress.spendPubKey).toString("hex"),
|
|
571
|
+
spendPrivKey: Buffer.from(keys.spendPrivKey).toString("hex"),
|
|
572
|
+
viewPubKey: Buffer.from(keys.metaAddress.viewPubKey).toString("hex"),
|
|
573
|
+
viewPrivKey: Buffer.from(keys.viewPrivKey).toString("hex")
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Generate stealth keys from a BIP-39 mnemonic.
|
|
578
|
+
* Returns the mnemonic alongside the keys for backup.
|
|
579
|
+
* No network connection needed.
|
|
580
|
+
*/
|
|
581
|
+
static fromMnemonic(mnemonic) {
|
|
582
|
+
const phrase = mnemonic || generateMnemonic();
|
|
583
|
+
const keys = mnemonicToStealthKeys(phrase);
|
|
584
|
+
const metaAddress = encodeMetaAddress(keys.metaAddress);
|
|
585
|
+
return {
|
|
586
|
+
mnemonic: phrase,
|
|
587
|
+
metaAddress,
|
|
588
|
+
spendPubKey: Buffer.from(keys.metaAddress.spendPubKey).toString("hex"),
|
|
589
|
+
spendPrivKey: Buffer.from(keys.spendPrivKey).toString("hex"),
|
|
590
|
+
viewPubKey: Buffer.from(keys.metaAddress.viewPubKey).toString("hex"),
|
|
591
|
+
viewPrivKey: Buffer.from(keys.viewPrivKey).toString("hex")
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Send tokens to a stealth address.
|
|
596
|
+
*
|
|
597
|
+
* Derives a one-time stealth address from the recipient's meta-address,
|
|
598
|
+
* deposits tokens into the pool contract, and records the announcement.
|
|
599
|
+
*
|
|
600
|
+
* @param metaAddress - Recipient's meta-address (st:stellar:... format)
|
|
601
|
+
* @param amount - Amount in whole units (e.g. 100 = 100 XLM)
|
|
602
|
+
* @param senderSecret - Sender's Stellar secret key
|
|
603
|
+
* @param opts - Optional: asset to send
|
|
604
|
+
*/
|
|
605
|
+
async send(metaAddress, amount, senderSecret, opts) {
|
|
606
|
+
if (amount <= 0) throw new Error("Amount must be positive");
|
|
607
|
+
const { spendPubKey, viewPubKey } = decodeMetaAddress(metaAddress);
|
|
608
|
+
const senderKeypair = StellarSdk.Keypair.fromSecret(senderSecret);
|
|
609
|
+
const tokenAddress = resolveTokenAddress(opts?.asset, this.networkPassphrase);
|
|
610
|
+
const stroops = BigInt(Math.round(amount * 1e7));
|
|
611
|
+
const ephemeralPrivKey = new Uint8Array(utils.randomBytes(32));
|
|
612
|
+
const stealth = deriveStealthAddressWithSecret(
|
|
613
|
+
spendPubKey,
|
|
614
|
+
viewPubKey,
|
|
615
|
+
ephemeralPrivKey
|
|
616
|
+
);
|
|
617
|
+
const contract = new StellarSdk.Contract(this.contractId);
|
|
618
|
+
const account = await this.server.getAccount(senderKeypair.publicKey());
|
|
619
|
+
const depositTx = new StellarSdk.TransactionBuilder(account, {
|
|
620
|
+
fee: "100",
|
|
621
|
+
networkPassphrase: this.networkPassphrase
|
|
622
|
+
}).addOperation(
|
|
623
|
+
contract.call(
|
|
624
|
+
"deposit",
|
|
625
|
+
new StellarSdk__namespace.Address(senderKeypair.publicKey()).toScVal(),
|
|
626
|
+
new StellarSdk__namespace.Address(tokenAddress).toScVal(),
|
|
627
|
+
StellarSdk.nativeToScVal(stroops, { type: "i128" }),
|
|
628
|
+
StellarSdk.nativeToScVal(Buffer.from(stealth.stealthPubKey)),
|
|
629
|
+
StellarSdk.nativeToScVal(Buffer.from(stealth.ephemeralPubKey)),
|
|
630
|
+
StellarSdk.nativeToScVal(stealth.viewTag, { type: "u32" })
|
|
631
|
+
)
|
|
632
|
+
).setTimeout(30).build();
|
|
633
|
+
const prepared = await this.server.prepareTransaction(depositTx);
|
|
634
|
+
prepared.sign(senderKeypair);
|
|
635
|
+
const result = await this.server.sendTransaction(prepared);
|
|
636
|
+
if (result.status === "ERROR") {
|
|
637
|
+
throw new Error("Transaction submission failed");
|
|
638
|
+
}
|
|
639
|
+
if (result.status === "PENDING") {
|
|
640
|
+
await waitForTransaction(this.server, result.hash);
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
stealthAddress: stealth.stealthAddress,
|
|
644
|
+
txHash: result.hash
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Scan for stealth payments you received.
|
|
649
|
+
*
|
|
650
|
+
* Uses the view key to detect which announcements are yours,
|
|
651
|
+
* then queries the contract for current balances.
|
|
652
|
+
*
|
|
653
|
+
* @param keys - Your stealth keys (needs viewPrivKey + spendPubKey)
|
|
654
|
+
*/
|
|
655
|
+
async scan(keys) {
|
|
656
|
+
const viewPrivKey = Buffer.from(keys.viewPrivKey, "hex");
|
|
657
|
+
const spendPubKey = Buffer.from(keys.spendPubKey, "hex");
|
|
658
|
+
const announcements = await fetchAnnouncements(
|
|
659
|
+
this.contractId,
|
|
660
|
+
this.server,
|
|
661
|
+
this.networkPassphrase
|
|
662
|
+
);
|
|
663
|
+
if (announcements.length === 0) return [];
|
|
664
|
+
const matches = scanAnnouncements(
|
|
665
|
+
viewPrivKey,
|
|
666
|
+
spendPubKey,
|
|
667
|
+
announcements.map((a) => ({
|
|
668
|
+
ephemeralPubKey: a.ephemeralPubKey,
|
|
669
|
+
viewTag: a.viewTag,
|
|
670
|
+
stealthAddress: a.stealthAddress
|
|
671
|
+
}))
|
|
672
|
+
);
|
|
673
|
+
const payments = [];
|
|
674
|
+
for (const match of matches) {
|
|
675
|
+
if (!match) continue;
|
|
676
|
+
const ann = announcements.find((a) => a.stealthAddress === match.address);
|
|
677
|
+
if (!ann) continue;
|
|
678
|
+
const balance = await queryBalance(
|
|
679
|
+
this.contractId,
|
|
680
|
+
ann.stealthPubKey,
|
|
681
|
+
ann.token,
|
|
682
|
+
this.server,
|
|
683
|
+
this.networkPassphrase
|
|
684
|
+
);
|
|
685
|
+
if (balance <= 0n) continue;
|
|
686
|
+
payments.push({
|
|
687
|
+
stealthAddress: ann.stealthAddress,
|
|
688
|
+
ephemeralPubKey: Buffer.from(ann.ephemeralPubKey).toString("hex"),
|
|
689
|
+
token: ann.token,
|
|
690
|
+
amount: Number(balance) / 1e7
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return payments;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Get balances for all your stealth addresses in the pool.
|
|
697
|
+
*
|
|
698
|
+
* @param keys - Your stealth keys (needs viewPrivKey + spendPubKey)
|
|
699
|
+
*/
|
|
700
|
+
async balance(keys) {
|
|
701
|
+
const payments = await this.scan(keys);
|
|
702
|
+
return payments.map((p) => ({
|
|
703
|
+
stealthAddress: p.stealthAddress,
|
|
704
|
+
token: p.token,
|
|
705
|
+
amount: p.amount
|
|
706
|
+
}));
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Withdraw tokens from the stealth pool.
|
|
710
|
+
*
|
|
711
|
+
* Recovers the stealth private key, signs a withdraw message,
|
|
712
|
+
* and submits the transaction. Use `opts.relay` for privacy-preserving
|
|
713
|
+
* withdrawal via a relayer.
|
|
714
|
+
*
|
|
715
|
+
* @param stealthAddress - The stealth address to withdraw from
|
|
716
|
+
* @param destination - Destination Stellar address (G...)
|
|
717
|
+
* @param opts - Withdraw options (keys, feePayer, optional relay/asset/amount)
|
|
718
|
+
*/
|
|
719
|
+
async withdraw(stealthAddress, destination, opts) {
|
|
720
|
+
if (!StellarSdk.StrKey.isValidEd25519PublicKey(stealthAddress)) {
|
|
721
|
+
throw new Error("Invalid stealth address");
|
|
722
|
+
}
|
|
723
|
+
if (!StellarSdk.StrKey.isValidEd25519PublicKey(destination)) {
|
|
724
|
+
throw new Error("Invalid destination address");
|
|
725
|
+
}
|
|
726
|
+
const viewPrivKey = Buffer.from(opts.keys.viewPrivKey, "hex");
|
|
727
|
+
const spendPrivKey = Buffer.from(opts.keys.spendPrivKey, "hex");
|
|
728
|
+
const spendPubKey = Buffer.from(opts.keys.spendPubKey, "hex");
|
|
729
|
+
const tokenAddress = resolveTokenAddress(opts.asset, this.networkPassphrase);
|
|
730
|
+
const announcements = await fetchAnnouncements(
|
|
731
|
+
this.contractId,
|
|
732
|
+
this.server,
|
|
733
|
+
this.networkPassphrase
|
|
734
|
+
);
|
|
735
|
+
const allMatches = scanAnnouncements(
|
|
736
|
+
viewPrivKey,
|
|
737
|
+
spendPubKey,
|
|
738
|
+
announcements.map((a) => ({
|
|
739
|
+
ephemeralPubKey: a.ephemeralPubKey,
|
|
740
|
+
viewTag: a.viewTag,
|
|
741
|
+
stealthAddress: a.stealthAddress
|
|
742
|
+
}))
|
|
743
|
+
);
|
|
744
|
+
const matchedAnn = announcements.find((a) => {
|
|
745
|
+
if (a.stealthAddress !== stealthAddress) return false;
|
|
746
|
+
return allMatches.some((m) => m?.address === stealthAddress);
|
|
747
|
+
});
|
|
748
|
+
if (!matchedAnn) {
|
|
749
|
+
throw new Error("Could not find announcement for this stealth address");
|
|
750
|
+
}
|
|
751
|
+
const stealthPrivKey = recoverStealthPrivateKey(
|
|
752
|
+
spendPrivKey,
|
|
753
|
+
viewPrivKey,
|
|
754
|
+
matchedAnn.ephemeralPubKey
|
|
755
|
+
);
|
|
756
|
+
const balance = await queryBalance(
|
|
757
|
+
this.contractId,
|
|
758
|
+
matchedAnn.stealthPubKey,
|
|
759
|
+
tokenAddress,
|
|
760
|
+
this.server,
|
|
761
|
+
this.networkPassphrase
|
|
762
|
+
);
|
|
763
|
+
if (balance <= 0n) throw new Error("Stealth address has no balance in the pool");
|
|
764
|
+
let withdrawAmount;
|
|
765
|
+
if (opts.amount !== void 0) {
|
|
766
|
+
withdrawAmount = BigInt(Math.round(opts.amount * 1e7));
|
|
767
|
+
if (withdrawAmount > balance) {
|
|
768
|
+
throw new Error(`Requested ${opts.amount} but balance is ${Number(balance) / 1e7}`);
|
|
769
|
+
}
|
|
770
|
+
} else {
|
|
771
|
+
withdrawAmount = balance;
|
|
772
|
+
}
|
|
773
|
+
const currentNonce = await queryNonce(
|
|
774
|
+
this.contractId,
|
|
775
|
+
matchedAnn.stealthPubKey,
|
|
776
|
+
this.server,
|
|
777
|
+
this.networkPassphrase
|
|
778
|
+
);
|
|
779
|
+
const nonce = currentNonce + 1n;
|
|
780
|
+
const messageHash = buildWithdrawMessage(
|
|
781
|
+
matchedAnn.stealthPubKey,
|
|
782
|
+
tokenAddress,
|
|
783
|
+
withdrawAmount,
|
|
784
|
+
destination,
|
|
785
|
+
nonce
|
|
786
|
+
);
|
|
787
|
+
const signature = signWithStealthKey(messageHash, stealthPrivKey);
|
|
788
|
+
const feePayerKeypair = StellarSdk.Keypair.fromSecret(opts.feePayer);
|
|
789
|
+
const contract = new StellarSdk.Contract(this.contractId);
|
|
790
|
+
const feePayerAccount = await this.server.getAccount(feePayerKeypair.publicKey());
|
|
791
|
+
const withdrawTx = new StellarSdk.TransactionBuilder(feePayerAccount, {
|
|
792
|
+
fee: "100",
|
|
793
|
+
networkPassphrase: this.networkPassphrase
|
|
794
|
+
}).addOperation(
|
|
795
|
+
contract.call(
|
|
796
|
+
"withdraw",
|
|
797
|
+
StellarSdk.nativeToScVal(Buffer.from(matchedAnn.stealthPubKey)),
|
|
798
|
+
new StellarSdk__namespace.Address(tokenAddress).toScVal(),
|
|
799
|
+
StellarSdk.nativeToScVal(withdrawAmount, { type: "i128" }),
|
|
800
|
+
new StellarSdk__namespace.Address(destination).toScVal(),
|
|
801
|
+
StellarSdk.nativeToScVal(nonce, { type: "u64" }),
|
|
802
|
+
StellarSdk.nativeToScVal(Buffer.from(signature))
|
|
803
|
+
)
|
|
804
|
+
).setTimeout(30).build();
|
|
805
|
+
const prepared = await this.server.prepareTransaction(withdrawTx);
|
|
806
|
+
prepared.sign(feePayerKeypair);
|
|
807
|
+
let txHash;
|
|
808
|
+
if (opts.relay) {
|
|
809
|
+
const url = opts.relay.endsWith("/relay") ? opts.relay : `${opts.relay}/relay`;
|
|
810
|
+
const res = await fetch(url, {
|
|
811
|
+
method: "POST",
|
|
812
|
+
headers: { "Content-Type": "application/json" },
|
|
813
|
+
body: JSON.stringify({ xdr: prepared.toEnvelope().toXDR("base64") })
|
|
814
|
+
});
|
|
815
|
+
if (!res.ok) {
|
|
816
|
+
const err = await res.json();
|
|
817
|
+
throw new Error(`Relay error: ${err.error || "unknown"}`);
|
|
818
|
+
}
|
|
819
|
+
const data = await res.json();
|
|
820
|
+
txHash = data.txHash;
|
|
821
|
+
} else {
|
|
822
|
+
const result = await this.server.sendTransaction(prepared);
|
|
823
|
+
if (result.status === "ERROR") throw new Error("Transaction submission failed");
|
|
824
|
+
if (result.status === "PENDING") {
|
|
825
|
+
await waitForTransaction(this.server, result.hash);
|
|
826
|
+
}
|
|
827
|
+
txHash = result.hash;
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
txHash,
|
|
831
|
+
amount: Number(withdrawAmount) / 1e7
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
exports.StealthClient = StealthClient;
|
|
837
|
+
//# sourceMappingURL=index.cjs.map
|
|
838
|
+
//# sourceMappingURL=index.cjs.map
|