@pioneer-platform/pioneer-coins 9.3.0 → 9.3.2
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/lib/thorchain.d.ts +27 -0
- package/lib/thorchain.js +260 -1
- package/package.json +1 -1
package/lib/thorchain.d.ts
CHANGED
|
@@ -3,6 +3,33 @@ interface BaseTx {
|
|
|
3
3
|
asset: string;
|
|
4
4
|
destAddr: string;
|
|
5
5
|
}
|
|
6
|
+
export interface MemoValidation {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
errors: string[];
|
|
9
|
+
warnings: string[];
|
|
10
|
+
normalized?: string;
|
|
11
|
+
fields?: {
|
|
12
|
+
chain: string;
|
|
13
|
+
symbol: string;
|
|
14
|
+
destination: string;
|
|
15
|
+
limit?: string;
|
|
16
|
+
stream?: string;
|
|
17
|
+
affiliateTag?: string;
|
|
18
|
+
affiliateBP?: number;
|
|
19
|
+
};
|
|
20
|
+
suggestedFix?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Comprehensive THORChain Swap Memo Validator
|
|
24
|
+
*
|
|
25
|
+
* Validates memos of the form:
|
|
26
|
+
* =:<CHAIN>.<SYMBOL>:<DEST>[:<limit>[/<stream]][:<affiliateTag>:<affiliateBP>]
|
|
27
|
+
*
|
|
28
|
+
* @param memoRaw - The raw memo string to validate
|
|
29
|
+
* @returns MemoValidation object with validation results
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateThorchainSwapMemo(memoRaw: string): MemoValidation;
|
|
32
|
+
export declare function validateMemo(memo: string): boolean;
|
|
6
33
|
export declare function normalizeSwapMemo(memo: string): string;
|
|
7
34
|
export declare function createMemo(tx: BaseTx): string;
|
|
8
35
|
export declare function parseMemo(memo: string): BaseTx;
|
package/lib/thorchain.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
----------------------
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.validateThorchainSwapMemo = validateThorchainSwapMemo;
|
|
8
|
+
exports.validateMemo = validateMemo;
|
|
7
9
|
exports.normalizeSwapMemo = normalizeSwapMemo;
|
|
8
10
|
exports.createMemo = createMemo;
|
|
9
11
|
exports.parseMemo = parseMemo;
|
|
@@ -198,7 +200,236 @@ function buildNoOpMemo(tx) {
|
|
|
198
200
|
];
|
|
199
201
|
return truncateMemo(parts.filter(function (part) { return part; }).join(':'));
|
|
200
202
|
}
|
|
201
|
-
//
|
|
203
|
+
// Basic destination sanity check by chain
|
|
204
|
+
function basicDestCheck(chain, dest) {
|
|
205
|
+
if (chain === 'BTC') {
|
|
206
|
+
// BTC: accept base58 (1..,3..) or bech32 (bc1.., tb1..)
|
|
207
|
+
var isB58 = /^[123mn][1-9A-HJ-NP-Za-km-z]{24,}$/;
|
|
208
|
+
var isBech32 = /^(bc1|tb1)[a-z0-9]{20,}$/;
|
|
209
|
+
if (!isB58.test(dest) && !isBech32.test(dest)) {
|
|
210
|
+
return 'destination does not look like a valid BTC address';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (chain === 'ETH') {
|
|
214
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(dest)) {
|
|
215
|
+
return 'destination is not a valid Ethereum address';
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (chain === 'DASH') {
|
|
219
|
+
if (!/^X[1-9A-HJ-NP-Za-km-z]{33}$/.test(dest)) {
|
|
220
|
+
return 'destination does not look like a valid DASH address';
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Comprehensive THORChain Swap Memo Validator
|
|
227
|
+
*
|
|
228
|
+
* Validates memos of the form:
|
|
229
|
+
* =:<CHAIN>.<SYMBOL>:<DEST>[:<limit>[/<stream]][:<affiliateTag>:<affiliateBP>]
|
|
230
|
+
*
|
|
231
|
+
* @param memoRaw - The raw memo string to validate
|
|
232
|
+
* @returns MemoValidation object with validation results
|
|
233
|
+
*/
|
|
234
|
+
function validateThorchainSwapMemo(memoRaw) {
|
|
235
|
+
var errors = [];
|
|
236
|
+
var warnings = [];
|
|
237
|
+
var MAX_MEMO_LEN = 250;
|
|
238
|
+
var ASCII_SAFE = /^[\x20-\x7E]+$/; // printable ASCII (no control chars)
|
|
239
|
+
if (!memoRaw || memoRaw.trim() === '') {
|
|
240
|
+
return { ok: false, errors: ['memo is empty'], warnings: warnings };
|
|
241
|
+
}
|
|
242
|
+
var memo = memoRaw.trim();
|
|
243
|
+
if (memo.length > MAX_MEMO_LEN) {
|
|
244
|
+
errors.push("memo exceeds ".concat(MAX_MEMO_LEN, " bytes"));
|
|
245
|
+
}
|
|
246
|
+
if (!ASCII_SAFE.test(memo)) {
|
|
247
|
+
errors.push('memo must be printable ASCII (no control characters)');
|
|
248
|
+
}
|
|
249
|
+
if (!memo.startsWith('=:')) {
|
|
250
|
+
errors.push('memo must start with "=:<CHAIN>.<SYMBOL>:"');
|
|
251
|
+
}
|
|
252
|
+
// Early exit if start is wrong
|
|
253
|
+
if (errors.length)
|
|
254
|
+
return { ok: false, errors: errors, warnings: warnings };
|
|
255
|
+
// Split by colon
|
|
256
|
+
var parts = memo.slice(2).split(':');
|
|
257
|
+
if (parts.length < 2) {
|
|
258
|
+
errors.push('memo missing destination segment');
|
|
259
|
+
return { ok: false, errors: errors, warnings: warnings };
|
|
260
|
+
}
|
|
261
|
+
var chainDotSymbol = parts[0];
|
|
262
|
+
var shorthandFix;
|
|
263
|
+
// Check if it's a shorthand that needs expansion
|
|
264
|
+
var maybeSh = chainDotSymbol.toLowerCase();
|
|
265
|
+
if (assetShortCodeMap[maybeSh]) {
|
|
266
|
+
// This is a shorthand - it's invalid in the memo itself
|
|
267
|
+
errors.push("shorthand \"".concat(chainDotSymbol, "\" not allowed - must use full CHAIN.SYMBOL format"));
|
|
268
|
+
shorthandFix = assetShortCodeMap[maybeSh];
|
|
269
|
+
if (shorthandFix) {
|
|
270
|
+
chainDotSymbol = shorthandFix;
|
|
271
|
+
warnings.push("expanded shorthand \"".concat(parts[0], "\" to \"").concat(shorthandFix, "\""));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
var cs = chainDotSymbol.split('.');
|
|
275
|
+
if (cs.length !== 2) {
|
|
276
|
+
errors.push('first segment must be "<CHAIN>.<SYMBOL>" (e.g., BTC.BTC)');
|
|
277
|
+
if (cs.length === 1 && !assetShortCodeMap[cs[0].toLowerCase()]) {
|
|
278
|
+
// Not even a valid shorthand
|
|
279
|
+
errors.push("\"".concat(cs[0], "\" is not a valid chain identifier"));
|
|
280
|
+
}
|
|
281
|
+
return { ok: false, errors: errors, warnings: warnings };
|
|
282
|
+
}
|
|
283
|
+
var chain = cs[0];
|
|
284
|
+
var symbol = cs[1];
|
|
285
|
+
if (!/^[A-Z0-9]+$/.test(chain) || !/^[A-Z0-9\-]+$/.test(symbol)) {
|
|
286
|
+
errors.push('CHAIN and SYMBOL must be uppercase alphanumerics');
|
|
287
|
+
}
|
|
288
|
+
var destination = parts[1];
|
|
289
|
+
if (!destination)
|
|
290
|
+
errors.push('destination is empty');
|
|
291
|
+
// Optional limit/stream
|
|
292
|
+
var limit;
|
|
293
|
+
var stream;
|
|
294
|
+
if (parts[2] !== undefined && parts[2] !== '') {
|
|
295
|
+
var ls = parts[2].split('/');
|
|
296
|
+
limit = ls[0];
|
|
297
|
+
if (limit && !/^\d+$/.test(limit)) {
|
|
298
|
+
errors.push('limit must be a non-negative integer');
|
|
299
|
+
}
|
|
300
|
+
if (ls.length > 1) {
|
|
301
|
+
stream = ls[1];
|
|
302
|
+
if (stream && !/^\d+$/.test(stream)) {
|
|
303
|
+
errors.push('stream must be a non-negative integer');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Optional affiliate
|
|
308
|
+
var affiliateTag;
|
|
309
|
+
var affiliateBP;
|
|
310
|
+
if (parts[3] !== undefined && parts[3] !== '') {
|
|
311
|
+
affiliateTag = parts[3];
|
|
312
|
+
if (/\s/.test(affiliateTag))
|
|
313
|
+
warnings.push('affiliateTag should not contain spaces');
|
|
314
|
+
}
|
|
315
|
+
if (parts[4] !== undefined && parts[4] !== '') {
|
|
316
|
+
if (!/^\d+$/.test(parts[4])) {
|
|
317
|
+
errors.push('affiliateBP must be integer 0..1000');
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
affiliateBP = Number(parts[4]);
|
|
321
|
+
if (affiliateBP < 0 || affiliateBP > 1000) {
|
|
322
|
+
errors.push('affiliateBP out of range (0..1000)');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Destination validation - convert warnings to errors for critical issues
|
|
327
|
+
var destErr = basicDestCheck(chain, destination);
|
|
328
|
+
if (destErr) {
|
|
329
|
+
// For ETH, a bad address is an error, not a warning
|
|
330
|
+
if (chain === 'ETH' && destination.length < 42) {
|
|
331
|
+
errors.push(destErr);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
warnings.push(destErr);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
var normalized = "=:" +
|
|
338
|
+
"".concat(chain, ".").concat(symbol, ":") +
|
|
339
|
+
"".concat(destination) +
|
|
340
|
+
(limit !== undefined || stream !== undefined
|
|
341
|
+
? ":".concat(limit !== null && limit !== void 0 ? limit : '0').concat(stream !== undefined ? "/".concat(stream) : '')
|
|
342
|
+
: '') +
|
|
343
|
+
(affiliateTag !== undefined
|
|
344
|
+
? ":".concat(affiliateTag, ":").concat(affiliateBP !== null && affiliateBP !== void 0 ? affiliateBP : 0)
|
|
345
|
+
: '');
|
|
346
|
+
return {
|
|
347
|
+
ok: errors.length === 0,
|
|
348
|
+
errors: errors,
|
|
349
|
+
warnings: warnings,
|
|
350
|
+
normalized: normalized,
|
|
351
|
+
fields: {
|
|
352
|
+
chain: chain,
|
|
353
|
+
symbol: symbol,
|
|
354
|
+
destination: destination,
|
|
355
|
+
limit: limit,
|
|
356
|
+
stream: stream,
|
|
357
|
+
affiliateTag: affiliateTag,
|
|
358
|
+
affiliateBP: affiliateBP,
|
|
359
|
+
},
|
|
360
|
+
suggestedFix: shorthandFix ? normalized : undefined,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// Validate a THORChain memo - returns true if valid, false otherwise
|
|
364
|
+
function validateMemo(memo) {
|
|
365
|
+
try {
|
|
366
|
+
if (!memo || memo.trim() === '')
|
|
367
|
+
return false;
|
|
368
|
+
var trimmed = memo.trim();
|
|
369
|
+
// Check basic format
|
|
370
|
+
if (!trimmed.startsWith('=:') && !trimmed.startsWith('SWAP:') &&
|
|
371
|
+
!trimmed.startsWith('DEPOSIT:') && !trimmed.startsWith('WITHDRAW:') &&
|
|
372
|
+
!trimmed.startsWith('LOAN+:') && !trimmed.startsWith('LOAN-:') &&
|
|
373
|
+
!trimmed.startsWith('ADD:') && !trimmed.startsWith('WD:') &&
|
|
374
|
+
!trimmed.startsWith('BOND:') && !trimmed.startsWith('UNBOND:') &&
|
|
375
|
+
!trimmed.startsWith('MIGRATE:') && !trimmed.startsWith('*NOOP*:')) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
// For swap memos, validate format more strictly
|
|
379
|
+
if (trimmed.startsWith('=:') || trimmed.startsWith('SWAP:')) {
|
|
380
|
+
var prefix = trimmed.startsWith('=:') ? '=:' : 'SWAP:';
|
|
381
|
+
var parts = trimmed.slice(prefix.length).split(':');
|
|
382
|
+
// Must have at least asset and destination
|
|
383
|
+
if (parts.length < 2)
|
|
384
|
+
return false;
|
|
385
|
+
// Check asset format (must be CHAIN.SYMBOL)
|
|
386
|
+
var asset = parts[0];
|
|
387
|
+
// Don't allow single-letter shorthands in validation
|
|
388
|
+
if (asset.length === 1)
|
|
389
|
+
return false;
|
|
390
|
+
var assetParts = asset.split('.');
|
|
391
|
+
if (assetParts.length !== 2)
|
|
392
|
+
return false;
|
|
393
|
+
var chain = assetParts[0], symbol = assetParts[1];
|
|
394
|
+
// Must be uppercase alphanumeric (symbol can have dash)
|
|
395
|
+
if (!/^[A-Z0-9]+$/.test(chain))
|
|
396
|
+
return false;
|
|
397
|
+
if (!/^[A-Z0-9\-]+$/.test(symbol))
|
|
398
|
+
return false;
|
|
399
|
+
// Must have destination
|
|
400
|
+
var destination = parts[1];
|
|
401
|
+
if (!destination || destination.trim() === '')
|
|
402
|
+
return false;
|
|
403
|
+
// Check optional limit/stream format
|
|
404
|
+
if (parts[2] && parts[2] !== '') {
|
|
405
|
+
var limitStream = parts[2].split('/');
|
|
406
|
+
// Limit must be integer if present
|
|
407
|
+
if (limitStream[0] && !/^\d+$/.test(limitStream[0]))
|
|
408
|
+
return false;
|
|
409
|
+
// Stream must be integer if present
|
|
410
|
+
if (limitStream[1] && !/^\d+$/.test(limitStream[1]))
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
// Check affiliate BP if present
|
|
414
|
+
if (parts[4] && parts[4] !== '') {
|
|
415
|
+
if (!/^\d+$/.test(parts[4]))
|
|
416
|
+
return false;
|
|
417
|
+
var bp = parseInt(parts[4]);
|
|
418
|
+
if (bp < 0 || bp > 1000)
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Check memo length
|
|
423
|
+
if (Buffer.from(trimmed, 'utf-8').length > 250)
|
|
424
|
+
return false;
|
|
425
|
+
// If we made it here, memo is valid
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
// Any error means invalid memo
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
202
433
|
// Normalize and validate swap memo
|
|
203
434
|
function normalizeSwapMemo(memo) {
|
|
204
435
|
// Ensure "=:" prefix and fully qualified asset
|
|
@@ -379,3 +610,31 @@ function parseMemo(memo) {
|
|
|
379
610
|
throw new Error("Unsupported memo type: ".concat(type));
|
|
380
611
|
}
|
|
381
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* Thorchain Swap Memo Validator
|
|
615
|
+
*
|
|
616
|
+
* Supports memos of the form:
|
|
617
|
+
* =:<CHAIN>.<SYMBOL>:<DEST>[:<limit>[/<stream]][:<affiliateTag>:<affiliateBP>]
|
|
618
|
+
*
|
|
619
|
+
* Examples:
|
|
620
|
+
* =:BTC.BTC:bc1qxyz...
|
|
621
|
+
* =:BTC.BTC:bc1qxyz...:120000
|
|
622
|
+
* =:BTC.BTC:1JNYtQ...:120000/30
|
|
623
|
+
* =:ETH.ETH:0xabc...:0:agg:25
|
|
624
|
+
*/
|
|
625
|
+
var SHORTHANDS = {
|
|
626
|
+
'b': 'BTC.BTC',
|
|
627
|
+
'btc': 'BTC.BTC',
|
|
628
|
+
'e': 'ETH.ETH',
|
|
629
|
+
'eth': 'ETH.ETH',
|
|
630
|
+
'r': 'THOR.RUNE',
|
|
631
|
+
'd': 'DOGE.DOGE',
|
|
632
|
+
'l': 'LTC.LTC',
|
|
633
|
+
'c': 'BCH.BCH',
|
|
634
|
+
'a': 'AVAX.AVAX',
|
|
635
|
+
's': 'BSC.BNB',
|
|
636
|
+
'n': 'BNB.BNB',
|
|
637
|
+
'g': 'GAIA.ATOM',
|
|
638
|
+
};
|
|
639
|
+
var MAX_MEMO_LEN = 250;
|
|
640
|
+
var ASCII_SAFE = /^[\x20-\x7E]+$/; // printable ASCII (no control chars)
|