@pioneer-platform/pioneer-coins 9.3.1 → 9.4.0
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 +12 -0
- package/lib/thorchain.d.ts +26 -0
- package/lib/thorchain.js +189 -1
- package/package.json +11 -11
package/CHANGELOG.md
ADDED
package/lib/thorchain.d.ts
CHANGED
|
@@ -3,6 +3,32 @@ 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;
|
|
6
32
|
export declare function validateMemo(memo: string): boolean;
|
|
7
33
|
export declare function normalizeSwapMemo(memo: string): string;
|
|
8
34
|
export declare function createMemo(tx: BaseTx): string;
|
package/lib/thorchain.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
----------------------
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.validateThorchainSwapMemo = validateThorchainSwapMemo;
|
|
7
8
|
exports.validateMemo = validateMemo;
|
|
8
9
|
exports.normalizeSwapMemo = normalizeSwapMemo;
|
|
9
10
|
exports.createMemo = createMemo;
|
|
@@ -199,7 +200,166 @@ function buildNoOpMemo(tx) {
|
|
|
199
200
|
];
|
|
200
201
|
return truncateMemo(parts.filter(function (part) { return part; }).join(':'));
|
|
201
202
|
}
|
|
202
|
-
//
|
|
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
|
+
}
|
|
203
363
|
// Validate a THORChain memo - returns true if valid, false otherwise
|
|
204
364
|
function validateMemo(memo) {
|
|
205
365
|
try {
|
|
@@ -450,3 +610,31 @@ function parseMemo(memo) {
|
|
|
450
610
|
throw new Error("Unsupported memo type: ".concat(type));
|
|
451
611
|
}
|
|
452
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)
|
package/package.json
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pioneer-platform/pioneer-coins",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.4.0",
|
|
4
4
|
"main": "./lib/index.js",
|
|
5
5
|
"types": "./lib/index.d.ts",
|
|
6
6
|
"_moduleAliases": {
|
|
7
7
|
"@coins": "lib/coins"
|
|
8
8
|
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"npm": "pnpm i",
|
|
11
|
-
"build": "tsc -p .",
|
|
12
|
-
"test": "pnpm run build && node __tests__/test-module.js",
|
|
13
|
-
"test-suite": "pnpm run build && node __tests__/tests-common.js",
|
|
14
|
-
"build:watch": "pnpm run build && onchange 'src/**/*.ts' -- pnpm run build",
|
|
15
|
-
"prepublish": "pnpm run build"
|
|
16
|
-
},
|
|
17
9
|
"dependencies": {
|
|
18
|
-
"@pioneer-platform/loggerdog": "^8.
|
|
10
|
+
"@pioneer-platform/loggerdog": "^8.4.0",
|
|
19
11
|
"bignumber.js": "^9.0.1",
|
|
20
12
|
"bs58check": "^3.0.1"
|
|
21
13
|
},
|
|
@@ -25,5 +17,13 @@
|
|
|
25
17
|
"nodemon": "^2.0.3",
|
|
26
18
|
"typescript": "^5.0.4"
|
|
27
19
|
},
|
|
28
|
-
"gitHead": "a76012f6693a12181c4744e53e977a9eaeef0ed3"
|
|
20
|
+
"gitHead": "a76012f6693a12181c4744e53e977a9eaeef0ed3",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"npm": "pnpm i",
|
|
23
|
+
"build": "tsc -p .",
|
|
24
|
+
"test": "pnpm run build && node __tests__/test-module.js",
|
|
25
|
+
"test-suite": "pnpm run build && node __tests__/tests-common.js",
|
|
26
|
+
"build:watch": "pnpm run build && onchange 'src/**/*.ts' -- pnpm run build",
|
|
27
|
+
"prepublish": "pnpm run build"
|
|
28
|
+
}
|
|
29
29
|
}
|