Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a4943e4
feature: add unit tests
Jan 26, 2026
e3eb7b5
chore: address unit test feedback
Jan 30, 2026
0ca7db9
refactor: use consistent partial mock pattern in wallet-manager tests
Jan 30, 2026
d1b5285
chore: clean up
Feb 3, 2026
971c7d0
chore: simplify wallet manager mock
Feb 6, 2026
c501d2a
chore: removed the redundant provider.getBalance mock
Feb 6, 2026
86f83a8
chore: provider.trx and provider.transactionBuilder on test suite moc…
Feb 6, 2026
f08ab79
chore: Use a regular import TronWeb from 'tronweb' on read only walle…
Feb 6, 2026
308803d
chore: added a comment explaining the partial mock pattern
Feb 6, 2026
17922ad
chore: replaced the matchers (expect.arrayContaining/expect.objectCon…
Feb 6, 2026
a084400
chore: applied all the patterns from the read-only tests to the walle…
Feb 6, 2026
bac87fa
chore: remove redundant dispose calls and assert path and keyPair in …
Feb 6, 2026
76478ff
chore: prefix test placeholder value with dummy- rather than mock-
Feb 6, 2026
54d0928
chore: assert that sendRawTransaction has been called with the proper…
Feb 6, 2026
5bac8b5
Refactor code.
Davi0kProgramsThings Feb 9, 2026
a25693e
Merge pull request #24 from AlonzoRicardo/feature/add-unit-tests
jonathunne Feb 9, 2026
34af052
fixed dependencies security issues via npm run audit
quocle108 Feb 5, 2026
2842b46
upgrade tonweb to v6.2.0 to fix security issue
quocle108 Feb 9, 2026
e6bba75
lint
quocle108 Feb 9, 2026
634e2f2
simple import tronweb
quocle108 Feb 27, 2026
a7387c3
fix: case sensitive address check in verify
jonathunne Feb 28, 2026
6635776
use verifyMessageV2 from Trx directly instead of requesting an api to…
quocle108 Mar 2, 2026
6056556
Merge pull request #26 from quocle108/fixed-dependencies-issue
AlonzoRicardo Mar 2, 2026
195fe12
chore: bump version, npm audit fix
jonathunne Mar 5, 2026
92ac8c5
Merge branch 'develop' into fix/case-sensitive-addresses
jonathunne Mar 5, 2026
a8952af
Merge pull request #28 from jonathunne/fix/case-sensitive-addresses
jonathunne Mar 5, 2026
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
583 changes: 67 additions & 516 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tetherto/wdk-wallet-tron",
"version": "1.0.0-beta.4",
"version": "1.0.0-beta.5",
"description": "A simple package to manage BIP-32 wallets for the tron blockchain.",
"keywords": [
"wdk",
Expand All @@ -20,7 +20,9 @@
"scripts": {
"build:types": "tsc",
"lint": "standard",
"lint:fix": "standard --fix --env jest"
"lint:fix": "standard --fix",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"test:coverage": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage"
},
"dependencies": {
"@noble/curves": "1.9.2",
Expand All @@ -31,7 +33,7 @@
"bare-node-runtime": "^1.1.4",
"bip39": "3.1.0",
"sodium-universal": "5.0.1",
"tronweb": "5.3.4"
"tronweb": "6.2.0"
},
"devDependencies": {
"cross-env": "7.0.3",
Expand Down Expand Up @@ -62,4 +64,4 @@
"tests/**/*.js"
]
}
}
}
6 changes: 3 additions & 3 deletions src/wallet-account-read-only-tron.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { WalletAccountReadOnly } from '@tetherto/wdk-wallet'

import TronWeb from 'tronweb'
import { TronWeb, Trx } from 'tronweb'

