Skip to content

Commit 02d84ca

Browse files
committed
Add and extend unit tests across SDK packages
- precompiles: add addresses.spec.ts validating all 12 precompile addresses are valid 42-char Ethereum addresses, unique, and in the reserved range; add abis.spec.ts validating each ABI has expected named functions, correct entry structure, and is non-empty - registry: extend networks, ibc, gas, and tokens tests with URL format validation, channel-N pattern checks, positive gas price assertions, and https image URL checks - create-sei: add templates.test.ts verifying template and extension directory structure, package.json validity, and tsconfig presence - sei-global-wallet: add config.spec.ts validating walletName, walletUrl, environmentId, rdns pattern, and walletIcon data URI - ledger: extend seiLedgerOfflineAminoSigner.spec.ts with BIP44 derivation path pattern tests Also revert planning/doc files added during exploration that are out of scope for this branch.
1 parent cf11419 commit 02d84ca

13 files changed

Lines changed: 361 additions & 103 deletions

File tree

.github/ISSUE_TEMPLATE/evm_compatibility.md

Lines changed: 0 additions & 40 deletions
This file was deleted.

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
packages/*/coverage
55

66
examples/
7-
.internal/
87

98
# compiled output
109
dist

docs/docs.json

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,6 @@
8181
}
8282
]
8383
},
84-
{
85-
"dropdown": "EVM Parity",
86-
"description": "Compatibility matrix",
87-
"icon": "table",
88-
"pages": ["evm-parity/matrix"]
89-
},
9084
{
9185
"dropdown": "@sei-js/sei-global-wallet",
9286
"description": "User friendly wallet connect",

docs/evm-parity/matrix.mdx

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, it, expect } from '@jest/globals';
2+
import { promises as fs } from 'node:fs';
3+
import path from 'node:path';
4+
5+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
6+
const TEMPLATES_DIR = path.join(PACKAGE_ROOT, 'templates');
7+
const EXTENSIONS_DIR = path.join(PACKAGE_ROOT, 'extensions');
8+
9+
const EXPECTED_TEMPLATES = ['next-template'];
10+
const EXPECTED_EXTENSIONS = ['precompiles'];
11+
12+
describe('Templates', () => {
13+
it('templates directory exists', async () => {
14+
const stat = await fs.stat(TEMPLATES_DIR);
15+
expect(stat.isDirectory()).toBe(true);
16+
});
17+
18+
it.each(EXPECTED_TEMPLATES)('%s template directory exists', async (template) => {
19+
const templatePath = path.join(TEMPLATES_DIR, template);
20+
const stat = await fs.stat(templatePath);
21+
expect(stat.isDirectory()).toBe(true);
22+
});
23+
24+
it.each(EXPECTED_TEMPLATES)('%s template has a valid package.json', async (template) => {
25+
const pkgPath = path.join(TEMPLATES_DIR, template, 'package.json');
26+
const contents = await fs.readFile(pkgPath, 'utf-8');
27+
const parsed = JSON.parse(contents);
28+
expect(typeof parsed.name).toBe('string');
29+
expect(parsed.name.trim().length).toBeGreaterThan(0);
30+
expect(typeof parsed.version).toBe('string');
31+
});
32+
33+
it.each(EXPECTED_TEMPLATES)('%s template has a tsconfig.json', async (template) => {
34+
const tsconfigPath = path.join(TEMPLATES_DIR, template, 'tsconfig.json');
35+
const stat = await fs.stat(tsconfigPath);
36+
expect(stat.isFile()).toBe(true);
37+
});
38+
39+
it.each(EXPECTED_TEMPLATES)('%s template has a src/ directory', async (template) => {
40+
const srcPath = path.join(TEMPLATES_DIR, template, 'src');
41+
const stat = await fs.stat(srcPath);
42+
expect(stat.isDirectory()).toBe(true);
43+
});
44+
});
45+
46+
describe('Extensions', () => {
47+
it('extensions directory exists', async () => {
48+
const stat = await fs.stat(EXTENSIONS_DIR);
49+
expect(stat.isDirectory()).toBe(true);
50+
});
51+
52+
it.each(EXPECTED_EXTENSIONS)('%s extension directory exists', async (extension) => {
53+
const extensionPath = path.join(EXTENSIONS_DIR, extension);
54+
const stat = await fs.stat(extensionPath);
55+
expect(stat.isDirectory()).toBe(true);
56+
});
57+
58+
it.each(EXPECTED_EXTENSIONS)('%s extension has a valid package.json', async (extension) => {
59+
const pkgPath = path.join(EXTENSIONS_DIR, extension, 'package.json');
60+
const contents = await fs.readFile(pkgPath, 'utf-8');
61+
const parsed = JSON.parse(contents);
62+
expect(typeof parsed.name).toBe('string');
63+
expect(parsed.name.trim().length).toBeGreaterThan(0);
64+
});
65+
});

packages/ledger/src/cosmos/__tests__/seiLedgerOfflineAminoSigner.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ const mockR = new Uint8Array([0, 1, 2]); // should be stripped
99
const mockS = new Uint8Array([0, 3, 4]); // should be stripped
1010
const path = "m/44'/118'/0'/0/0";
1111

12+
/** BIP44 derivation path pattern: m/purpose'/coin_type'/account'/change/index */
13+
const BIP44_PATTERN = /^m\/44'\/\d+'\/\d+'\/\d+\/\d+$/;
14+
1215
const mockSeiApp = {
1316
getCosmosAddress: jest.fn().mockResolvedValue({
1417
address: mockAddress,
@@ -20,6 +23,28 @@ const mockSeiApp = {
2023
})
2124
};
2225

26+
describe('BIP44 derivation paths', () => {
27+
it('standard Cosmos derivation path matches BIP44 pattern', () => {
28+
expect("m/44'/118'/0'/0/0").toMatch(BIP44_PATTERN);
29+
});
30+
31+
it('EVM-compatible derivation path matches BIP44 pattern', () => {
32+
expect("m/44'/60'/0'/0/0").toMatch(BIP44_PATTERN);
33+
});
34+
35+
it('non-zero account indices match BIP44 pattern', () => {
36+
expect("m/44'/118'/1'/0/0").toMatch(BIP44_PATTERN);
37+
expect("m/44'/118'/0'/0/5").toMatch(BIP44_PATTERN);
38+
});
39+
40+
it('path is passed through to getCosmosAddress unchanged', async () => {
41+
const customPath = "m/44'/118'/0'/0/3";
42+
const signer = new SeiLedgerOfflineAminoSigner(mockSeiApp as never, customPath);
43+
await signer.getAccounts();
44+
expect(mockSeiApp.getCosmosAddress).toHaveBeenCalledWith(customPath);
45+
});
46+
});
47+
2348
describe('SeiLedgerOfflineAminoSigner', () => {
2449
let signer: SeiLedgerOfflineAminoSigner;
2550

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import {
2+
ADDRESS_PRECOMPILE_ABI,
3+
BANK_PRECOMPILE_ABI,
4+
DISTRIBUTION_PRECOMPILE_ABI,
5+
GOVERNANCE_PRECOMPILE_ABI,
6+
IBC_PRECOMPILE_ABI,
7+
JSON_PRECOMPILE_ABI,
8+
ORACLE_PRECOMPILE_ABI,
9+
POINTER_PRECOMPILE_ABI,
10+
POINTERVIEW_PRECOMPILE_ABI,
11+
SOLO_PRECOMPILE_ABI,
12+
STAKING_PRECOMPILE_ABI,
13+
WASM_PRECOMPILE_ABI
14+
} from '../index';
15+
16+
type AbiEntry = { type: string; name?: string; inputs?: readonly unknown[]; outputs?: readonly unknown[]; stateMutability?: string };
17+
type Abi = readonly AbiEntry[];
18+
19+
function getFunctionNames(abi: Abi): string[] {
20+
return abi.filter((entry) => entry.type === 'function').map((entry) => entry.name!);
21+
}
22+
23+
function getFunctions(abi: Abi): AbiEntry[] {
24+
return abi.filter((entry) => entry.type === 'function');
25+
}
26+
27+
const PRECOMPILE_ABIS: [string, Abi, string[]][] = [
28+
['ADDRESS', ADDRESS_PRECOMPILE_ABI, ['getSeiAddr', 'getEvmAddr', 'associate', 'associatePubKey']],
29+
['BANK', BANK_PRECOMPILE_ABI, ['send', 'sendNative', 'balance', 'all_balances', 'supply', 'decimals', 'name', 'symbol']],
30+
['DISTRIBUTION', DISTRIBUTION_PRECOMPILE_ABI, ['setWithdrawAddress', 'withdrawDelegationRewards', 'withdrawMultipleDelegationRewards', 'rewards']],
31+
['GOVERNANCE', GOVERNANCE_PRECOMPILE_ABI, ['vote', 'deposit']],
32+
['IBC', IBC_PRECOMPILE_ABI, ['transfer']],
33+
['JSON', JSON_PRECOMPILE_ABI, ['extractAsBytes', 'extractAsBytesList']],
34+
['ORACLE', ORACLE_PRECOMPILE_ABI, ['getExchangeRates', 'getOracleTwaps']],
35+
['POINTER', POINTER_PRECOMPILE_ABI, ['addCW20Pointer', 'addCW721Pointer', 'addNativePointer']],
36+
['POINTERVIEW', POINTERVIEW_PRECOMPILE_ABI, ['getCW20Pointer', 'getCW721Pointer']],
37+
['STAKING', STAKING_PRECOMPILE_ABI, ['delegate', 'undelegate', 'redelegate', 'delegation']],
38+
['WASM', WASM_PRECOMPILE_ABI, ['execute', 'execute_batch']]
39+
];
40+
41+
describe('Precompile ABIs — function names', () => {
42+
it.each(PRECOMPILE_ABIS)('%s ABI contains all expected function names', (_name, abi, expectedFunctions) => {
43+
const actualFunctions = getFunctionNames(abi as Abi);
44+
for (const fn of expectedFunctions) {
45+
expect(actualFunctions).toContain(fn);
46+
}
47+
});
48+
});
49+
50+
describe('Precompile ABIs — function entry structure', () => {
51+
it.each(PRECOMPILE_ABIS)('%s ABI functions each have inputs, outputs, and stateMutability', (_name, abi) => {
52+
const functions = getFunctions(abi as Abi);
53+
expect(functions.length).toBeGreaterThan(0);
54+
55+
for (const fn of functions) {
56+
expect(Array.isArray(fn.inputs)).toBe(true);
57+
expect(Array.isArray(fn.outputs)).toBe(true);
58+
expect(typeof fn.stateMutability).toBe('string');
59+
expect(['view', 'nonpayable', 'payable', 'pure']).toContain(fn.stateMutability);
60+
}
61+
});
62+
});
63+
64+
describe('Precompile ABIs — top-level structure', () => {
65+
const ALL_ABIS: [string, Abi][] = [
66+
['ADDRESS', ADDRESS_PRECOMPILE_ABI],
67+
['BANK', BANK_PRECOMPILE_ABI],
68+
['DISTRIBUTION', DISTRIBUTION_PRECOMPILE_ABI],
69+
['GOVERNANCE', GOVERNANCE_PRECOMPILE_ABI],
70+
['IBC', IBC_PRECOMPILE_ABI],
71+
['JSON', JSON_PRECOMPILE_ABI],
72+
['ORACLE', ORACLE_PRECOMPILE_ABI],
73+
['POINTER', POINTER_PRECOMPILE_ABI],
74+
['POINTERVIEW', POINTERVIEW_PRECOMPILE_ABI],
75+
['SOLO', SOLO_PRECOMPILE_ABI],
76+
['STAKING', STAKING_PRECOMPILE_ABI],
77+
['WASM', WASM_PRECOMPILE_ABI]
78+
];
79+
80+
it.each(ALL_ABIS)('%s ABI is a non-empty array', (_name, abi) => {
81+
expect(Array.isArray(abi)).toBe(true);
82+
expect((abi as Abi).length).toBeGreaterThan(0);
83+
});
84+
85+
it.each(ALL_ABIS)('%s ABI entries each have a type field', (_name, abi) => {
86+
for (const entry of abi as Abi) {
87+
expect(typeof entry.type).toBe('string');
88+
expect(entry.type.length).toBeGreaterThan(0);
89+
}
90+
});
91+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {
2+
ADDRESS_PRECOMPILE_ADDRESS,
3+
BANK_PRECOMPILE_ADDRESS,
4+
DISTRIBUTION_PRECOMPILE_ADDRESS,
5+
GOVERNANCE_PRECOMPILE_ADDRESS,
6+
IBC_PRECOMPILE_ADDRESS,
7+
JSON_PRECOMPILE_ADDRESS,
8+
ORACLE_PRECOMPILE_ADDRESS,
9+
POINTER_PRECOMPILE_ADDRESS,
10+
POINTERVIEW_PRECOMPILE_ADDRESS,
11+
SOLO_PRECOMPILE_ADDRESS,
12+
STAKING_PRECOMPILE_ADDRESS,
13+
WASM_PRECOMPILE_ADDRESS
14+
} from '../index';
15+
16+
const PRECOMPILE_ADDRESSES: [string, string][] = [
17+
['ADDRESS', ADDRESS_PRECOMPILE_ADDRESS],
18+
['BANK', BANK_PRECOMPILE_ADDRESS],
19+
['DISTRIBUTION', DISTRIBUTION_PRECOMPILE_ADDRESS],
20+
['GOVERNANCE', GOVERNANCE_PRECOMPILE_ADDRESS],
21+
['IBC', IBC_PRECOMPILE_ADDRESS],
22+
['JSON', JSON_PRECOMPILE_ADDRESS],
23+
['ORACLE', ORACLE_PRECOMPILE_ADDRESS],
24+
['POINTER', POINTER_PRECOMPILE_ADDRESS],
25+
['POINTERVIEW', POINTERVIEW_PRECOMPILE_ADDRESS],
26+
['SOLO', SOLO_PRECOMPILE_ADDRESS],
27+
['STAKING', STAKING_PRECOMPILE_ADDRESS],
28+
['WASM', WASM_PRECOMPILE_ADDRESS]
29+
];
30+
31+
/** Validates an ERC-55 checksummed Ethereum address: 0x + exactly 40 hex characters. */
32+
function isValidEthAddress(address: string): boolean {
33+
return /^0x[0-9a-fA-F]{40}$/.test(address);
34+
}
35+
36+
describe('Precompile addresses', () => {
37+
it.each(PRECOMPILE_ADDRESSES)('%s address is a valid 42-character Ethereum address', (_name, address) => {
38+
expect(typeof address).toBe('string');
39+
expect(isValidEthAddress(address)).toBe(true);
40+
});
41+
42+
it('all precompile addresses are unique', () => {
43+
const addresses = PRECOMPILE_ADDRESSES.map(([, addr]) => addr.toLowerCase());
44+
const unique = new Set(addresses);
45+
expect(unique.size).toBe(addresses.length);
46+
});
47+
48+
it('all precompile addresses start with 0x000000000000000000000000000000000000', () => {
49+
// Sei precompiles live in the reserved 0x1000–0x10FF range
50+
for (const [, address] of PRECOMPILE_ADDRESSES) {
51+
expect(address.toLowerCase()).toMatch(/^0x0{36}/);
52+
}
53+
});
54+
});

packages/registry/src/gas/__tests__/index.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,19 @@ describe('GasInfo Tests', () => {
3232
expect(pacific1.min_gas_price).toBeGreaterThanOrEqual(0.01);
3333
expect(pacific1.module_adjustments.dex.sudo_gas_price).toBeLessThanOrEqual(0.02);
3434
});
35+
36+
it('all networks have a positive min_gas_price', () => {
37+
for (const [network, info] of Object.entries(GAS_INFO)) {
38+
expect(info.min_gas_price).toBeGreaterThan(0);
39+
if (info.min_gas_price <= 0) {
40+
throw new Error(`Network ${network} has non-positive min_gas_price: ${info.min_gas_price}`);
41+
}
42+
}
43+
});
44+
45+
it('all networks use usei as the fee denom', () => {
46+
for (const info of Object.values(GAS_INFO)) {
47+
expect(info.denom).toBe('usei');
48+
}
49+
});
3550
});

0 commit comments

Comments
 (0)