Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/lib/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ export function filterFields(obj: any): any {

export function outputJson(data: unknown) {
const filtered = filterFields(data)
process.stdout.write(JSON.stringify(filtered) + '\n')
process.stdout.write(
JSON.stringify(filtered, (_, v) => (typeof v === 'bigint' ? v.toString() : v)) + '\n',
)
}

function classifyError(message: string, error?: unknown): string {
Expand Down
1 change: 1 addition & 0 deletions test/commands/registration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe('command registration', () => {
expect(names).toContain('farm')
expect(names).toContain('liquidity')
expect(names).toContain('contract')
expect(names).not.toContain('sunpump')
})

it('registers wallet subcommands', () => {
Expand Down
16 changes: 15 additions & 1 deletion test/lib/output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,23 @@ describe('outputJson compact format', () => {
it('outputs compact JSON without pretty-printing', () => {
outputJson({ key: 'value', nested: { a: 1 } })
expect(stdoutData).toBe('{"key":"value","nested":{"a":1}}\n')
// No spaces or newlines inside the JSON
expect(stdoutData).not.toContain(' ')
})

it('serializes BigInt values as strings', () => {
outputJson({ amount: BigInt('1000000'), nested: { fee: 500n } })
const parsed = JSON.parse(stdoutData)
expect(parsed.amount).toBe('1000000')
expect(parsed.nested.fee).toBe('500')
})

it('handles mixed BigInt and regular values', () => {
outputJson({ name: 'TRX', balance: 123456789n, active: true })
const parsed = JSON.parse(stdoutData)
expect(parsed.name).toBe('TRX')
expect(parsed.balance).toBe('123456789')
expect(parsed.active).toBe(true)
})
})

describe('outputError structured codes', () => {
Expand Down
109 changes: 109 additions & 0 deletions test/lib/tokens.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
resolveTokenAddress,
tryResolveTokenAddress,
getTokenByAddress,
getSymbolOrAddress,
listKnownSymbols,
isAddress,
} from '../../src/lib/tokens'

/**
* Expected tokens aligned with common_tokens.json.
* If a token is added/removed from the registry, update this map and src/lib/tokens.ts together.
*/
const EXPECTED: Record<string, Record<string, { address: string; decimals: number }>> = {
mainnet: {
TRX: { address: 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb', decimals: 6 },
WTRX: { address: 'TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR', decimals: 6 },
USDT: { address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', decimals: 6 },
USDD: { address: 'TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn', decimals: 18 },
BTT: { address: 'TAFjULxiVgT4qWk6UZwjqwZXTSaGaqnVp4', decimals: 18 },
JST: { address: 'TCFLL5dx5ZJdKnWuesXxi1VPwjLVmWZZy9', decimals: 18 },
SUN: { address: 'TSSMHYeV2uE9qYH95DqyoCuNCzEL1NvU3S', decimals: 18 },
},
nile: {
TRX: { address: 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb', decimals: 6 },
WTRX: { address: 'TYsbWxNnyTgsZaTFaue9hqpxkU3Fkco94a', decimals: 6 },
USDT: { address: 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf', decimals: 6 },
USDC: { address: 'TWMCMCoJPqCGw5RR7eChF2HoY3a9B8eYA3', decimals: 6 },
SUN: { address: 'TWrZRHY9aKQZcyjpovdH6qeCEyYZrRQDZt', decimals: 18 },
USDJ: { address: 'TLBaRhANQoJFTqre9Nf1mjuwNWjCJeYqUL', decimals: 18 },
TUSD: { address: 'TRz7J6dD2QWxBoumfYt4b3FaiRG23pXfop', decimals: 18 },
JST: { address: 'TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3', decimals: 18 },
},
}

describe('token registry (common_tokens.json alignment)', () => {
for (const [network, tokens] of Object.entries(EXPECTED)) {
describe(network, () => {
for (const [symbol, info] of Object.entries(tokens)) {
it(`resolves ${symbol} → ${info.address}`, () => {
expect(resolveTokenAddress(symbol, network)).toBe(info.address)
})

it(`reverse-lookups ${info.address} → ${symbol}`, () => {
const token = getTokenByAddress(info.address, network)
expect(token).not.toBeNull()
expect(token!.symbol).toBe(symbol)
})
}

it('listKnownSymbols includes all expected symbols', () => {
const symbols = listKnownSymbols(network)
for (const symbol of Object.keys(tokens)) {
expect(symbols).toContain(symbol)
}
})
})
}
})

describe('resolveTokenAddress', () => {
it('returns address as-is when a valid TRON address is passed', () => {
const addr = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'
expect(resolveTokenAddress(addr, 'mainnet')).toBe(addr)
})

it('is case-insensitive for symbols', () => {
expect(resolveTokenAddress('trx', 'mainnet')).toBe('T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb')
expect(resolveTokenAddress('Usdt', 'mainnet')).toBe('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t')
})

it('throws for unknown symbol', () => {
expect(() => resolveTokenAddress('UNKNOWN_TOKEN', 'mainnet')).toThrow('Unknown token symbol')
})
})

describe('tryResolveTokenAddress', () => {
it('returns address for known symbol', () => {
expect(tryResolveTokenAddress('USDT', 'mainnet')).toBe('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t')
})

it('returns null for unknown symbol', () => {
expect(tryResolveTokenAddress('NONEXISTENT', 'mainnet')).toBeNull()
})
})

describe('getSymbolOrAddress', () => {
it('returns symbol for known address', () => {
expect(getSymbolOrAddress('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', 'mainnet')).toBe('USDT')
})

it('returns address unchanged for unknown address', () => {
const addr = 'TUnknownAddr1234567890123456789012'
expect(getSymbolOrAddress(addr, 'mainnet')).toBe(addr)
})
})

describe('isAddress', () => {
it('recognises valid TRON addresses', () => {
expect(isAddress('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t')).toBe(true)
expect(isAddress('T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb')).toBe(true)
})

it('rejects non-address strings', () => {
expect(isAddress('USDT')).toBe(false)
expect(isAddress('0x1234')).toBe(false)
expect(isAddress('')).toBe(false)
})
})
Loading