@pioneer-platform/pioneer-coins 9.3.1 → 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.
@@ -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
- // Main function to create a memo from any transaction type
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@pioneer-platform/pioneer-coins",
3
- "version": "9.3.1",
3
+ "version": "9.3.2",
4
4
  "main": "./lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
6
  "_moduleAliases": {