| name | code-of-conduct |
| description | Code of conduct that contributors — including AI coding assistants — MUST follow when writing, modifying, or reviewing code for any Tether Wallet Development Kit (WDK) module (wallets, protocols, core). Covers project workflow (atomic single-feature pull requests, design-before-implementation specs), code quality (avoid code smells, never violate S.O.L.I.D. principles, document every public/protected component, use narrow types, avoid defensive programming, mark every thrown error with @throws), and testing (arrange-act-assert, test only the public API, assert real values not patterns, define jest mocks at the top level, mock all external-service dependencies). |
WDK Code of Conduct
Binding rules every contributor — human or AI coding assistant — MUST follow when writing, modifying, or reviewing code for any Wallet Development Kit (WDK) module (wallets, protocols, core).
- When writing or changing WDK code, comply with every applicable rule before you consider the work done.
- When reviewing WDK code, flag each violation, cite its rule ID, and propose a concrete fix.
The rules are repository-agnostic. No WDK module is a "golden reference" — apply every rule to every module equally. If existing code conflicts with a rule, the code is the candidate for change, not the rule.
Rules are grouped by prefix: P = project & workflow, C = code, T = testing.
| ID | Rule |
|---|
| P001 | Pull requests should only include single features |
| P002 | Design, then implement |
| C001 | Avoid code smells |
| C002 | Code should never violate S.O.L.I.D. principles |
| C003 | Provide proper documentation for all public and protected components |
| C004 | Use narrow types for properties and methods |
| C005 | Avoid defensive programming |
| C006 | Always mark errors that a method can throw |
| T001 | Follow the arrange, act, assert pattern |
| T002 | Test only components that are part of the public API |
| T003 | Always test against real values, not properties |
| T004 | Define and assign jest functions at the top level, stub them in test cases |
| T005 | Mock all depended-on components that interact with external services |
Related skills: wdk-review-types-jsdoc enforces the JSDoc & type conventions behind C003/C004 in depth; wdk-review-tests enforces the T-rules in depth. Reach for them when you want a focused review of an existing file.
Project guidelines
P001: Pull requests should only include single features
Keep every pull request to the minimum code needed to implement one atomic feature — a single piece of functionality — so reviews stay fast. For a public-API class, each public method is a separate atomic feature.
You may bundle several minor features in one PR at your discretion to cut down the number of PRs, but only when doing so keeps the PR small and quick to go over. Large, multi-feature PRs are not acceptable.
P002: Design, then implement
Before implementing any change to the public API or any new module from scratch, design first: write a technical specification document and reach consensus on it before writing a line of implementation.
The specification must:
- describe only public-API components — stay fully agnostic of implementation details;
- include a technical reference for every new public component (e.g. for a method: its full documentation, including types and descriptions);
- optionally capture the rationale and critical thinking behind the new change or module.
Reviewers and technical leaders review the document, discussing and requesting changes. Implementation starts only after the specification reaches common consensus.
Example
A document template is a work in progress. In the meantime, use wdk-protocol-swap-aori-evm's specification as a reference.
Code guidelines
C001: Avoid code smells
Write cleaner, more readable, more maintainable code by avoiding every code smell. If you are unsure what counts as a smell, consult refactoring.guru's list or the code smell catalog. Common offenders: magic numbers, duplicated code, long methods, large classes, and feature envy.
Example
Magic numbers are usually a code smell — extract them into named constants.
export default class WalletManagerEvm extends WalletManager {
async getFeeRates () {
[...]
return {
normal: feeRate * 110n / 100n,
[...]
}
return {
normal: feeRate * WalletManagerEvm._FEE_RATE_NORMAL_MULTIPLIER / 100n,
[...]
}
}
}
C002: Code should never violate S.O.L.I.D. principles
Know all five S.O.L.I.D. principles and make sure your code violates none of them: Single Responsibility, Open–Closed, Liskov Substitution, Interface Segregation, and Dependency Injection.
Example
Adding a stricter post-condition to an override breaks the Liskov Substitution Principle.
export default class WalletAccountReadOnlyEvm extends WalletAccountReadOnly {
async getTokenBalance (tokenAddress) {
[...]
}
}
C003: Provide proper documentation for all public and protected components
Every component that is part of the public API needs proper documentation — meaningful descriptions and types. @protected members count: protected visibility is part of the public API. Documenting private fields and methods is not as essential, but still a significant readability improvement, especially for complex components.
Example
A @protected property is part of the public API and needs a description and @type.
export default class WalletAccountEvm extends WalletAccountReadOnlyEvm {
constructor (seed, path, config = { }) {
this._config = config
this._config = config
}
}
C004: Use narrow types for properties and methods
Always extract and use the narrowest type that fits a property or method.
- For a value that can be any type, or whose type is not known, prefer
unknown over any.
- For an object that should hold arbitrary user-chosen data, use
Record<string, unknown> instead of Object.
Wide types leak terrible types to consumers and shift the effort of validating a value from the type checker into your own runtime code.
Example
Narrow the body field to the types it actually accepts.
C005: Avoid defensive programming
Defensive programming means checking that a method's pre-conditions hold before proceeding. The only pre-conditions WDK uses are the types of a method's arguments, and you should always assume those are correct — the generated .d.ts declarations together with the consumer's type checker already report type errors. Runtime null/type guards on already-typed parameters are redundant; do not write them.
Example
Do not re-validate a parameter the type already guarantees.
export default class WalletAccountReadOnlyTon {
async getTokenBalance (tokenAddress) {
if (!tokenAddress || typeof tokenAddress !== 'string') {
throw new Error('Invalid value for the token address.')
}
[...]
}
}
C006: Always mark errors that a method can throw
Document every error a method might throw in its documentation, using the @throws tag to state both the error type and the condition under which it is thrown. This lets consumers know which errors to expect and exactly when.
Example
export default class WalletAccountEvm extends WalletAccountReadOnlyEvm {
async approve (options) {
[...]
}
}
Testing guidelines
T001: Follow the arrange, act, assert pattern
Structure every unit and integration test as Arrange → Act → Assert:
- Arrange — set up all constants, mocks, and data the test needs, including any
before/after hook.
- Act — run the code under test, e.g. one method call on the SUT.
- Assert — make all the assertions on the result of the act step.
Example
describe('WalletAccountTron', () => {
describe('signTransaction', () => {
test('should sign a transaction', async () => {
const EXPECTED_SIGNATURE = 'e2fbd0590d2a6150952afdcdb8c0b137a8828fe45dacc6f17f552b10234baa9231811488104983c4d4333ad51c90343801aa72e41b1d576719cc798c4c98546100'
sendTrxMock.mockResolvedValue(DUMMY_SEND_TRX_RESULT)
const transaction = await account.signTransaction(TRANSACTION)
expect(sendTrxMock).toHaveBeenCalledWith(TRANSACTION.to, TRANSACTION.value, ACCOUNT.address)
expect(transaction).toEqual({
...DUMMY_SEND_TRX_RESULT,
signature: [EXPECTED_SIGNATURE]
})
})
})
})
T002: Test only components that are part of the public API
Unit and integration tests must cover and use only public-API components. This hides implementation details, keeps tests passing when internals change, and forces you to test against the specification from the user's point of view. Do not test private members (prefixed with _) directly — exercising the public API reaches protected and private code indirectly, so coverage still flows through.
Example
Do not write a test suite for a private method.
describe('WalletAccountTron', () => {
describe('_signTransaction', () => {
})
})
T003: Always test against real values, not properties
Always assert and compare outputs against concrete, real expected values, instead of properties or patterns that merely should apply to them. A pattern/shape check passes for any value of the right form — even a wrong one — so it does not prove correctness.
Example
describe('WalletAccountTron', () => {
describe('sign', () => {
test('should return the correct signature', async () => {
const SIGNATURE_PATTERN = /^(?:0x)?[0-9a-fA-F]{128}(?:00|01|1b|1c|1B|1C)$/
const signature = await account.sign('Hello world!')
expect(signature).toMatch(SIGNATURE_PATTERN)
const EXPECTED_SIGNATURE = '0x0e6d4a25bc8da8fcc08a227612d1caa5f33635f2c9b490f26ea08e228ec879a670607c5bfb68f45fbb1e67972420a366e66aadf18c9dc7806201ea202354c6fc1b'
const signature = await account.sign('Hello world!')
expect(signature).toBe(EXPECTED_SIGNATURE)
})
})
})
T004: Define and assign jest functions at the top level, stub them in test cases
When a unit test must mock one of the SUT's depended-on components, define and assign the jest mock functions at the top level of the test suite. Because WDK uses ESM, this usually requires jest's unstable_mockModule. Configure the stubs (return values) only inside before hooks or individual test cases. The Assert step must always include assertions that verify each stub was called with the proper arguments.
Example
const sendTrxMock = jest.fn()
jest.unstable_mockModule('tronweb', () => ({
transactionBuilder: {
sendTrx: sendTrxMock
}
}))
describe('WalletAccountTron', () => {
describe('signTransaction', () => {
test('should sign a transaction', async () => {
const EXPECTED_SIGNATURE = 'e2fbd0590d2a6150952afdcdb8c0b137a8828fe45dacc6f17f552b10234baa9231811488104983c4d4333ad51c90343801aa72e41b1d576719cc798c4c98546100'
sendTrxMock.mockResolvedValue(DUMMY_SEND_TRX_RESULT)
[...]
expect(sendTrxMock).toHaveBeenCalledWith(TRANSACTION.to, TRANSACTION.value, ACCOUNT.address)
})
})
})
T005: Mock all depended-on components that interact with external services
Unit tests must mock all the SUT's depended-on components that interact with external services (network, blockchain nodes, I/O). This keeps unit tests fast, isolated, and deterministic. Integration tests take care of covering the actual integration with the external service.
Example
const getNetworkMock = jest.fn()
jest.unstable_mockModule('ethers', () => ({
...ethers,
JsonRpcProvider: jest.fn().mockImplementation(() => ({
getNetwork: getNetworkMock
}))
}))
Compliance checklist
Before considering any WDK code change complete, confirm:
Project
Code
Testing