@magnaflow/merchant-utils 1.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 ADDED
@@ -0,0 +1,72 @@
1
+ # @magnaflow/merchant-utils
2
+
3
+ Shared merchant-facing helpers on top of the core chain and Tron tooling.
4
+
5
+ ## What It Provides
6
+
7
+ - Cross-chain transaction waiting and friendly error helpers
8
+ - Tron transaction polling helpers
9
+ - TronWeb instance creation from merchant chain config
10
+ - EVM selector-based error formatting
11
+ - Payout order display helpers
12
+ - EIP-712 digest signing utility
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @magnaflow/merchant-utils
18
+ ```
19
+
20
+ ## Key Exports
21
+
22
+ - `waitForTransactionCrossChain`
23
+ - `createCrossChainFriendlyErrorMessage`
24
+ - `pollTronTransaction`
25
+ - `pollTronTransaction2`
26
+ - `pollTronTransactionWithBackoff`
27
+ - `createTronWebInstance`
28
+ - `parseEvmError`
29
+ - `createFriendlyErrorMessage`
30
+ - `isTronLikeChain`
31
+ - `isBscLikeChain`
32
+ - `signDigest`
33
+ - `formatPayoutOrderTime`
34
+ - `getPayoutTypeColor`
35
+ - `getPayoutStatusColor`
36
+ - `ORDER_TITLES`
37
+ - `TYPE_DISPLAY_NAMES`
38
+
39
+ ## Example
40
+
41
+ ```ts
42
+ import {
43
+ createCrossChainFriendlyErrorMessage,
44
+ waitForTransactionCrossChain,
45
+ } from '@magnaflow/merchant-utils';
46
+
47
+ const result = await waitForTransactionCrossChain({
48
+ chainType: 'evm',
49
+ hash: '0x1234...',
50
+ client: publicClient,
51
+ confirmations: 1,
52
+ });
53
+
54
+ const message = createCrossChainFriendlyErrorMessage('evm', new Error('Operation failed'));
55
+ ```
56
+
57
+ ## Notes
58
+
59
+ - This package is intentionally a helper layer. Full merchant workflows should still live in application services.
60
+ - `@magnaflow/tronsave` remains a separate package for Tron energy rental APIs.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ bunx nx run merchant-utils:lint
66
+ bunx nx run merchant-utils:test
67
+ bunx nx run merchant-utils:build
68
+ ```
69
+
70
+ ## License
71
+
72
+ MIT
@@ -0,0 +1,60 @@
1
+ import type { ChainType } from '@magnaflow/chain-provider';
2
+ import type { TronWeb } from 'tronweb';
3
+ /**
4
+ * Interface layer: minimal shape that any transaction waiting strategy
5
+ * (EVM / Tron / future chains) must satisfy.
6
+ */
7
+ export interface CrossChainWaitResult {
8
+ hash: string;
9
+ status: 'success' | 'failed';
10
+ rawReceipt: unknown;
11
+ }
12
+ /**
13
+ * EVM wait strategy input (interface layer).
14
+ */
15
+ export interface EvmWaitConfig {
16
+ chainType: 'evm';
17
+ hash: string;
18
+ client: {
19
+ waitForTransactionReceipt: (args: {
20
+ hash: `0x${string}`;
21
+ timeout?: number;
22
+ confirmations?: number;
23
+ }) => Promise<{
24
+ transactionHash: `0x${string}`;
25
+ status: 'success' | 'reverted' | 'pending';
26
+ blockNumber?: bigint;
27
+ blockHash?: `0x${string}`;
28
+ gasUsed?: bigint;
29
+ effectiveGasPrice?: bigint;
30
+ }>;
31
+ };
32
+ timeoutMs?: number;
33
+ confirmations?: number;
34
+ }
35
+ /**
36
+ * Tron wait strategy input (interface layer).
37
+ */
38
+ export interface TronWaitConfig {
39
+ chainType: 'tron';
40
+ hash: string;
41
+ tronWeb: TronWeb;
42
+ timeoutMs?: number;
43
+ }
44
+ export type WaitForTransactionCrossChainParams = EvmWaitConfig | TronWaitConfig;
45
+ /**
46
+ * Implementation layer: default cross-chain transaction polling helper
47
+ * that implements the interface defined by WaitForTransactionCrossChainParams.
48
+ *
49
+ * - EVM: delegates to viem-compatible waitForTransactionReceipt.
50
+ * - Tron: delegates to pollTronTransactionWithBackoff.
51
+ */
52
+ export declare function waitForTransactionCrossChain(params: WaitForTransactionCrossChainParams): Promise<CrossChainWaitResult>;
53
+ /**
54
+ * Create a cross-chain friendly error message for UI.
55
+ *
56
+ * - EVM: use EVM selector-based parsing.
57
+ * - Tron: try to extract contractResult/receipt info if present, otherwise fallback to message.
58
+ */
59
+ export declare function createCrossChainFriendlyErrorMessage(chainType: ChainType, error: unknown, defaultMessage?: string): string;
60
+ //# sourceMappingURL=cross-chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-chain.d.ts","sourceRoot":"","sources":["../src/cross-chain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAG3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,KAAK,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QACN,yBAAyB,EAAE,CAAC,IAAI,EAAE;YAChC,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,aAAa,CAAC,EAAE,MAAM,CAAC;SACxB,KAAK,OAAO,CAAC;YACZ,eAAe,EAAE,KAAK,MAAM,EAAE,CAAC;YAC/B,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;YAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,SAAS,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;YAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;SAC5B,CAAC,CAAC;KACJ,CAAC;IACF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,kCAAkC,GAAG,aAAa,GAAG,cAAc,CAAC;AAEhF;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,kCAAkC,GACzC,OAAO,CAAC,oBAAoB,CAAC,CA4B/B;AAED;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,OAAO,EACd,cAAc,GAAE,MAA2B,GAC1C,MAAM,CAmBR"}
@@ -0,0 +1,56 @@
1
+ import { createFriendlyErrorMessage as createEvmFriendlyErrorMessage } from './evm-error';
2
+ import { pollTronTransactionWithBackoff } from './tron';
3
+ /**
4
+ * Implementation layer: default cross-chain transaction polling helper
5
+ * that implements the interface defined by WaitForTransactionCrossChainParams.
6
+ *
7
+ * - EVM: delegates to viem-compatible waitForTransactionReceipt.
8
+ * - Tron: delegates to pollTronTransactionWithBackoff.
9
+ */
10
+ export async function waitForTransactionCrossChain(params) {
11
+ if (params.chainType === 'evm') {
12
+ const { client, hash, timeoutMs, confirmations } = params;
13
+ const receipt = await client.waitForTransactionReceipt({
14
+ hash: hash,
15
+ timeout: timeoutMs,
16
+ confirmations,
17
+ });
18
+ return {
19
+ hash: receipt.transactionHash,
20
+ status: receipt.status === 'success' ? 'success' : 'failed',
21
+ rawReceipt: receipt,
22
+ };
23
+ }
24
+ const { tronWeb, hash, timeoutMs } = params;
25
+ const receipt = await pollTronTransactionWithBackoff(tronWeb, hash, {
26
+ timeout: timeoutMs,
27
+ });
28
+ const status = receipt?.receipt?.result === 'SUCCESS' && receipt.blockNumber ? 'success' : 'failed';
29
+ return {
30
+ hash,
31
+ status,
32
+ rawReceipt: receipt,
33
+ };
34
+ }
35
+ /**
36
+ * Create a cross-chain friendly error message for UI.
37
+ *
38
+ * - EVM: use EVM selector-based parsing.
39
+ * - Tron: try to extract contractResult/receipt info if present, otherwise fallback to message.
40
+ */
41
+ export function createCrossChainFriendlyErrorMessage(chainType, error, defaultMessage = 'Operation failed') {
42
+ if (chainType === 'evm') {
43
+ return createEvmFriendlyErrorMessage(error, defaultMessage);
44
+ }
45
+ const err = error;
46
+ // Try to use Tron-style receipt/contractResult information when available
47
+ if (err && err.receipt && Array.isArray(err.contractResult)) {
48
+ const contractRet = err.receipt;
49
+ const base = contractRet.result ? String(contractRet.result) : defaultMessage;
50
+ return `${base}`;
51
+ }
52
+ if (err && typeof err.message === 'string') {
53
+ return err.message;
54
+ }
55
+ return defaultMessage;
56
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Sign a digest (bytes32)
3
+ *
4
+ * @param digest 0x-prefixed bytes32
5
+ * @param privateKey 0x-prefixed private key
6
+ * @returns 65-byte signature hex (r + s + v)
7
+ */
8
+ export declare function signDigest(digest: string, privateKey: string): Promise<`0x${string}`>;
9
+ //# sourceMappingURL=eip712-signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip712-signature.d.ts","sourceRoot":"","sources":["../src/eip712-signature.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC,CAc3F"}
@@ -0,0 +1,22 @@
1
+ import { sign } from 'viem/accounts';
2
+ import { bytesToHex, concatHex } from 'viem';
3
+ /**
4
+ * Sign a digest (bytes32)
5
+ *
6
+ * @param digest 0x-prefixed bytes32
7
+ * @param privateKey 0x-prefixed private key
8
+ * @returns 65-byte signature hex (r + s + v)
9
+ */
10
+ export async function signDigest(digest, privateKey) {
11
+ const digestHex = digest.startsWith('0x') ? digest : `0x${digest}`;
12
+ const pk = privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`;
13
+ const signature = await sign({
14
+ hash: digestHex,
15
+ privateKey: pk,
16
+ });
17
+ let v = Number(signature.v ?? 0);
18
+ if (v < 27) {
19
+ v = v + 27;
20
+ }
21
+ return concatHex([signature.r, signature.s, bytesToHex(Uint8Array.of(v))]);
22
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Parse error information from ethers error
3
+ * @param error ethers error object
4
+ * @returns Parsed error message
5
+ */
6
+ export declare function parseEvmError(error: any): string;
7
+ /**
8
+ * Create a friendly error message
9
+ * @param error Original error
10
+ * @param defaultMessage Default error message
11
+ * @returns Friendly error message
12
+ */
13
+ export declare function createFriendlyErrorMessage(error: any, defaultMessage?: string): string;
14
+ //# sourceMappingURL=evm-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evm-error.d.ts","sourceRoot":"","sources":["../src/evm-error.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CAwDhD;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,GAAG,EACV,cAAc,GAAE,MAA2B,GAC1C,MAAM,CAUR"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * EVM Contract Error Selector Mapping
3
+ * Error selector = first 4 bytes of keccak256("ErrorName()")
4
+ */
5
+ const ERROR_SELECTORS = {
6
+ '0x7db491eb': 'InvalidInputLength - Input length mismatch (array length inconsistent or empty)',
7
+ '0x9c358fad': 'FundTransferFailed - Transfer failed (insufficient balance or allowance)',
8
+ '0xf4d678b8': 'InsufficientBalance - Insufficient balance',
9
+ '0x599989d2': 'InsufficientMerchantBalance - Insufficient merchant balance (cannot pay fee)',
10
+ '0x4b692736': 'InvalidEd25519PublicKey - Invalid Ed25519 public key',
11
+ '0x3a81d6fc': 'AlreadyRegistered - Already registered',
12
+ '0x8baa579f': 'InvalidSignature - Signature verification failed',
13
+ '0x56d69198': 'InvalidFeeRate - Invalid fee rate',
14
+ '0x2c5211c6': 'InvalidAmount - Invalid amount',
15
+ '0xe6f2de87': 'InvalidStableCoin - Not a stablecoin',
16
+ '0x9c8d2cd2': 'InvalidRecipient - Invalid recipient address',
17
+ '0xb89fe006': 'InvalidWhitelist - Not in whitelist',
18
+ '0xc0b6c919': 'InvalidMerchant - Invalid merchant address',
19
+ };
20
+ /**
21
+ * Parse error information from ethers error
22
+ * @param error ethers error object
23
+ * @returns Parsed error message
24
+ */
25
+ export function parseEvmError(error) {
26
+ if (!error) {
27
+ return 'Unknown error';
28
+ }
29
+ // Try to extract error selector from error.data
30
+ let errorSelector = null;
31
+ // ethers v6 error format: error.data may be a string
32
+ if (error.data) {
33
+ if (typeof error.data === 'string') {
34
+ errorSelector = error.data;
35
+ }
36
+ else if (error.data.error?.data) {
37
+ // In some cases error data is in error.data.error.data
38
+ errorSelector = error.data.error.data;
39
+ }
40
+ else if (error.data.originalError?.data) {
41
+ // In some cases error data is in error.data.originalError.data
42
+ errorSelector = error.data.originalError.data;
43
+ }
44
+ }
45
+ // If error.data is a string, extract error selector (first 4 bytes = 10 characters)
46
+ if (errorSelector) {
47
+ // Ensure 0x prefix exists
48
+ let selector;
49
+ if (errorSelector.startsWith('0x')) {
50
+ // If length >= 10, take first 10 characters; otherwise use full string
51
+ selector = errorSelector.length >= 10 ? errorSelector.slice(0, 10) : errorSelector;
52
+ }
53
+ else {
54
+ // If no 0x prefix, add it and take first 8 characters
55
+ selector = `0x${errorSelector.slice(0, 8)}`;
56
+ }
57
+ if (ERROR_SELECTORS[selector]) {
58
+ return ERROR_SELECTORS[selector];
59
+ }
60
+ }
61
+ // Try to extract error selector from error message
62
+ if (error.message) {
63
+ // Look for pattern like "0x..." (8 hex characters)
64
+ const match = error.message.match(/0x[a-fA-F0-9]{8}/);
65
+ if (match) {
66
+ const selector = match[0];
67
+ if (ERROR_SELECTORS[selector]) {
68
+ return ERROR_SELECTORS[selector];
69
+ }
70
+ }
71
+ }
72
+ // If cannot parse, return original error message or default message
73
+ if (error.message) {
74
+ return error.message;
75
+ }
76
+ return 'Unknown error';
77
+ }
78
+ /**
79
+ * Create a friendly error message
80
+ * @param error Original error
81
+ * @param defaultMessage Default error message
82
+ * @returns Friendly error message
83
+ */
84
+ export function createFriendlyErrorMessage(error, defaultMessage = 'Operation failed') {
85
+ const parsedError = parseEvmError(error);
86
+ // If parsed error differs from default message, use parsed error
87
+ if (parsedError !== 'Unknown error' && parsedError !== error.message) {
88
+ return `${defaultMessage}: ${parsedError}`;
89
+ }
90
+ // Otherwise use original error message or default message
91
+ return error?.message || defaultMessage;
92
+ }
@@ -0,0 +1,7 @@
1
+ export * from './eip712-signature';
2
+ export * from './tron';
3
+ export * from './tronweb-manager';
4
+ export * from './evm-error';
5
+ export * from './payout-order-utils';
6
+ export * from './cross-chain';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './eip712-signature';
2
+ export * from './tron';
3
+ export * from './tronweb-manager';
4
+ export * from './evm-error';
5
+ export * from './payout-order-utils';
6
+ export * from './cross-chain';
@@ -0,0 +1,34 @@
1
+ import type { PayoutOrderType } from '@/types/merchant';
2
+ /**
3
+ * Check if the chain is Tron-like
4
+ */
5
+ export declare const isTronLikeChain: (chain?: string) => boolean;
6
+ /**
7
+ * Check if the chain is BSC-like
8
+ */
9
+ export declare const isBscLikeChain: (chain?: string) => boolean;
10
+ /**
11
+ * Format order creation time
12
+ */
13
+ export declare const formatPayoutOrderTime: (timestamp: string | number) => string;
14
+ /**
15
+ * Chip color type (consistent with UI component library literal values)
16
+ */
17
+ export type PayoutChipColor = 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
18
+ /**
19
+ * Get Chip color for display based on order type
20
+ */
21
+ export declare const getPayoutTypeColor: (type: string) => PayoutChipColor;
22
+ /**
23
+ * Get Chip color for display based on internal status
24
+ */
25
+ export declare const getPayoutStatusColor: (status: string) => PayoutChipColor;
26
+ /**
27
+ * Order title mapping
28
+ */
29
+ export declare const ORDER_TITLES: Record<PayoutOrderType, string>;
30
+ /**
31
+ * Order type display name mapping
32
+ */
33
+ export declare const TYPE_DISPLAY_NAMES: Record<PayoutOrderType, string>;
34
+ //# sourceMappingURL=payout-order-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payout-order-utils.d.ts","sourceRoot":"","sources":["../src/payout-order-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAcxD;;GAEG;AACH,eAAO,MAAM,eAAe,WATjB,MAAM,KAAG,OASoD,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,cAAc,WAdhB,MAAM,KAAG,OAc2C,CAAC;AAMhE;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,MAAM,WAE/D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,SAAS,GACT,WAAW,GACX,SAAS,GACT,SAAS,GACT,QAAQ,CAAC;AAEb;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,eA8BjD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,KAAG,eAWrD,CAAC;AAMF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAIxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAI9D,CAAC"}
@@ -0,0 +1,98 @@
1
+ // ------------------------------
2
+ // Chain helpers
3
+ // ------------------------------
4
+ const createChainPrefixChecker = (prefixes) => (chain) => {
5
+ if (!chain)
6
+ return false;
7
+ const lower = chain.toLowerCase();
8
+ return prefixes.some((p) => lower.startsWith(p));
9
+ };
10
+ /**
11
+ * Check if the chain is Tron-like
12
+ */
13
+ export const isTronLikeChain = createChainPrefixChecker(['tron', 'trx']);
14
+ /**
15
+ * Check if the chain is BSC-like
16
+ */
17
+ export const isBscLikeChain = createChainPrefixChecker(['bsc']);
18
+ // ------------------------------
19
+ // Payout Order formatting and display utility functions
20
+ // ------------------------------
21
+ /**
22
+ * Format order creation time
23
+ */
24
+ export const formatPayoutOrderTime = (timestamp) => {
25
+ return new Date(timestamp).toLocaleString();
26
+ };
27
+ /**
28
+ * Get Chip color for display based on order type
29
+ */
30
+ export const getPayoutTypeColor = (type) => {
31
+ const map = {
32
+ payout: 'primary',
33
+ collection: 'secondary',
34
+ recharge: 'danger',
35
+ confirmed: 'success',
36
+ pending: 'warning',
37
+ };
38
+ if (type in map) {
39
+ return map[type];
40
+ }
41
+ else {
42
+ const lowerType = type.toLowerCase();
43
+ if (lowerType.includes('danger') ||
44
+ lowerType.includes('error') ||
45
+ lowerType.includes('failed')) {
46
+ return 'danger';
47
+ }
48
+ else if (lowerType.includes('warning')) {
49
+ return 'warning';
50
+ }
51
+ else if (lowerType.includes('success')) {
52
+ return 'success';
53
+ }
54
+ else if (lowerType.includes('primary')) {
55
+ return 'primary';
56
+ }
57
+ else if (lowerType.includes('secondary')) {
58
+ return 'secondary';
59
+ }
60
+ else {
61
+ return 'primary';
62
+ }
63
+ }
64
+ };
65
+ /**
66
+ * Get Chip color for display based on internal status
67
+ */
68
+ export const getPayoutStatusColor = (status) => {
69
+ const map = {
70
+ success: 'success',
71
+ failed: 'danger',
72
+ processing: 'warning',
73
+ pending: 'warning',
74
+ confirmed: 'success',
75
+ rejected: 'danger',
76
+ urgent: 'danger',
77
+ };
78
+ return map[status] ?? 'default';
79
+ };
80
+ // ------------------------------
81
+ // Payout Order constants definition
82
+ // ------------------------------
83
+ /**
84
+ * Order title mapping
85
+ */
86
+ export const ORDER_TITLES = {
87
+ payout: 'Payout Order',
88
+ collection: 'Collection Order',
89
+ recharge: 'Recharge Order',
90
+ };
91
+ /**
92
+ * Order type display name mapping
93
+ */
94
+ export const TYPE_DISPLAY_NAMES = {
95
+ payout: 'Regular',
96
+ collection: 'Collection',
97
+ recharge: 'Recharge',
98
+ };
package/dist/tron.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tron transaction receipt interface
3
+ */
4
+ export interface TronTransactionReceipt {
5
+ receipt?: {
6
+ result?: string;
7
+ energy_usage_total?: number;
8
+ net_usage?: number;
9
+ [key: string]: any;
10
+ };
11
+ contractResult?: string[];
12
+ log?: any[];
13
+ [key: string]: any;
14
+ }
15
+ /**
16
+ * Configuration options for polling Tron transaction status
17
+ */
18
+ export interface PollTransactionOptions {
19
+ /** Maximum retry count, default 40 */
20
+ maxRetries?: number;
21
+ /** Retry delay (milliseconds), default 4000 */
22
+ retryDelay?: number;
23
+ /** Whether to throw an error on transaction failure, default true */
24
+ throwOnFailure?: boolean;
25
+ /** Custom timeout error message */
26
+ timeoutMessage?: string;
27
+ /** Custom failure error message */
28
+ failureMessage?: (txHash: string) => string;
29
+ }
30
+ /**
31
+ * Poll Tron transaction status, wait for transaction confirmation
32
+ * @param tronWeb TronWeb instance
33
+ * @param txHash Transaction hash
34
+ * @param options Poll configuration options
35
+ * @returns Transaction receipt
36
+ * @throws If transaction times out or fails (according to configuration)
37
+ */
38
+ export declare function pollTronTransaction(tronWeb: any, txHash: string, options?: PollTransactionOptions): Promise<TronTransactionReceipt>;
39
+ export declare function pollTronTransaction2(tronWeb: any, txHash: string, options?: PollTransactionOptions): Promise<TronTransactionReceipt>;
40
+ export declare function pollTronTransactionWithBackoff(tronWeb: any, txHash: string, options?: {
41
+ baseDelay?: number;
42
+ maxDelay?: number;
43
+ timeout?: number;
44
+ signal?: AbortSignal;
45
+ /** Custom timeout error message */
46
+ timeoutMessage?: string;
47
+ /** Custom failure error message */
48
+ failureMessage?: (txHash: string) => string;
49
+ }): Promise<any>;
50
+ //# sourceMappingURL=tron.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tron.d.ts","sourceRoot":"","sources":["../src/tron.ts"],"names":[],"mappings":"AAkDA;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7C;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,GAAG,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAkFjC;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,GAAG,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CA0HjC;AAED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,GAAG,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC,gBAmDP"}
package/dist/tron.js ADDED
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Contract error selector mapping
3
+ * Error selector = first 4 bytes of keccak256("ErrorName()")
4
+ */
5
+ const ERROR_SELECTORS = {
6
+ '0x7db491eb': 'InvalidInputLength - Input length mismatch (array length mismatch or empty)',
7
+ '0x9c358fad': 'FundTransferFailed - Transfer failed (insufficient balance or insufficient authorization)',
8
+ '0xf4d678b8': 'InsufficientBalance - Insufficient balance',
9
+ '0x599989d2': 'InsufficientMerchantBalance - Insufficient merchant balance (cannot pay fee)',
10
+ '0x4b692736': 'InvalidEd25519PublicKey - Invalid Ed25519 public key',
11
+ '0x3a81d6fc': 'AlreadyRegistered - Already registered',
12
+ '0x8baa579f': 'InvalidSignature - Signature verification failed',
13
+ '0x56d69198': 'InvalidFeeRate - Invalid fee rate',
14
+ '0x2c5211c6': 'InvalidAmount - Invalid amount',
15
+ '0xe6f2de87': 'InvalidStableCoin - Not a stablecoin',
16
+ '0x9c8d2cd2': 'InvalidRecipient - Invalid recipient address',
17
+ '0xb89fe006': 'InvalidWhitelist - Not in whitelist',
18
+ '0xc0b6c919': 'InvalidMerchant - Invalid merchant address',
19
+ };
20
+ /**
21
+ * Parse error information from contract result
22
+ * @param contractResult The result array returned by the contract
23
+ * @returns Parsed error information
24
+ */
25
+ function parseContractError(contractResult) {
26
+ if (!contractResult || contractResult.length === 0) {
27
+ return 'Unknown error';
28
+ }
29
+ // Contract errors typically return an error selector (4 bytes, 8 hex characters)
30
+ const errorSelector = Array.isArray(contractResult) ? contractResult[0] : contractResult;
31
+ if (typeof errorSelector === 'string') {
32
+ // Ensure 0x prefix exists
33
+ const selector = errorSelector.startsWith('0x') ? errorSelector : `0x${errorSelector}`;
34
+ // Check if it's a known error selector
35
+ if (ERROR_SELECTORS[selector]) {
36
+ return ERROR_SELECTORS[selector];
37
+ }
38
+ // If not a known error, return the raw selector
39
+ return `Unknown error: ${selector}`;
40
+ }
41
+ return 'Unable to parse error information';
42
+ }
43
+ /**
44
+ * Poll Tron transaction status, wait for transaction confirmation
45
+ * @param tronWeb TronWeb instance
46
+ * @param txHash Transaction hash
47
+ * @param options Poll configuration options
48
+ * @returns Transaction receipt
49
+ * @throws If transaction times out or fails (according to configuration)
50
+ */
51
+ export async function pollTronTransaction(tronWeb, txHash, options = {}) {
52
+ if (!txHash) {
53
+ throw new Error('Transaction hash is empty, cannot confirm transaction status');
54
+ }
55
+ const { maxRetries = 50, retryDelay = 3000, throwOnFailure = true, timeoutMessage = 'Transaction confirmation timed out, please check transaction status', failureMessage = (hash) => `Transaction failed, transaction hash: ${hash}`, } = options;
56
+ let transactionReceipt = null;
57
+ let retryCount = 0;
58
+ while (retryCount < maxRetries) {
59
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
60
+ try {
61
+ const txInfo = await tronWeb.trx.getTransactionInfo(txHash);
62
+ if (txInfo?.blockNumber) {
63
+ transactionReceipt = txInfo;
64
+ break;
65
+ }
66
+ }
67
+ catch {
68
+ // Silently continue retry on error
69
+ }
70
+ retryCount++;
71
+ }
72
+ if (!transactionReceipt?.blockNumber) {
73
+ throw new Error(timeoutMessage);
74
+ }
75
+ const contractRet = transactionReceipt?.receipt;
76
+ const isSuccess = Boolean(contractRet && transactionReceipt?.blockNumber);
77
+ if (throwOnFailure && !isSuccess) {
78
+ let errorDetails = '';
79
+ let parsedError = '';
80
+ try {
81
+ if (contractRet) {
82
+ errorDetails = `Transaction status: ${contractRet}`;
83
+ }
84
+ // Check if there is contract error information
85
+ if (transactionReceipt?.contractResult && Array.isArray(transactionReceipt.contractResult)) {
86
+ const contractResult = transactionReceipt.contractResult;
87
+ errorDetails += `\nContract result: ${JSON.stringify(contractResult)}`;
88
+ // Try to parse error selector
89
+ parsedError = parseContractError(contractResult);
90
+ }
91
+ // Check if there is log (may contain error information)
92
+ if (transactionReceipt?.log && transactionReceipt.log.length > 0) {
93
+ errorDetails += `\nLog: ${JSON.stringify(transactionReceipt.log)}`;
94
+ }
95
+ // Add resource usage information
96
+ if (transactionReceipt?.receipt?.energy_usage_total) {
97
+ errorDetails += `\nEnergy usage: ${transactionReceipt.receipt.energy_usage_total}`;
98
+ }
99
+ if (transactionReceipt?.receipt?.net_usage) {
100
+ errorDetails += `\nBandwidth usage: ${transactionReceipt.receipt.net_usage}`;
101
+ }
102
+ }
103
+ catch {
104
+ // Silently ignore error extraction failure
105
+ }
106
+ // Build error message, prioritize parsed error information
107
+ let errorMsg = failureMessage(txHash);
108
+ if (parsedError) {
109
+ errorMsg += `\nError reason: ${parsedError}`;
110
+ }
111
+ if (errorDetails) {
112
+ errorMsg += `\n${errorDetails}`;
113
+ }
114
+ throw new Error(errorMsg);
115
+ }
116
+ return transactionReceipt || {};
117
+ }
118
+ export async function pollTronTransaction2(tronWeb, txHash, options = {}) {
119
+ if (!txHash) {
120
+ throw new Error('Transaction hash is empty, cannot confirm transaction status');
121
+ }
122
+ const { maxRetries = 50, retryDelay = 3000, throwOnFailure = true, timeoutMessage = 'Transaction confirmation timed out, please check transaction status', failureMessage = (hash) => `Transaction failed, transaction hash: ${hash}`, } = options;
123
+ let transactionReceipt = null;
124
+ let retryCount = 0;
125
+ // Helper function to add timeout to getTransactionInfo call
126
+ // Returns a promise that resolves/rejects with cleanup to prevent memory leaks
127
+ const getTransactionInfoWithTimeout = async (timeoutMs = 10000) => {
128
+ let timeoutId = null;
129
+ const timeoutPromise = new Promise((_, reject) => {
130
+ timeoutId = setTimeout(() => {
131
+ timeoutId = null;
132
+ reject(new Error(`getTransactionInfo timeout after ${timeoutMs}ms`));
133
+ }, timeoutMs);
134
+ });
135
+ const infoPromise = tronWeb.trx
136
+ .getTransactionInfo(txHash)
137
+ .then((result) => {
138
+ // Clear timeout if infoPromise resolves first
139
+ if (timeoutId) {
140
+ clearTimeout(timeoutId);
141
+ timeoutId = null;
142
+ }
143
+ return result;
144
+ })
145
+ .catch((error) => {
146
+ // Clear timeout if infoPromise rejects
147
+ if (timeoutId) {
148
+ clearTimeout(timeoutId);
149
+ timeoutId = null;
150
+ }
151
+ throw error;
152
+ });
153
+ return Promise.race([infoPromise, timeoutPromise]).finally(() => {
154
+ // Ensure cleanup in case of any edge cases
155
+ if (timeoutId) {
156
+ clearTimeout(timeoutId);
157
+ timeoutId = null;
158
+ }
159
+ });
160
+ };
161
+ while (retryCount < maxRetries) {
162
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
163
+ try {
164
+ const txInfo = await getTransactionInfoWithTimeout(10000);
165
+ if (txInfo?.blockNumber) {
166
+ transactionReceipt = txInfo;
167
+ break;
168
+ }
169
+ }
170
+ catch {
171
+ // Continue retry on error
172
+ }
173
+ retryCount++;
174
+ }
175
+ if (!transactionReceipt?.blockNumber) {
176
+ throw new Error(timeoutMessage);
177
+ }
178
+ const contractRet = transactionReceipt?.receipt;
179
+ const isSuccess = Boolean(contractRet && transactionReceipt?.blockNumber);
180
+ if (throwOnFailure && !isSuccess) {
181
+ let errorDetails = '';
182
+ let parsedError = '';
183
+ try {
184
+ if (contractRet) {
185
+ errorDetails = `Transaction status: ${contractRet}`;
186
+ }
187
+ // Check if there is contract error information
188
+ if (transactionReceipt?.contractResult && Array.isArray(transactionReceipt.contractResult)) {
189
+ const contractResult = transactionReceipt.contractResult;
190
+ errorDetails += `\nContract result: ${JSON.stringify(contractResult)}`;
191
+ // Try to parse error selector
192
+ parsedError = parseContractError(contractResult);
193
+ }
194
+ // Check if there is log (may contain error information)
195
+ if (transactionReceipt?.log && transactionReceipt.log.length > 0) {
196
+ errorDetails += `\nLog: ${JSON.stringify(transactionReceipt.log)}`;
197
+ }
198
+ // Add resource usage information
199
+ if (transactionReceipt?.receipt?.energy_usage_total) {
200
+ errorDetails += `\nEnergy usage: ${transactionReceipt.receipt.energy_usage_total}`;
201
+ }
202
+ if (transactionReceipt?.receipt?.net_usage) {
203
+ errorDetails += `\nBandwidth usage: ${transactionReceipt.receipt.net_usage}`;
204
+ }
205
+ }
206
+ catch {
207
+ // Silently ignore error extraction failure
208
+ }
209
+ // Build error message, prioritize parsed error information
210
+ let errorMsg = failureMessage(txHash);
211
+ if (parsedError) {
212
+ errorMsg += `\nError reason: ${parsedError}`;
213
+ }
214
+ if (errorDetails) {
215
+ errorMsg += `\n${errorDetails}`;
216
+ }
217
+ throw new Error(errorMsg);
218
+ }
219
+ return transactionReceipt || {};
220
+ }
221
+ export async function pollTronTransactionWithBackoff(tronWeb, txHash, options = {}) {
222
+ const { baseDelay = 3000, // final stable 3s
223
+ maxDelay = 10000, // initial 10s
224
+ timeout = 150000, signal, timeoutMessage = 'Transaction confirmation timed out, please check transaction status', failureMessage = (hash) => `Transaction failed, transaction hash: ${hash}`, } = options;
225
+ const STEP = 2000; // decrease by 2s each step
226
+ let attempt = 0;
227
+ const start = Date.now();
228
+ const sleep = (ms) => new Promise((resolve, reject) => {
229
+ const id = setTimeout(resolve, ms);
230
+ signal?.addEventListener('abort', () => {
231
+ clearTimeout(id);
232
+ reject(new Error('Polling aborted'));
233
+ });
234
+ });
235
+ while (true) {
236
+ if (signal?.aborted) {
237
+ throw new Error('Polling aborted');
238
+ }
239
+ if (Date.now() - start > timeout) {
240
+ throw new Error(timeoutMessage);
241
+ }
242
+ const info = await tronWeb.trx.getTransactionInfo(txHash);
243
+ if (info?.blockNumber) {
244
+ if (info.receipt) {
245
+ return info;
246
+ }
247
+ // explicit failure (REVERT / OUT_OF_ENERGY / etc)
248
+ throw new Error(failureMessage(txHash));
249
+ }
250
+ // convergent backoff (e.g. 10 → 8 → 6 → 4 → 3)
251
+ const delay = Math.max(maxDelay - attempt * STEP, baseDelay);
252
+ attempt++;
253
+ await sleep(delay);
254
+ }
255
+ }
@@ -0,0 +1,16 @@
1
+ import { TronWeb } from 'tronweb';
2
+ import type { ChainConfig } from '@/types/merchant';
3
+ export interface CreateTronWebOptions {
4
+ privateKey?: string;
5
+ defaultAddress?: string;
6
+ fullHostOverride?: string;
7
+ headers?: Record<string, string>;
8
+ }
9
+ /**
10
+ * Create a standalone TronWeb instance.
11
+ * @param chainConfig - Chain config
12
+ * @param options - Options (privateKey, headers, etc.)
13
+ * @returns TronWeb instance
14
+ */
15
+ export declare function createTronWebInstance(chainConfig: ChainConfig, options?: CreateTronWebOptions): TronWeb;
16
+ //# sourceMappingURL=tronweb-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tronweb-manager.d.ts","sourceRoot":"","sources":["../src/tronweb-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CA0CT"}
@@ -0,0 +1,44 @@
1
+ import { TronWeb } from 'tronweb';
2
+ /**
3
+ * Create a standalone TronWeb instance.
4
+ * @param chainConfig - Chain config
5
+ * @param options - Options (privateKey, headers, etc.)
6
+ * @returns TronWeb instance
7
+ */
8
+ export function createTronWebInstance(chainConfig, options = {}) {
9
+ // 1. Normalize host URL (strip trailing slash and /jsonrpc)
10
+ let fullHost = options.fullHostOverride ?? chainConfig.rest_url;
11
+ if (typeof fullHost === 'string') {
12
+ fullHost = fullHost.replace(/\/+$/, '').replace(/\/jsonrpc$/i, '');
13
+ }
14
+ // 2. Create instance
15
+ const tronWeb = new TronWeb({
16
+ fullHost,
17
+ headers: options.headers,
18
+ });
19
+ // 3. Set private key (TronWeb will derive address from it)
20
+ if (options.privateKey) {
21
+ try {
22
+ tronWeb.setPrivateKey(options.privateKey);
23
+ }
24
+ catch (error) {
25
+ console.error('[TronWeb] Invalid private key format:', error);
26
+ }
27
+ }
28
+ // 4. Set default address: options.defaultAddress > (if no key) payout_contract
29
+ const targetAddress = options.defaultAddress
30
+ ? options.defaultAddress
31
+ : !options.privateKey
32
+ ? chainConfig.payout_contract
33
+ : undefined;
34
+ if (targetAddress && TronWeb.isAddress(targetAddress)) {
35
+ try {
36
+ // TronWeb may throw if address does not match private key
37
+ tronWeb.setAddress(targetAddress);
38
+ }
39
+ catch {
40
+ console.warn('[TronWeb] Could not set default address (may not match private key or invalid format):', targetAddress);
41
+ }
42
+ }
43
+ return tronWeb;
44
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Local types for merchant-utils (used when @/types/merchant is not provided by app)
3
+ */
4
+ export type PayoutOrderType = 'payout' | 'collection' | 'recharge';
5
+ export interface ChainConfig {
6
+ name?: string;
7
+ chain?: string;
8
+ rpc_url?: string;
9
+ rest_url?: string;
10
+ payout_contract?: string;
11
+ scan_url?: string;
12
+ [key: string]: unknown;
13
+ }
14
+ //# sourceMappingURL=merchant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merchant.d.ts","sourceRoot":"","sources":["../../src/types/merchant.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,CAAC;AAEnE,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@magnaflow/merchant-utils",
3
+ "version": "1.0.1",
4
+ "description": "Merchant shared utilities",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "test:coverage": "vitest run --coverage",
24
+ "dev": "tsc --watch",
25
+ "clean": "rm -rf dist",
26
+ "prepublishOnly": "node ../../scripts/verify-package-dist.cjs"
27
+ },
28
+ "keywords": [
29
+ "merchant",
30
+ "utils",
31
+ "tron",
32
+ "evm"
33
+ ],
34
+ "author": "",
35
+ "license": "MIT",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "dependencies": {
40
+ "@magnaflow/chain-provider": "workspace:*",
41
+ "tronweb": "^6.1.0",
42
+ "viem": "^2.39.2"
43
+ },
44
+ "devDependencies": {
45
+ "typescript": "~5.9.3",
46
+ "vitest": "^2.1.9",
47
+ "@vitest/coverage-v8": "^2.1.9"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/magnaflowlabs/merchant-v2-sdk.git",
52
+ "directory": "packages/merchant-utils"
53
+ }
54
+ }