Skip to content

Commit a1dfa8f

Browse files
committed
feat(wallet): implement external stake credential support and enhance wallet management features
- Added support for external stake credentials in wallet creation and management. - Updated wallet models to include `stakeCredentialHash` and `scriptType`. - Enhanced UI components to reflect changes in stake key handling based on external credentials. - Introduced new methods for managing stake keys and validating addresses. - Updated API endpoints to handle new wallet properties. - Improved user experience with informative messages regarding stake key imports.
1 parent 5fd21fd commit a1dfa8f

File tree

32 files changed

+8298
-2120
lines changed

32 files changed

+8298
-2120
lines changed

jest.config.mjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** @type {import('jest').Config} */
2+
export default {
3+
preset: 'ts-jest/presets/default-esm',
4+
extensionsToTreatAsEsm: ['.ts', '.tsx'],
5+
testEnvironment: 'node',
6+
roots: ['<rootDir>/src'],
7+
testMatch: [
8+
'**/__tests__/**/*.(test|spec).+(ts|tsx|js)',
9+
'**/*.(test|spec).+(ts|tsx|js)'
10+
],
11+
transform: {
12+
'^.+\\.(ts|tsx)$': ['ts-jest', {
13+
useESM: true
14+
}],
15+
},
16+
collectCoverageFrom: [
17+
'src/**/*.{ts,tsx}',
18+
'!src/**/*.d.ts',
19+
'!src/pages/**',
20+
'!src/components/**/*.tsx',
21+
'!src/**/*.stories.{ts,tsx}',
22+
'!src/__tests__/**',
23+
],
24+
coverageDirectory: 'coverage',
25+
coverageReporters: ['text', 'lcov', 'html'],
26+
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
27+
testTimeout: 10000,
28+
verbose: true,
29+
};

package-lock.json

