@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 +72 -0
- package/dist/cross-chain.d.ts +60 -0
- package/dist/cross-chain.d.ts.map +1 -0
- package/dist/cross-chain.js +56 -0
- package/dist/eip712-signature.d.ts +9 -0
- package/dist/eip712-signature.d.ts.map +1 -0
- package/dist/eip712-signature.js +22 -0
- package/dist/evm-error.d.ts +14 -0
- package/dist/evm-error.d.ts.map +1 -0
- package/dist/evm-error.js +92 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/payout-order-utils.d.ts +34 -0
- package/dist/payout-order-utils.d.ts.map +1 -0
- package/dist/payout-order-utils.js +98 -0
- package/dist/tron.d.ts +50 -0
- package/dist/tron.d.ts.map +1 -0
- package/dist/tron.js +255 -0
- package/dist/tronweb-manager.d.ts +16 -0
- package/dist/tronweb-manager.d.ts.map +1 -0
- package/dist/tronweb-manager.js +44 -0
- package/dist/types/merchant.d.ts +14 -0
- package/dist/types/merchant.d.ts.map +1 -0
- package/dist/types/merchant.js +1 -0
- package/package.json +54 -0
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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,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
|
+
}
|