/** @typedef {import('tronweb').Transaction} Transaction */
/** @typedef {import('tronweb').TriggerSmartContract} TriggerSmartContract */
Expand Down Expand Up @@ -83,9 +83,9 @@ export default class WalletAccountReadOnlyTron extends WalletAccountReadOnly {
async verify (message, signature) {
const address = await this.getAddress()

const recoveredAddress = await TronWeb.Trx.verifyMessageV2(message, signature)
const recoveredAddress = await Trx.verifyMessageV2(message, signature)

return address.toLowerCase() === recoveredAddress.toLowerCase()
return address === recoveredAddress
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/wallet-account-tron.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

'use strict'

import TronWeb from 'tronweb'
import { TronWeb } from 'tronweb'

// eslint-disable-next-line camelcase
import { keccak_256 } from '@noble/hashes/sha3'
Expand Down
2 changes: 1 addition & 1 deletion src/wallet-manager-tron.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import WalletManager from '@tetherto/wdk-wallet'

import TronWeb from 'tronweb'
import { TronWeb } from 'tronweb'

import WalletAccountTron from './wallet-account-tron.js'

Expand Down
266 changes: 266 additions & 0 deletions tests/wallet-account-read-only-tron.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import { beforeEach, describe, expect, jest, test } from '@jest/globals'

import { TronWeb, Trx } from 'tronweb'

const ADDRESS = 'TXngH8bVadn9ZWtKBgjKQcqN1GsZ7A1jcb'

const getBalanceMock = jest.fn()
const getAccountResourcesMock = jest.fn()
const getTransactionInfoMock = jest.fn()
const getChainParametersMock = jest.fn()

const triggerConstantContractMock = jest.fn()
const sendTrxMock = jest.fn()

jest.unstable_mockModule('tronweb', () => {
const TronWebMock = jest.fn().mockImplementation((options) => {
const provider = new TronWeb(options)

provider.trx = {
getBalance: getBalanceMock,
getAccountResources: getAccountResourcesMock,
getTransactionInfo: getTransactionInfoMock,
getChainParameters: getChainParametersMock
}

provider.transactionBuilder = {
triggerConstantContract: triggerConstantContractMock,
sendTrx: sendTrxMock
}

return provider
})

// Assigns static properties of the 'TronWeb' class to the mock constructor:
Object.defineProperties(TronWebMock, Object.getOwnPropertyDescriptors(TronWeb))

return {
TronWeb: TronWebMock,
Trx
}
})

const { WalletAccountReadOnlyTron } = await import('../index.js')

describe('WalletAccountReadOnlyTron', () => {
let account

beforeEach(() => {
jest.clearAllMocks()

account = new WalletAccountReadOnlyTron(ADDRESS, {
provider: 'https://tron.web.provider/'
})
})

describe('verify', () => {
const MESSAGE = 'Dummy message to sign.'
const SIGNATURE = '0x67b1e4bb9a9b070cd60776ceab1ff4d7c4d4997bb5b4a71757da646f75d847e6600c22d8d83caa13d42c33099f75ba5ec30390467392aa78a3e5319da6c30e291b'

test('should return true for a valid signature', async () => {
const result = await account.verify(MESSAGE, SIGNATURE)

expect(result).toBe(true)
})

test('should return false for an invalid signature', async () => {
const result = await account.verify('Another message.', SIGNATURE)

expect(result).toBe(false)
})

test('should return false for an address with invalid casing', async () => {
const lowercasedAddressAccount = new WalletAccountReadOnlyTron(ADDRESS.toLowerCase())

const result = await lowercasedAddressAccount.verify(MESSAGE, SIGNATURE)

expect(result).toBe(false)
})

test('should throw on a malformed signature', async () => {
await expect(account.verify(MESSAGE, '0xinvalid'))
.rejects.toThrow(/invalid BytesLike value/)
})
})

describe('getBalance', () => {
test('should return the correct balance of the account', async () => {
const DUMMY_BALANCE = 1_000_000_000
getBalanceMock.mockResolvedValue(DUMMY_BALANCE)

const balance = await account.getBalance()

expect(getBalanceMock).toHaveBeenCalledWith(ADDRESS)
expect(balance).toBe(1_000_000_000n)
})

test('should throw if the account is not connected to tron web', async () => {
const disconnectedAccount = new WalletAccountReadOnlyTron(ADDRESS)
await expect(disconnectedAccount.getBalance())
.rejects.toThrow('The wallet must be connected to tron web to retrieve balances.')
})
})

describe('getTokenBalance', () => {
const TOKEN_ADDRESS = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'

test('should return the correct token balance of the account', async () => {
const DUMMY_CONSTANT_RESULT = {
constant_result: ['00000000000000000000000000000000000000000000000000000000000f4240']
}

triggerConstantContractMock.mockResolvedValue(DUMMY_CONSTANT_RESULT)

const balance = await account.getTokenBalance(TOKEN_ADDRESS)

expect(triggerConstantContractMock).toHaveBeenCalledWith(
TOKEN_ADDRESS,
'balanceOf(address)',
{},
[{ type: 'address', value: TronWeb.address.toHex(ADDRESS) }],
TronWeb.address.toHex(ADDRESS)
)

expect(balance).toBe(1_000_000n)
})

test('should throw if the account is not connected to tron web', async () => {
const disconnectedAccount = new WalletAccountReadOnlyTron(ADDRESS)
await expect(disconnectedAccount.getTokenBalance(TOKEN_ADDRESS))
.rejects.toThrow('The wallet must be connected to tron web to retrieve token balances.')
})
})

describe('quoteSendTransaction', () => {
test('should successfully quote a transaction', async () => {
const TRANSACTION = {
to: 'TAibbFBAkcNioexXTFWKbp65mgLp7JiqHD',
value: 1_000_000
}
const EXPECTED_FEE = 202_000n

sendTrxMock.mockResolvedValue({
txID: 'dummy-tx-id',
raw_data_hex: '0a' + '00'.repeat(100)
})

getAccountResourcesMock.mockResolvedValue({
freeNetLimit: 5000,
freeNetUsed: 4900,
NetLimit: 0,
NetUsed: 0
})

const { fee } = await account.quoteSendTransaction(TRANSACTION)

expect(sendTrxMock).toHaveBeenCalledWith(
TRANSACTION.to,
TRANSACTION.value,
ADDRESS
)

expect(getAccountResourcesMock).toHaveBeenCalledWith(ADDRESS)

expect(fee).toBe(EXPECTED_FEE)
})

test('should throw if the account is not connected to tron web', async () => {
const disconnectedAccount = new WalletAccountReadOnlyTron(ADDRESS)
await expect(disconnectedAccount.quoteSendTransaction({ to: ADDRESS, value: 1000 }))
.rejects.toThrow('The wallet must be connected to tron web to quote transactions.')
})
})

describe('quoteTransfer', () => {
test('should successfully quote a transfer operation', async () => {
const TRANSFER = {
token: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
recipient: 'TAibbFBAkcNioexXTFWKbp65mgLp7JiqHD',
amount: 100_000_000
}

triggerConstantContractMock.mockResolvedValue({
constant_result: ['0000000000000000000000000000000000000000000000000000000000000064'],
energy_used: 10000,
transaction: {
raw_data_hex: '0a' + '00'.repeat(200)
}
})

getAccountResourcesMock.mockResolvedValue({
freeNetLimit: 5000,
freeNetUsed: 0,
NetLimit: 0,
NetUsed: 0,
EnergyLimit: 100000,
EnergyUsed: 0
})

getChainParametersMock.mockResolvedValue([
{ key: 'getEnergyFee', value: 420 }
])

const { fee } = await account.quoteTransfer(TRANSFER)

expect(triggerConstantContractMock).toHaveBeenCalledWith(
TRANSFER.token,
'transfer(address,uint256)',
{},
[
{ type: 'address', value: TronWeb.address.toHex(TRANSFER.recipient) },
{ type: 'uint256', value: TRANSFER.amount }
],
TronWeb.address.toHex(ADDRESS)
)

expect(getAccountResourcesMock).toHaveBeenCalledWith(ADDRESS)

expect(typeof fee).toBe('bigint')
})

test('should throw if the account is not connected to tron web', async () => {
const disconnectedAccount = new WalletAccountReadOnlyTron(ADDRESS)
await expect(disconnectedAccount.quoteTransfer({
token: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
recipient: ADDRESS,
amount: 100
}))
.rejects.toThrow('The wallet must be connected to tron web to quote transfer operations.')
})
})

describe('getTransactionReceipt', () => {
const TRANSACTION_HASH = 'abc123def456'

test('should return the correct transaction receipt', async () => {
const DUMMY_RECEIPT = {
id: TRANSACTION_HASH,
blockNumber: 12345,
fee: 1000,
result: 'SUCCESS'
}

getTransactionInfoMock.mockResolvedValue(DUMMY_RECEIPT)

const receipt = await account.getTransactionReceipt(TRANSACTION_HASH)

expect(getTransactionInfoMock).toHaveBeenCalledWith(TRANSACTION_HASH)
expect(receipt).toEqual(DUMMY_RECEIPT)
})

test('should return null if the transaction has not been included in a block yet', async () => {
getTransactionInfoMock.mockResolvedValue({})

const receipt = await account.getTransactionReceipt(TRANSACTION_HASH)

expect(getTransactionInfoMock).toHaveBeenCalledWith(TRANSACTION_HASH)
expect(receipt).toBe(null)
})

test('should throw if the account is not connected to tron web', async () => {
const disconnectedAccount = new WalletAccountReadOnlyTron(ADDRESS)
await expect(disconnectedAccount.getTransactionReceipt(TRANSACTION_HASH))
.rejects.toThrow('The wallet must be connected to tron web to fetch transaction receipts.')
})
})
})
Loading