Lines changed: 5213 additions & 1813 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414
"dev": "next dev",
1515
"postinstall": "prisma format && prisma generate && prisma migrate deploy",
1616
"lint": "next lint",
17-
"start": "next start"
17+
"start": "next start",
18+
"test": "jest",
19+
"test:watch": "jest --watch",
20+
"test:coverage": "jest --coverage",
21+
"test:ci": "jest --ci --coverage --watchAll=false"
1822
},
1923
"dependencies": {
2024
"@auth/prisma-adapter": "^1.6.0",
@@ -85,10 +89,12 @@
8589
"zustand": "^4.5.5"
8690
},
8791
"devDependencies": {
92+
"@jest/globals": "^30.1.2",
8893
"@types/busboy": "^1.5.4",
8994
"@types/cors": "^2.8.18",
9095
"@types/eslint": "^8.56.10",
9196
"@types/formidable": "^3.4.5",
97+
"@types/jest": "^30.0.0",
9298
"@types/jsonld": "^1.5.15",
9399
"@types/jsonwebtoken": "^9.0.9",
94100
"@types/node": "^20.14.10",
@@ -101,11 +107,13 @@
101107
"@typescript-eslint/parser": "^8.1.0",
102108
"eslint": "^8.57.0",
103109
"eslint-config-next": "^14.2.4",
110+
"jest": "^30.1.3",
104111
"postcss": "^8.4.39",
105112
"prettier": "^3.3.2",
106113
"prettier-plugin-tailwindcss": "^0.6.5",
107114
"prisma": "^6.4.1",
108115
"tailwindcss": "^3.4.3",
116+
"ts-jest": "^29.4.4",
109117
"typescript": "^5.5.3"
110118
},
111119
"ct3aMetadata": {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- AlterTable
2+
ALTER TABLE "NewWallet" ADD COLUMN "scriptType" TEXT,
3+
ADD COLUMN "stakeCredentialHash" TEXT;

prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ model NewWallet {
7373
signersDescriptions String[]
7474
numRequiredSigners Int?
7575
ownerAddress String
76+
stakeCredentialHash String?
77+
scriptType String?
7678
}
7779

7880
model Nonce {

src/__tests__/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# MultisigSDK Testing Framework
2+
3+
This directory contains comprehensive tests for the MultisigSDK functionality.
4+
5+
## Test Structure
6+
7+
- `setup.ts` - Jest configuration and global test setup
8+
- `testUtils.ts` - Mock data and helper functions for testing
9+
- `multisigSDK.test.ts` - Tests for the MultisigWallet class
10+
- `helpers.test.ts` - Tests for utility functions
11+
12+
## Running Tests
13+
14+
```bash
15+
# Run all tests
16+
npm test
17+
18+
# Run tests in watch mode during development
19+
npm run test:watch
20+
21+
# Run tests with coverage report
22+
npm run test:coverage
23+
24+
# Run tests for CI/CD (no watch mode)
25+
npm run test:ci
26+
```
27+
28+
## Test Coverage
29+
30+
The tests cover:
31+
32+
### MultisigWallet Class
33+
- Constructor validation and key filtering/sorting
34+
- Role-based key management (`getKeysByRole`)
35+
- Script building for different roles (`buildScript`)
36+
- Staking functionality detection (`stakingEnabled`)
37+
- Stake credential hash computation
38+
- JSON metadata generation (CIP-0146)
39+
- Error handling for invalid inputs
40+
41+
### Helper Functions
42+
- `paymentKeyHash` - Extract payment key hash from addresses
43+
- `stakeKeyHash` - Extract stake key hash from stake addresses
44+
- `addressToNetwork` - Determine network from address format
45+
- `checkValidAddress` - Validate Cardano addresses
46+
- `checkValidStakeKey` - Validate stake addresses
47+
48+
## Mock Data
49+
50+
The `testUtils.ts` file provides:
51+
- Mock key hashes for different roles (payment, stake, drep)
52+
- Mock addresses for mainnet and testnet
53+
- Mock MultisigKey arrays for testing
54+
- Helper functions to create test wallets
55+
56+
## Important Notes
57+
58+
1. **Real Addresses**: Some tests use mock addresses. For production testing, use real Cardano addresses.
59+
60+
2. **Network Dependencies**: Tests that interact with @meshsdk/core functions may require actual Cardano address formats.
61+
62+
3. **Error Handling**: Tests verify both success paths and error conditions.
63+
64+
4. **Lexicographic Sorting**: Tests verify that keys are properly sorted per CIP-1854.
65+
66+
## Adding New Tests
67+
68+
When adding new functionality to the MultisigSDK:
69+
70+
1. Add corresponding test cases in the appropriate test file
71+
2. Update mock data in `testUtils.ts` if needed
72+
3. Ensure both success and error cases are covered
73+
4. Run `npm run test:coverage` to verify coverage levels
74+
75+
## CI/CD Integration
76+
77+
The `test:ci` script is designed for continuous integration:
78+
- Runs without watch mode
79+
- Generates coverage reports
80+
- Exits with appropriate status codes for build systems

src/__tests__/helpers.test.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { describe, it, expect } from '@jest/globals';
2+
import {
3+
paymentKeyHash,
4+
stakeKeyHash,
5+
addressToNetwork,
6+
checkValidAddress,
7+
checkValidStakeKey,
8+
} from '../utils/multisigSDK';
9+
import { mockAddresses, mockStakeAddresses, realTestAddresses, externalStakeCredential } from './testUtils';
10+
11+
describe('Helper Functions', () => {
12+
describe('paymentKeyHash', () => {
13+
it('should be a function', () => {
14+
expect(typeof paymentKeyHash).toBe('function');
15+
});
16+
17+
it('should extract key hash from real testnet addresses', () => {
18+
const keyHash1 = paymentKeyHash(realTestAddresses.address1);
19+
const keyHash2 = paymentKeyHash(realTestAddresses.address2);
20+
21+
expect(keyHash1).toBeDefined();
22+
expect(keyHash2).toBeDefined();
23+
expect(keyHash1).not.toBe(keyHash2);
24+
expect(typeof keyHash1).toBe('string');
25+
expect(typeof keyHash2).toBe('string');
26+
});
27+
28+
it('should handle invalid addresses', () => {
29+
expect(() => paymentKeyHash(mockAddresses.invalid)).toThrow();
30+
});
31+
32+
it('should handle empty string', () => {
33+
expect(() => paymentKeyHash('')).toThrow();
34+
});
35+
});
36+
37+
describe('stakeKeyHash', () => {
38+
it('should be a function', () => {
39+
expect(typeof stakeKeyHash).toBe('function');
40+
});
41+
42+
it('should extract stake key hash from real stake address', () => {
43+
const extractedHash = stakeKeyHash(externalStakeCredential);
44+
expect(extractedHash).toBeDefined();
45+
expect(typeof extractedHash).toBe('string');
46+
});
47+
48+
it('should handle invalid stake addresses', () => {
49+
expect(() => stakeKeyHash(mockStakeAddresses.invalid)).toThrow();
50+
});
51+
52+
it('should handle empty string', () => {
53+
expect(() => stakeKeyHash('')).toThrow();
54+
});
55+
});
56+
57+
describe('addressToNetwork', () => {
58+
it('should return 0 for real testnet addresses', () => {
59+
expect(addressToNetwork(realTestAddresses.address1)).toBe(0);
60+
expect(addressToNetwork(realTestAddresses.address2)).toBe(0);
61+
});
62+
63+
it('should return 1 for mainnet addresses', () => {
64+
const mainnetAddress = 'addr1qx3w7rh2p447qkx34x0p0vlr6z34r3n8e8r9qxrl6n';
65+
expect(addressToNetwork(mainnetAddress)).toBe(1);
66+
});
67+
68+
it('should handle addresses without test prefix', () => {
69+
const addressWithoutTest = 'addr1qx3w7rh2p447qkx34x0p0vlr6z34r3n8e8r9qxrl6n';
70+
expect(addressToNetwork(addressWithoutTest)).toBe(1);
71+
});
72+
73+
it('should handle empty string', () => {
74+
expect(addressToNetwork('')).toBe(1);
75+
});
76+
});
77+
78+
describe('checkValidAddress', () => {
79+
it('should return true for real testnet addresses', () => {
80+
expect(checkValidAddress(realTestAddresses.address1)).toBe(true);
81+
expect(checkValidAddress(realTestAddresses.address2)).toBe(true);
82+
});
83+
84+
it('should return false for invalid addresses', () => {
85+
expect(checkValidAddress(mockAddresses.invalid)).toBe(false);
86+
});
87+
88+
it('should return false for empty string', () => {
89+
expect(checkValidAddress('')).toBe(false);
90+
});
91+
92+
it('should return false for null or undefined', () => {
93+
expect(checkValidAddress(null as any)).toBe(false);
94+
expect(checkValidAddress(undefined as any)).toBe(false);
95+
});
96+
});
97+
98+
describe('checkValidStakeKey', () => {
99+
it('should return true for real stake address', () => {
100+
expect(checkValidStakeKey(externalStakeCredential)).toBe(true);
101+
});
102+
103+
it('should return false for invalid stake addresses', () => {
104+
expect(checkValidStakeKey(mockStakeAddresses.invalid)).toBe(false);
105+
});
106+
107+
it('should return false for empty string', () => {
108+
expect(checkValidStakeKey('')).toBe(false);
109+
});
110+
111+
it('should return false for null or undefined', () => {
112+
expect(checkValidStakeKey(null as any)).toBe(false);
113+
expect(checkValidStakeKey(undefined as any)).toBe(false);
114+
});
115+
});
116+
});
117+
118+
describe('Integration Tests', () => {
119+
describe('Address and Network utilities', () => {
120+
it('should correctly identify network from real addresses and validate', () => {
121+
expect(addressToNetwork(realTestAddresses.address1)).toBe(0);
122+
expect(addressToNetwork(realTestAddresses.address2)).toBe(0);
123+
124+
const mainnetAddr = 'addr1qx3w7rh2p447qkx34x0p0vlr6z34r3n8e8r9qxrl6n';
125+
expect(addressToNetwork(mainnetAddr)).toBe(1);
126+
});
127+
128+
it('should handle validation edge cases', () => {
129+
const invalidInputs = ['', null, undefined, 'random_string', 123];
130+
131+
invalidInputs.forEach(input => {
132+
expect(checkValidAddress(input as any)).toBe(false);
133+
expect(checkValidStakeKey(input as any)).toBe(false);
134+
});
135+
});
136+
137+
it('should work with real address extraction and validation', () => {
138+
// Test that we can extract key hashes and validate addresses
139+
const keyHash1 = paymentKeyHash(realTestAddresses.address1);
140+
const keyHash2 = paymentKeyHash(realTestAddresses.address2);
141+
142+
expect(keyHash1).toBeDefined();
143+
expect(keyHash2).toBeDefined();
144+
expect(checkValidAddress(realTestAddresses.address1)).toBe(true);
145+
expect(checkValidAddress(realTestAddresses.address2)).toBe(true);
146+
});
147+
});
148+
});

0 commit comments

Comments
 (0)