Skip to content

Commit a347e79

Browse files
committed
feat(sdk): support voter client
1 parent 22ae196 commit a347e79

File tree

19 files changed

+3483
-425
lines changed

19 files changed

+3483
-425
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/**
2+
* Pre-Add-New-Key and Pre-Deactivate API Complete Test (using MaciClient and VoterClient)
3+
*
4+
* This script demonstrates the complete AMACI Pre-Deactivate workflow:
5+
* 1. Create Tenant and API Key
6+
* 2. Create AMACI Round (automatic Pre-Deactivate mode)
7+
* 3. Query Pre-Deactivate data
8+
* 4. Test Pre-Add-New-Key
9+
* 5. Test voting
10+
*/
11+
12+
import { MaciClient } from '../src/maci';
13+
import { VoterClient } from '../src/voter';
14+
import { MaciCircuitType } from '../src/types';
15+
import * as path from 'path';
16+
17+
function generateRandomString(length: number) {
18+
return Math.random()
19+
.toString(36)
20+
.substring(2, 2 + length);
21+
}
22+
23+
async function main() {
24+
const network = 'testnet';
25+
26+
console.log('='.repeat(80));
27+
console.log('Pre-Add-New-Key and Pre-Deactivate API Complete Test (MaciClient & VoterClient)');
28+
console.log('='.repeat(80));
29+
30+
// API base configuration
31+
const API_BASE_URL = 'http://localhost:8080';
32+
33+
// Create temporary MaciClient (for admin operations, no API key required)
34+
const adminMaciClient = new MaciClient({
35+
network: network,
36+
saasApiEndpoint: API_BASE_URL
37+
});
38+
39+
// ==================== 1. Create Tenant and API Key ====================
40+
const tenantName = `Test Tenant ${generateRandomString(10)}`;
41+
console.log(`\n[1/5] Creating Tenant: ${tenantName}`);
42+
43+
// Create Tenant through underlying API client
44+
const tenantData = await adminMaciClient.getSaasApiClient().createTenant({
45+
name: tenantName
46+
});
47+
console.log('✓ Tenant created successfully:', tenantData.id);
48+
49+
const apiKeyData = await adminMaciClient.getSaasApiClient().createApiKey({
50+
tenantId: tenantData.id,
51+
label: 'Test API Key',
52+
plan: 'pro'
53+
});
54+
const apiKey = apiKeyData.apiKey;
55+
console.log('✓ API Key created successfully:', apiKey);
56+
57+
// Create MaciClient with API Key
58+
const maciClient = new MaciClient({
59+
network: network,
60+
saasApiEndpoint: API_BASE_URL,
61+
saasApiKey: apiKey
62+
});
63+
64+
// ==================== 2. Create AMACI Round (Auto Pre-Deactivate Mode) ====================
65+
console.log('\n[2/5] Creating AMACI Round (Auto Pre-Deactivate Mode)');
66+
console.log('Note: Without allowlistId, API will auto-generate accounts in response');
67+
68+
const startVoting = new Date();
69+
const endVoting = new Date(startVoting.getTime() + 1000 * 60 * 11); // 11 minutes later
70+
const maxVoter = 10;
71+
72+
const createRoundData = await maciClient.saasCreateAmaciRound({
73+
title: 'Pre-Add-New-Key Test Round',
74+
description: 'Testing pre-add-new-key with auto pre-deactivate',
75+
link: 'https://test.com',
76+
startVoting: startVoting.toISOString(),
77+
endVoting: endVoting.toISOString(),
78+
operator: 'dora149n5yhzgk5gex0eqmnnpnsxh6ys4exg5xyqjzm',
79+
maxVoter: maxVoter,
80+
voteOptionMap: ['Option A', 'Option B', 'Option C', 'Option D', 'Option E'],
81+
circuitType: MaciCircuitType.IP1V,
82+
voiceCreditAmount: 10
83+
// Without allowlistId, API will auto-generate pre-deactivate data
84+
});
85+
86+
const contractAddress = createRoundData.contractAddress;
87+
if (!contractAddress) {
88+
throw new Error('Contract address not returned');
89+
}
90+
91+
console.log('✓ Round created successfully!');
92+
console.log(' Contract Address:', contractAddress);
93+
console.log(' Status:', createRoundData.status);
94+
console.log(' TX Hash:', createRoundData.txHash);
95+
96+
// Verify accounts are returned
97+
if (!createRoundData.accounts) {
98+
throw new Error('Accounts not returned in response');
99+
}
100+
101+
const accountsData = createRoundData.accounts;
102+
console.log(' Accounts count:', accountsData.length);
103+
104+
// Wait for transaction confirmation
105+
console.log('\nWaiting 6 seconds to ensure transaction confirmation...');
106+
await new Promise((resolve) => setTimeout(resolve, 6000));
107+
108+
// Display first 3 accounts
109+
if (accountsData.length > 0) {
110+
console.log('\nFirst 3 accounts returned:');
111+
accountsData.slice(0, 3).forEach((account, index) => {
112+
console.log(`\n Account ${index + 1}:`);
113+
console.log(` Pubkey: ${account.pubkey}`);
114+
console.log(` Secret Key: ${account.secretKey.substring(0, 20)}...`);
115+
});
116+
}
117+
118+
// ==================== 3. Query Pre-Deactivate Data ====================
119+
console.log('\n[3/5] Querying Pre-Deactivate data from dedicated API');
120+
121+
// Use public API (no API key required) - create a temporary VoterClient
122+
const publicVoterClient = new VoterClient({
123+
network: network,
124+
saasApiEndpoint: API_BASE_URL
125+
});
126+
127+
const deactivateData = await publicVoterClient.saasGetPreDeactivate(contractAddress);
128+
129+
console.log('✓ Deactivate data queried successfully!');
130+
console.log(' Root:', deactivateData.root);
131+
console.log(' Coordinator:', deactivateData.coordinator);
132+
console.log(' Leaves count:', deactivateData.leaves.length);
133+
console.log(' Deactivates count:', deactivateData.deactivates.length);
134+
135+
// ==================== 4. Test Pre-Add-New-Key ====================
136+
console.log('\n[4/5] Testing Pre-Add-New-Key');
137+
138+
// Use the first auto-generated account for Pre-Add-New-Key
139+
if (accountsData.length === 0) {
140+
throw new Error('No available account found for Pre-Add-New-Key test');
141+
}
142+
143+
const testAccount = accountsData[0];
144+
console.log('Using first account:', testAccount.pubkey);
145+
146+
// Create voter client using account's secretKey
147+
const voterClient = new VoterClient({
148+
network: network,
149+
secretKey: testAccount.secretKey,
150+
saasApiEndpoint: API_BASE_URL
151+
});
152+
153+
const circuitPower = '2-1-1-5';
154+
console.log('Executing Pre-Add-New-Key (with auto payload generation)...');
155+
156+
// Get coordinator pubkey from deactivateData
157+
const coordinatorPubkey = BigInt(deactivateData.coordinator);
158+
159+
try {
160+
// Use saasPreCreateNewAccount: builds payload + submits pre-add-new-key
161+
// const derivePathParams = { accountIndex: 2 };
162+
const { account, result } = await voterClient.saasPreCreateNewAccount({
163+
contractAddress: contractAddress,
164+
stateTreeDepth: 2,
165+
coordinatorPubkey: coordinatorPubkey,
166+
deactivates: deactivateData.deactivates as any,
167+
wasmFile: path.join(process.cwd(), `add-new-key_v3/${circuitPower}/addKey.wasm`),
168+
zkeyFile: path.join(process.cwd(), `add-new-key_v3/${circuitPower}/addKey.zkey`)
169+
// derivePathParams
170+
});
171+
172+
console.log('✓ Pre-Add-New-Key succeeded!', result);
173+
console.log('✓ Pre-Add-New-Key account:', account.getPubkey().toPackedData());
174+
175+
// Wait for transaction confirmation
176+
console.log('\nWaiting 6 seconds to ensure Pre-Add-New-Key transaction confirmation...');
177+
await new Promise((resolve) => setTimeout(resolve, 6000));
178+
179+
// ==================== 5. Test Voting ====================
180+
console.log('\n[5/5] Testing Voting (with auto payload generation)');
181+
182+
// Use saasVote: builds payload + submits vote
183+
const voteResult = await account.saasVote({
184+
contractAddress,
185+
operatorPubkey:
186+
10721319678265866063861912417916780787229942812531198850410477756757845824096n,
187+
selectedOptions: [
188+
{ idx: 0, vc: 1 },
189+
{ idx: 2, vc: 1 },
190+
{ idx: 3, vc: 1 }
191+
]
192+
});
193+
194+
console.log('✓ Voting succeeded!', voteResult);
195+
} catch (error) {
196+
console.log('⚠ Failed:', error);
197+
if (error instanceof Error) {
198+
console.log(' Error message:', error.message);
199+
}
200+
}
201+
202+
// ==================== Completed ====================
203+
console.log('\n' + '='.repeat(80));
204+
console.log('Test completed!');
205+
console.log('='.repeat(80));
206+
console.log('\nSummary:');
207+
console.log('✓ Successfully created Tenant and API Key');
208+
console.log('✓ Successfully created AMACI Round (Auto Pre-Deactivate Mode)');
209+
console.log('✓ Accounts returned directly in create round response');
210+
console.log('✓ Pre-Deactivate data queried from dedicated API');
211+
console.log('✓ Completed Pre-Add-New-Key using auto-generated account');
212+
console.log('✓ Tested voting functionality');
213+
console.log('\nContract Address:', contractAddress);
214+
console.log('Total Accounts Generated:', accountsData.length);
215+
console.log('\nClient Features Demonstrated:');
216+
console.log(' - MaciClient:');
217+
console.log(' • Admin API via getSaasApiClient(): createTenant, createApiKey');
218+
console.log(' • Round API via saasCreateAmaciRound()');
219+
console.log(' - VoterClient:');
220+
console.log(' • Pre-Deactivate API via saasGetPreDeactivate()');
221+
console.log(' • Integrated Methods (auto payload + submit):');
222+
console.log(' - saasPreCreateNewAccount(): builds payload + submits pre-add-new-key');
223+
console.log(' - saasVote(): builds payload + submits vote');
224+
}
225+
226+
main().catch((error) => {
227+
console.error('\n❌ Test failed:', error);
228+
if (error instanceof Error) {
229+
console.error('Error message:', error.message);
230+
console.error('Stack trace:', error.stack);
231+
}
232+
process.exit(1);
233+
});
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
* MaciApiClient Usage Example
3+
*
4+
* This script demonstrates how to use MaciApiClient to call MACI API
5+
*/
6+
7+
import { MaciApiClient } from '../src/libs/api';
8+
9+
async function main() {
10+
// 1. Create API client instance
11+
const apiClient = new MaciApiClient({
12+
baseUrl: 'https://api.example.com', // Replace with actual API URL
13+
apiKey: 'your-api-key-here', // Replace with actual API Key
14+
timeout: 30000 // Optional: request timeout in milliseconds
15+
});
16+
17+
console.log('=== MaciApiClient Usage Example ===\n');
18+
19+
try {
20+
// 2. Health check
21+
console.log('1. Health check...');
22+
const health = await apiClient.health();
23+
console.log('Health status:', health);
24+
console.log();
25+
26+
// 3. Get usage
27+
console.log('2. Get usage...');
28+
const usage = await apiClient.getUsage({
29+
page: 1,
30+
pageSize: 10
31+
});
32+
console.log('Usage:', usage);
33+
console.log();
34+
35+
// 4. Create AMaci Round
36+
console.log('3. Create AMaci Round...');
37+
const roundResult = await apiClient.createAmaciRound({
38+
title: 'Test Round',
39+
description: 'This is a test round',
40+
link: 'https://example.com',
41+
startVoting: new Date().toISOString(),
42+
endVoting: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
43+
operator: 'dora1...',
44+
maxVoter: 100,
45+
voteOptionMap: ['Option 1', 'Option 2', 'Option 3'],
46+
voiceCreditAmount: 10,
47+
allowlistId: 'allowlist-id'
48+
});
49+
console.log('Round creation result:', roundResult);
50+
console.log();
51+
52+
// 5. User registration (Signup)
53+
console.log('4. User registration...');
54+
const signupResult = await apiClient.signup({
55+
pubkey: '0x1234...', // MACI public key
56+
contractAddress: 'contract-address',
57+
certificate: 'certificate-data',
58+
amount: '1'
59+
});
60+
console.log('Registration result:', signupResult);
61+
console.log();
62+
63+
// 6. Vote
64+
console.log('5. Vote...');
65+
const voteResult = await apiClient.vote({
66+
contractAddress: 'contract-address',
67+
payload: [
68+
{
69+
msg: ['1', '2', '3', '4', '5', '6', '7'], // 7 elements
70+
encPubkeys: ['pubkey1', 'pubkey2'] // 2 elements
71+
}
72+
]
73+
});
74+
console.log('Vote result:', voteResult);
75+
console.log();
76+
77+
// 7. Get allowlist list
78+
console.log('6. Get allowlist list...');
79+
const allowlists = await apiClient.getAllowlists({
80+
page: 1,
81+
limit: 10,
82+
activeOnly: true
83+
});
84+
console.log('Allowlist list:', allowlists);
85+
console.log();
86+
87+
// 8. Create allowlist
88+
console.log('7. Create allowlist...');
89+
const newAllowlist = await apiClient.createAllowlist({
90+
name: 'Test Allowlist',
91+
description: 'This is a test allowlist',
92+
pubkeys: ['0x1234...', '0x5678...']
93+
});
94+
console.log('New allowlist:', newAllowlist);
95+
console.log();
96+
97+
// 9. Get allowlist details
98+
console.log('8. Get allowlist details...');
99+
const allowlistDetail = await apiClient.getAllowlistDetail({
100+
id: newAllowlist.id
101+
});
102+
console.log('Allowlist details:', allowlistDetail);
103+
console.log();
104+
105+
// 10. Update allowlist
106+
console.log('9. Update allowlist...');
107+
const updatedAllowlist = await apiClient.updateAllowlist(
108+
{ id: newAllowlist.id },
109+
{
110+
name: 'Updated Allowlist',
111+
pubkeysToAdd: ['0x9abc...'],
112+
isActive: true
113+
}
114+
);
115+
console.log('Updated allowlist:', updatedAllowlist);
116+
console.log();
117+
118+
// 11. Get Round allowlist snapshot
119+
console.log('10. Get Round allowlist snapshot...');
120+
const snapshot = await apiClient.getRoundAllowlistSnapshot({
121+
contractAddress: 'contract-address'
122+
});
123+
console.log('Allowlist snapshot:', snapshot);
124+
console.log();
125+
126+
// 12. Request certificate
127+
console.log('11. Request certificate...');
128+
const certificate = await apiClient.requestCertificate({
129+
pubkey: '0x1234...',
130+
contractAddress: 'contract-address',
131+
signature: 'signature-data'
132+
});
133+
console.log('Certificate:', certificate);
134+
console.log();
135+
136+
// 13. Get Pre-deactivate data
137+
console.log('12. Get Pre-deactivate data...');
138+
const preDeactivate = await apiClient.getPreDeactivate({
139+
contractAddress: 'contract-address'
140+
});
141+
console.log('Pre-deactivate data:', preDeactivate);
142+
console.log();
143+
144+
// 14. Dynamically update configuration
145+
console.log('13. Dynamically update API Key and Base URL...');
146+
apiClient.setApiKey('new-api-key');
147+
apiClient.setBaseUrl('https://new-api.example.com');
148+
console.log('Configuration updated');
149+
console.log();
150+
} catch (error: any) {
151+
console.error('Error:', error.message);
152+
if (error.statusCode) {
153+
console.error('Status code:', error.statusCode);
154+
}
155+
}
156+
157+
console.log('=== Example completed ===');
158+
}
159+
160+
// Run example
161+
main().catch(console.error);

0 commit comments

Comments
 (0)