solvrn-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +960 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# solvrn-sdk
|
|
2
|
+
|
|
3
|
+
**Solvrn SDK** — Privacy SDK for Solana Governance
|
|
4
|
+
|
|
5
|
+
A TypeScript SDK for building private, encrypted voting applications on Solana using zero-knowledge proofs and confidential computing.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install solvrn-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { SolvrnClient } from 'solvrn-sdk';
|
|
17
|
+
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
|
|
18
|
+
import { Connection, Keypair } from '@solana/web3.js';
|
|
19
|
+
|
|
20
|
+
// Initialize the client
|
|
21
|
+
const solvrn = new SolvrnClient(
|
|
22
|
+
'http://localhost:3000', // Relayer URL
|
|
23
|
+
'DBCtofDd6f3U342nwz768FXbH6K5QyGxZUGLjFeb9JTS', // Arcium Program ID (optional)
|
|
24
|
+
'AL2krCFs4WuzAdjZJbiYJCUnjJ2gmzQdtQuh7YJ3LXcv' // Solvrn Program ID (optional)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Initialize ZK backend (must be called once)
|
|
28
|
+
await solvrn.init(circuitJson); // Pass compiled Noir circuit JSON
|
|
29
|
+
|
|
30
|
+
// Create a proposal
|
|
31
|
+
const { proposalId, txid } = await solvrn.createProposal(
|
|
32
|
+
provider, // AnchorProvider
|
|
33
|
+
authority, // PublicKey
|
|
34
|
+
votingMint, // Token mint address
|
|
35
|
+
metadata, // { title, desc, duration }
|
|
36
|
+
gasBufferSol // SOL amount for gas (e.g., 0.05)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Cast a vote (full flow)
|
|
40
|
+
const result = await solvrn.castVote(
|
|
41
|
+
provider, // AnchorProvider
|
|
42
|
+
walletPubkey, // string (base58)
|
|
43
|
+
proposalId, // number
|
|
44
|
+
choice // 0 = NO, 1 = YES
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
console.log('Vote submitted:', result.tx);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API
|
|
51
|
+
|
|
52
|
+
### `SolvrnClient`
|
|
53
|
+
|
|
54
|
+
#### Constructor
|
|
55
|
+
```typescript
|
|
56
|
+
constructor(
|
|
57
|
+
relayerUrl: string,
|
|
58
|
+
arciumProgramId?: string,
|
|
59
|
+
programId?: string
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Methods
|
|
64
|
+
|
|
65
|
+
- **`init(circuitJson: any): Promise<void>`** — Initialize ZK backend with Noir circuit
|
|
66
|
+
- **`createProposal(provider, authority, votingMint, metadata, gasBufferSol, proposalIdOverride?): Promise<{proposalId, txid}>`** — Create voting proposal and snapshot
|
|
67
|
+
- **`castVote(provider, walletPubkey, proposalId, choice): Promise<{success, tx, error}>`** — Full voting flow (proof + encryption + submission)
|
|
68
|
+
- **`api.getNextProposalId(): Promise<{nextId, success}>`** — Get next available proposal ID
|
|
69
|
+
- **`api.initializeSnapshot(proposalId, votingMint, metadata, creator): Promise<SnapshotResponse>`** — Initialize voting snapshot
|
|
70
|
+
- **`api.getProposal(proposalId): Promise<ProposalResponse>`** — Get proposal data
|
|
71
|
+
- **`api.getProof(proposalId, userPubkey): Promise<ProofResponse>`** — Get Merkle proof for voter
|
|
72
|
+
- **`api.submitVote(proposalId, nullifier, encryptedBallot): Promise<SubmitVoteResponse>`** — Submit encrypted vote
|
|
73
|
+
- **`api.getVoteCounts(proposalId): Promise<VoteCountsResponse>`** — Get vote counts
|
|
74
|
+
- **`api.proveTally(proposalId, yesVotes, noVotes, threshold, quorum): Promise<TallyProofResponse>`** — Generate ZK tally proof
|
|
75
|
+
|
|
76
|
+
### Sub-modules
|
|
77
|
+
|
|
78
|
+
- **`prover.generateVoteProof(secret, proofData, proposalId)`** — Generate ZK proof of eligibility
|
|
79
|
+
- **`encryption.encryptVote(provider, voteChoice, votingWeight)`** — Encrypt vote using Arcium MPC
|
|
80
|
+
|
|
81
|
+
## Requirements
|
|
82
|
+
|
|
83
|
+
- Relayer running at `relayerUrl` (see [Solvrn Relayer](https://github.com/solvrn-labs/solvrn-relayer))
|
|
84
|
+
- Compiled Noir circuit JSON (from `frontend/circuit/target/circuit.json`)
|
|
85
|
+
- Solana wallet connected via AnchorProvider
|
|
86
|
+
|
|
87
|
+
## How It Works
|
|
88
|
+
|
|
89
|
+
1. **Snapshot** — Relayer fetches token holders and builds Merkle tree
|
|
90
|
+
2. **Proof** — SDK gets Merkle proof + voter secret from relayer
|
|
91
|
+
3. **ZK Proof** — SDK generates zero-knowledge proof proving eligibility
|
|
92
|
+
4. **Encryption** — Vote choice encrypted using Arcium MPC
|
|
93
|
+
5. **Submission** — Encrypted vote + ZK proof sent to relayer → Solana
|
|
94
|
+
6. **Tally** — Relayer decrypts votes and generates ZK tally proof
|
|
95
|
+
7. **Verification** — Tally proof verifies quorum & majority thresholds
|
|
96
|
+
|
|
97
|
+
## What's Real
|
|
98
|
+
|
|
99
|
+
✅ **Real ZK Proofs** - Uses Barretenberg WASM + Noir circuits
|
|
100
|
+
✅ **Real Encryption** - Arcium MPC encryption
|
|
101
|
+
✅ **Real On-Chain** - Actual Solana transactions
|
|
102
|
+
✅ **Real Merkle Trees** - Built from actual token holders
|
|
103
|
+
✅ **Real Nullifiers** - Prevents double voting
|
|
104
|
+
✅ **Real Vote Storage** - Encrypted votes stored on-chain
|
|
105
|
+
|
|
106
|
+
## Current Limitations
|
|
107
|
+
|
|
108
|
+
⚠️ **Vote Decryption** - Currently simulated (relayer-side). Real Arcium MPC decryption coming soon.
|
|
109
|
+
⚠️ **Vote Count Breakdown** - `getVoteCounts()` returns simulated yes/no breakdown. The total vote count (`realVoteCount`) is accurate.
|
|
110
|
+
✅ **Tally Proofs** - Work perfectly with user-provided vote counts. ZK proofs are 100% real.
|
|
111
|
+
|
|
112
|
+
## Important: Using Tally in Production
|
|
113
|
+
|
|
114
|
+
**⚠️ CRITICAL:** `proveTally()` generates REAL ZK proofs, but if you use `getVoteCounts()` for vote counts, you'll be proving SIMULATED data.
|
|
115
|
+
|
|
116
|
+
### ✅ Full Flow (With Limitation):
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// 1. Create proposal ✅ REAL
|
|
120
|
+
const { proposalId } = await solvrn.createProposal(...);
|
|
121
|
+
|
|
122
|
+
// 2. Cast vote ✅ REAL
|
|
123
|
+
await solvrn.castVote(provider, wallet, proposalId, 1);
|
|
124
|
+
|
|
125
|
+
// 3. Get vote counts ⚠️ PARTIALLY REAL
|
|
126
|
+
const counts = await solvrn.api.getVoteCounts(proposalId);
|
|
127
|
+
// counts.realVoteCount ✅ - Accurate total
|
|
128
|
+
// counts.yesVotes/noVotes ⚠️ - Simulated breakdown
|
|
129
|
+
|
|
130
|
+
// 4. Prove tally ⚠️ REAL PROOF, BUT PROVING SIMULATED DATA
|
|
131
|
+
const tallyProof = await solvrn.api.proveTally(
|
|
132
|
+
proposalId,
|
|
133
|
+
counts.yesVotes, // ⚠️ Simulated!
|
|
134
|
+
counts.noVotes, // ⚠️ Simulated!
|
|
135
|
+
51, 10
|
|
136
|
+
);
|
|
137
|
+
// Returns: Real ZK proof ✅
|
|
138
|
+
// But proving: Simulated vote counts ⚠️
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### ✅ Recommended: Provide Your Own Vote Counts
|
|
142
|
+
|
|
143
|
+
For production, decrypt votes yourself or wait for relayer decryption:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Get accurate total vote count
|
|
147
|
+
const counts = await solvrn.api.getVoteCounts(proposalId);
|
|
148
|
+
console.log(`Total votes: ${counts.realVoteCount}`); // ✅ Accurate
|
|
149
|
+
|
|
150
|
+
// Provide your own yes/no breakdown (from your own decryption)
|
|
151
|
+
const tallyProof = await solvrn.api.proveTally(
|
|
152
|
+
proposalId,
|
|
153
|
+
yourDecryptedYesVotes, // ✅ Your own decryption
|
|
154
|
+
yourDecryptedNoVotes, // ✅ Your own decryption
|
|
155
|
+
51, 10
|
|
156
|
+
);
|
|
157
|
+
// Returns: Real ZK proof of REAL data ✅
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**What's Real:**
|
|
161
|
+
- ✅ Total vote count (`realVoteCount`) - Accurate
|
|
162
|
+
- ✅ ZK proof generation - 100% real
|
|
163
|
+
- ✅ Vote encryption - 100% real
|
|
164
|
+
- ✅ On-chain storage - 100% real
|
|
165
|
+
- ✅ Tally proof generation - 100% real
|
|
166
|
+
|
|
167
|
+
**What's Simulated:**
|
|
168
|
+
- ⚠️ Yes/No breakdown from `getVoteCounts()` - Until Arcium MPC decryption is implemented
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
ISC
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as _aztec_bb_js from '@aztec/bb.js';
|
|
2
|
+
import { Buffer } from 'buffer';
|
|
3
|
+
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
4
|
+
import { PublicKey } from '@solana/web3.js';
|
|
5
|
+
|
|
6
|
+
interface ProofResponse {
|
|
7
|
+
proof: {
|
|
8
|
+
path: string[];
|
|
9
|
+
index: number;
|
|
10
|
+
root: string;
|
|
11
|
+
balance: string;
|
|
12
|
+
weight: string;
|
|
13
|
+
secret: string;
|
|
14
|
+
leaf: string;
|
|
15
|
+
};
|
|
16
|
+
success: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
interface ProposalMetadata {
|
|
20
|
+
title: string;
|
|
21
|
+
desc: string;
|
|
22
|
+
duration: number;
|
|
23
|
+
}
|
|
24
|
+
declare class SolvrnApi {
|
|
25
|
+
private baseUrl;
|
|
26
|
+
constructor(baseUrl: string);
|
|
27
|
+
initializeSnapshot(proposalId: number, votingMint: string, metadata?: ProposalMetadata, creator?: string): Promise<any>;
|
|
28
|
+
getNextProposalId(): Promise<{
|
|
29
|
+
nextId: number;
|
|
30
|
+
success: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
getProposal(proposalId: number): Promise<any>;
|
|
33
|
+
getProof(proposalId: number, userPubkey: string): Promise<ProofResponse>;
|
|
34
|
+
submitVote(proposalId: number, nullifierHex: string, encryptedBallot: {
|
|
35
|
+
ciphertext: Uint8Array;
|
|
36
|
+
public_key: number[];
|
|
37
|
+
nonce: number[];
|
|
38
|
+
}): Promise<any>;
|
|
39
|
+
proveTally(proposalId: number, yesVotes: number, noVotes: number, threshold: number, quorum: number): Promise<any>;
|
|
40
|
+
getVoteCounts(proposalId: number): Promise<any>;
|
|
41
|
+
private post;
|
|
42
|
+
private get;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
declare class SolvrnProver {
|
|
46
|
+
private backend;
|
|
47
|
+
private noir;
|
|
48
|
+
init(circuitJson: any): Promise<void>;
|
|
49
|
+
generateVoteProof(secret: string, proofRes: any, proposalId: number): Promise<_aztec_bb_js.ProofData>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare class SolvrnEncryption {
|
|
53
|
+
private programId;
|
|
54
|
+
constructor(programId?: string);
|
|
55
|
+
encryptVote(provider: AnchorProvider, voteChoice: number, votingWeight: number): Promise<{
|
|
56
|
+
ciphertext: Buffer<ArrayBuffer>;
|
|
57
|
+
nonce: number[];
|
|
58
|
+
public_key: number[];
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
declare class SolvrnClient {
|
|
63
|
+
api: SolvrnApi;
|
|
64
|
+
prover: SolvrnProver;
|
|
65
|
+
encryption: SolvrnEncryption;
|
|
66
|
+
constructor(relayerUrl: string, arciumProgramId?: string, programId?: string);
|
|
67
|
+
init(circuitJson: any): Promise<void>;
|
|
68
|
+
createProposal(provider: AnchorProvider, authorityPubkey: PublicKey, votingMint: string, metadata: ProposalMetadata, gasBufferSol: number, proposalIdOverride?: number): Promise<{
|
|
69
|
+
proposalId: number;
|
|
70
|
+
txid: string;
|
|
71
|
+
}>;
|
|
72
|
+
castVote(provider: AnchorProvider, walletPubkey: string, proposalId: number, choice: number): Promise<{
|
|
73
|
+
success: any;
|
|
74
|
+
tx: any;
|
|
75
|
+
error: any;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { SolvrnClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
global.Buffer = Buffer;
|
|
3
|
+
|
|
4
|
+
// src/utils.ts
|
|
5
|
+
import { PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
|
6
|
+
import { getAssociatedTokenAddress, getAccount } from "@solana/spl-token";
|
|
7
|
+
var SYSTEM_PROGRAM_ID = new PublicKey("11111111111111111111111111111111");
|
|
8
|
+
var LEGACY_TOKEN_PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
9
|
+
var hexToBytes = (hex) => {
|
|
10
|
+
let cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
11
|
+
if (cleanHex.length % 2 !== 0) cleanHex = "0" + cleanHex;
|
|
12
|
+
const bytes = [];
|
|
13
|
+
for (let i = 0; i < cleanHex.length; i += 2) bytes.push(parseInt(cleanHex.substr(i, 2), 16));
|
|
14
|
+
return bytes;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/api.ts
|
|
18
|
+
var SolvrnApi = class {
|
|
19
|
+
constructor(baseUrl) {
|
|
20
|
+
this.baseUrl = baseUrl;
|
|
21
|
+
}
|
|
22
|
+
// --- UPDATED: Accepts Creator Argument ---
|
|
23
|
+
async initializeSnapshot(proposalId, votingMint, metadata, creator) {
|
|
24
|
+
if (process.env.NODE_ENV !== "development") {
|
|
25
|
+
}
|
|
26
|
+
return this.post("initialize-snapshot", {
|
|
27
|
+
proposalId,
|
|
28
|
+
votingMint,
|
|
29
|
+
metadata,
|
|
30
|
+
creator
|
|
31
|
+
// <--- THIS MUST BE SENT TO THE RELAYER
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async getNextProposalId() {
|
|
35
|
+
return this.get("next-proposal-id");
|
|
36
|
+
}
|
|
37
|
+
async getProposal(proposalId) {
|
|
38
|
+
return this.get(`proposal/${proposalId}`);
|
|
39
|
+
}
|
|
40
|
+
async getProof(proposalId, userPubkey) {
|
|
41
|
+
const res = await this.post("get-proof", { proposalId: proposalId.toString(), userPubkey });
|
|
42
|
+
if (!res.success) throw new Error(res.error || "Failed to fetch merkle proof");
|
|
43
|
+
return res;
|
|
44
|
+
}
|
|
45
|
+
async submitVote(proposalId, nullifierHex, encryptedBallot) {
|
|
46
|
+
return this.post("relay-vote", {
|
|
47
|
+
nullifier: hexToBytes(nullifierHex),
|
|
48
|
+
ciphertext: Array.from(encryptedBallot.ciphertext),
|
|
49
|
+
pubkey: encryptedBallot.public_key,
|
|
50
|
+
nonce: encryptedBallot.nonce,
|
|
51
|
+
proposalId
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async proveTally(proposalId, yesVotes, noVotes, threshold, quorum) {
|
|
55
|
+
return this.post("prove-tally", { proposalId, yesVotes, noVotes, threshold, quorum });
|
|
56
|
+
}
|
|
57
|
+
async getVoteCounts(proposalId) {
|
|
58
|
+
return this.get(`vote-counts/${proposalId}`);
|
|
59
|
+
}
|
|
60
|
+
async post(endpoint, body) {
|
|
61
|
+
const allowedEndpoints = [
|
|
62
|
+
"initialize-snapshot",
|
|
63
|
+
"get-proof",
|
|
64
|
+
"relay-vote",
|
|
65
|
+
"prove-tally"
|
|
66
|
+
];
|
|
67
|
+
if (!allowedEndpoints.includes(endpoint)) {
|
|
68
|
+
throw new Error(`Endpoint '${endpoint}' is not accessible through SDK. Use direct API calls for admin functionality.`);
|
|
69
|
+
}
|
|
70
|
+
const res = await fetch(`${this.baseUrl}/${endpoint}`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "Content-Type": "application/json" },
|
|
73
|
+
body: JSON.stringify(body)
|
|
74
|
+
});
|
|
75
|
+
return await res.json();
|
|
76
|
+
}
|
|
77
|
+
async get(endpoint) {
|
|
78
|
+
const allowedEndpoints = [
|
|
79
|
+
"next-proposal-id",
|
|
80
|
+
"proposal",
|
|
81
|
+
"vote-counts"
|
|
82
|
+
];
|
|
83
|
+
const isAllowed = allowedEndpoints.some(
|
|
84
|
+
(allowed) => endpoint === allowed || endpoint.startsWith(`${allowed}/`)
|
|
85
|
+
);
|
|
86
|
+
if (!isAllowed) {
|
|
87
|
+
throw new Error(`Endpoint '${endpoint}' is not accessible through SDK. Use direct API calls for admin functionality.`);
|
|
88
|
+
}
|
|
89
|
+
const res = await fetch(`${this.baseUrl}/${endpoint}`);
|
|
90
|
+
return await res.json();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/prover.ts
|
|
95
|
+
import { Barretenberg, UltraHonkBackend } from "@aztec/bb.js";
|
|
96
|
+
import { Noir } from "@noir-lang/noir_js";
|
|
97
|
+
var SolvrnProver = class {
|
|
98
|
+
constructor() {
|
|
99
|
+
this.backend = null;
|
|
100
|
+
this.noir = null;
|
|
101
|
+
}
|
|
102
|
+
async init(circuitJson) {
|
|
103
|
+
const bb = await Barretenberg.new();
|
|
104
|
+
this.backend = new UltraHonkBackend(circuitJson.bytecode, bb);
|
|
105
|
+
this.noir = new Noir(circuitJson);
|
|
106
|
+
}
|
|
107
|
+
async generateVoteProof(secret, proofRes, proposalId) {
|
|
108
|
+
if (!this.noir || !this.backend) throw new Error("Prover not initialized. Call init() first.");
|
|
109
|
+
const weightVal = BigInt(proofRes.proof.weight);
|
|
110
|
+
const balanceVal = BigInt(proofRes.proof.balance);
|
|
111
|
+
const inputs = {
|
|
112
|
+
user_secret: "0x" + secret.replace("0x", "").padStart(64, "0"),
|
|
113
|
+
balance: "0x" + balanceVal.toString(16).padStart(64, "0"),
|
|
114
|
+
weight: "0x" + weightVal.toString(16).padStart(64, "0"),
|
|
115
|
+
merkle_path: proofRes.proof.path,
|
|
116
|
+
// Pass as-is, no padding
|
|
117
|
+
merkle_index: Number(proofRes.proof.index),
|
|
118
|
+
merkle_root: proofRes.proof.root,
|
|
119
|
+
// Pass as-is, no padding
|
|
120
|
+
proposal_id: "0x" + BigInt(proposalId).toString(16).padStart(64, "0")
|
|
121
|
+
};
|
|
122
|
+
console.log("Noir inputs:", JSON.stringify(inputs, null, 2));
|
|
123
|
+
const { witness } = await this.noir.execute(inputs);
|
|
124
|
+
const proof = await this.backend.generateProof(witness);
|
|
125
|
+
return proof;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/encryption.ts
|
|
130
|
+
import { getMXEPublicKey, RescueCipher, x25519 } from "@arcium-hq/client";
|
|
131
|
+
import { PublicKey as PublicKey2 } from "@solana/web3.js";
|
|
132
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
133
|
+
var SolvrnEncryption = class {
|
|
134
|
+
constructor(programId = process.env.ARCIUM_PROGRAM_ID || "DBCtofDd6f3U342nwz768FXbH6K5QyGxZUGLjFeb9JTS") {
|
|
135
|
+
this.programId = new PublicKey2(programId);
|
|
136
|
+
}
|
|
137
|
+
async encryptVote(provider, voteChoice, votingWeight) {
|
|
138
|
+
let mxePublicKey = null;
|
|
139
|
+
let attempts = 0;
|
|
140
|
+
while (!mxePublicKey && attempts < 20) {
|
|
141
|
+
try {
|
|
142
|
+
mxePublicKey = await getMXEPublicKey(provider, this.programId);
|
|
143
|
+
if (mxePublicKey) break;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
}
|
|
146
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
147
|
+
attempts++;
|
|
148
|
+
}
|
|
149
|
+
if (!mxePublicKey) throw new Error("Arcium MXE Public Key not found.");
|
|
150
|
+
const ephemeralSecret = x25519.utils.randomSecretKey();
|
|
151
|
+
const ephemeralPublic = x25519.getPublicKey(ephemeralSecret);
|
|
152
|
+
const sharedSecret = x25519.getSharedSecret(ephemeralSecret, mxePublicKey);
|
|
153
|
+
const cipher = new RescueCipher(sharedSecret);
|
|
154
|
+
const inputs = [BigInt(votingWeight), BigInt(voteChoice)];
|
|
155
|
+
const nonce = x25519.utils.randomSecretKey().slice(0, 16);
|
|
156
|
+
const encrypted = cipher.encrypt(inputs, nonce);
|
|
157
|
+
return {
|
|
158
|
+
ciphertext: Buffer2.from(new Uint8Array(encrypted.flat().map((n) => Number(n)))),
|
|
159
|
+
nonce: Array.from(nonce),
|
|
160
|
+
public_key: Array.from(ephemeralPublic)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// src/index.ts
|
|
166
|
+
import { Program } from "@coral-xyz/anchor";
|
|
167
|
+
import BN from "bn.js";
|
|
168
|
+
import { PublicKey as PublicKey3, SystemProgram, LAMPORTS_PER_SOL as LAMPORTS_PER_SOL2 } from "@solana/web3.js";
|
|
169
|
+
|
|
170
|
+
// src/idl.json
|
|
171
|
+
var idl_default = {
|
|
172
|
+
address: "AL2krCFs4WuzAdjZJbiYJCUnjJ2gmzQdtQuh7YJ3LXcv",
|
|
173
|
+
metadata: {
|
|
174
|
+
name: "solvote_chain",
|
|
175
|
+
version: "0.1.0",
|
|
176
|
+
spec: "0.1.0",
|
|
177
|
+
description: "Created with Anchor"
|
|
178
|
+
},
|
|
179
|
+
instructions: [
|
|
180
|
+
{
|
|
181
|
+
name: "finalize_proposal",
|
|
182
|
+
discriminator: [
|
|
183
|
+
23,
|
|
184
|
+
68,
|
|
185
|
+
51,
|
|
186
|
+
167,
|
|
187
|
+
109,
|
|
188
|
+
173,
|
|
189
|
+
187,
|
|
190
|
+
164
|
|
191
|
+
],
|
|
192
|
+
accounts: [
|
|
193
|
+
{
|
|
194
|
+
name: "proposal",
|
|
195
|
+
writable: true,
|
|
196
|
+
pda: {
|
|
197
|
+
seeds: [
|
|
198
|
+
{
|
|
199
|
+
kind: "const",
|
|
200
|
+
value: [
|
|
201
|
+
115,
|
|
202
|
+
118,
|
|
203
|
+
114,
|
|
204
|
+
110,
|
|
205
|
+
95,
|
|
206
|
+
118,
|
|
207
|
+
53
|
|
208
|
+
]
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
kind: "account",
|
|
212
|
+
path: "proposal.proposal_id",
|
|
213
|
+
account: "Proposal"
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "proposal_token_account",
|
|
220
|
+
writable: true,
|
|
221
|
+
pda: {
|
|
222
|
+
seeds: [
|
|
223
|
+
{
|
|
224
|
+
kind: "account",
|
|
225
|
+
path: "proposal"
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
kind: "const",
|
|
229
|
+
value: [
|
|
230
|
+
6,
|
|
231
|
+
221,
|
|
232
|
+
246,
|
|
233
|
+
225,
|
|
234
|
+
215,
|
|
235
|
+
101,
|
|
236
|
+
161,
|
|
237
|
+
147,
|
|
238
|
+
217,
|
|
239
|
+
203,
|
|
240
|
+
225,
|
|
241
|
+
70,
|
|
242
|
+
206,
|
|
243
|
+
235,
|
|
244
|
+
121,
|
|
245
|
+
172,
|
|
246
|
+
28,
|
|
247
|
+
180,
|
|
248
|
+
133,
|
|
249
|
+
237,
|
|
250
|
+
95,
|
|
251
|
+
91,
|
|
252
|
+
55,
|
|
253
|
+
145,
|
|
254
|
+
58,
|
|
255
|
+
140,
|
|
256
|
+
245,
|
|
257
|
+
133,
|
|
258
|
+
126,
|
|
259
|
+
255,
|
|
260
|
+
0,
|
|
261
|
+
169
|
|
262
|
+
]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
kind: "account",
|
|
266
|
+
path: "treasury_mint"
|
|
267
|
+
}
|
|
268
|
+
],
|
|
269
|
+
program: {
|
|
270
|
+
kind: "const",
|
|
271
|
+
value: [
|
|
272
|
+
140,
|
|
273
|
+
151,
|
|
274
|
+
37,
|
|
275
|
+
143,
|
|
276
|
+
78,
|
|
277
|
+
36,
|
|
278
|
+
137,
|
|
279
|
+
241,
|
|
280
|
+
187,
|
|
281
|
+
61,
|
|
282
|
+
16,
|
|
283
|
+
41,
|
|
284
|
+
20,
|
|
285
|
+
142,
|
|
286
|
+
13,
|
|
287
|
+
131,
|
|
288
|
+
11,
|
|
289
|
+
90,
|
|
290
|
+
19,
|
|
291
|
+
153,
|
|
292
|
+
218,
|
|
293
|
+
255,
|
|
294
|
+
16,
|
|
295
|
+
132,
|
|
296
|
+
4,
|
|
297
|
+
142,
|
|
298
|
+
123,
|
|
299
|
+
216,
|
|
300
|
+
219,
|
|
301
|
+
233,
|
|
302
|
+
248,
|
|
303
|
+
89
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "target_token_account",
|
|
310
|
+
writable: true,
|
|
311
|
+
pda: {
|
|
312
|
+
seeds: [
|
|
313
|
+
{
|
|
314
|
+
kind: "account",
|
|
315
|
+
path: "target_wallet"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
kind: "const",
|
|
319
|
+
value: [
|
|
320
|
+
6,
|
|
321
|
+
221,
|
|
322
|
+
246,
|
|
323
|
+
225,
|
|
324
|
+
215,
|
|
325
|
+
101,
|
|
326
|
+
161,
|
|
327
|
+
147,
|
|
328
|
+
217,
|
|
329
|
+
203,
|
|
330
|
+
225,
|
|
331
|
+
70,
|
|
332
|
+
206,
|
|
333
|
+
235,
|
|
334
|
+
121,
|
|
335
|
+
172,
|
|
336
|
+
28,
|
|
337
|
+
180,
|
|
338
|
+
133,
|
|
339
|
+
237,
|
|
340
|
+
95,
|
|
341
|
+
91,
|
|
342
|
+
55,
|
|
343
|
+
145,
|
|
344
|
+
58,
|
|
345
|
+
140,
|
|
346
|
+
245,
|
|
347
|
+
133,
|
|
348
|
+
126,
|
|
349
|
+
255,
|
|
350
|
+
0,
|
|
351
|
+
169
|
|
352
|
+
]
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
kind: "account",
|
|
356
|
+
path: "treasury_mint"
|
|
357
|
+
}
|
|
358
|
+
],
|
|
359
|
+
program: {
|
|
360
|
+
kind: "const",
|
|
361
|
+
value: [
|
|
362
|
+
140,
|
|
363
|
+
151,
|
|
364
|
+
37,
|
|
365
|
+
143,
|
|
366
|
+
78,
|
|
367
|
+
36,
|
|
368
|
+
137,
|
|
369
|
+
241,
|
|
370
|
+
187,
|
|
371
|
+
61,
|
|
372
|
+
16,
|
|
373
|
+
41,
|
|
374
|
+
20,
|
|
375
|
+
142,
|
|
376
|
+
13,
|
|
377
|
+
131,
|
|
378
|
+
11,
|
|
379
|
+
90,
|
|
380
|
+
19,
|
|
381
|
+
153,
|
|
382
|
+
218,
|
|
383
|
+
255,
|
|
384
|
+
16,
|
|
385
|
+
132,
|
|
386
|
+
4,
|
|
387
|
+
142,
|
|
388
|
+
123,
|
|
389
|
+
216,
|
|
390
|
+
219,
|
|
391
|
+
233,
|
|
392
|
+
248,
|
|
393
|
+
89
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: "target_wallet",
|
|
400
|
+
relations: [
|
|
401
|
+
"proposal"
|
|
402
|
+
]
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "treasury_mint",
|
|
406
|
+
relations: [
|
|
407
|
+
"proposal"
|
|
408
|
+
]
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "authority",
|
|
412
|
+
signer: true,
|
|
413
|
+
relations: [
|
|
414
|
+
"proposal"
|
|
415
|
+
]
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: "token_program",
|
|
419
|
+
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
420
|
+
}
|
|
421
|
+
],
|
|
422
|
+
args: [
|
|
423
|
+
{
|
|
424
|
+
name: "proof",
|
|
425
|
+
type: "bytes"
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: "yes_votes",
|
|
429
|
+
type: "u64"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "no_votes",
|
|
433
|
+
type: "u64"
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: "threshold",
|
|
437
|
+
type: "u64"
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: "quorum",
|
|
441
|
+
type: "u64"
|
|
442
|
+
}
|
|
443
|
+
]
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: "initialize_proposal",
|
|
447
|
+
discriminator: [
|
|
448
|
+
50,
|
|
449
|
+
73,
|
|
450
|
+
156,
|
|
451
|
+
98,
|
|
452
|
+
129,
|
|
453
|
+
149,
|
|
454
|
+
21,
|
|
455
|
+
158
|
|
456
|
+
],
|
|
457
|
+
accounts: [
|
|
458
|
+
{
|
|
459
|
+
name: "proposal",
|
|
460
|
+
writable: true,
|
|
461
|
+
pda: {
|
|
462
|
+
seeds: [
|
|
463
|
+
{
|
|
464
|
+
kind: "const",
|
|
465
|
+
value: [
|
|
466
|
+
115,
|
|
467
|
+
118,
|
|
468
|
+
114,
|
|
469
|
+
110,
|
|
470
|
+
95,
|
|
471
|
+
118,
|
|
472
|
+
53
|
|
473
|
+
]
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
kind: "arg",
|
|
477
|
+
path: "proposal_id"
|
|
478
|
+
}
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: "proposal_token_account",
|
|
484
|
+
writable: true,
|
|
485
|
+
pda: {
|
|
486
|
+
seeds: [
|
|
487
|
+
{
|
|
488
|
+
kind: "account",
|
|
489
|
+
path: "proposal"
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
kind: "const",
|
|
493
|
+
value: [
|
|
494
|
+
6,
|
|
495
|
+
221,
|
|
496
|
+
246,
|
|
497
|
+
225,
|
|
498
|
+
215,
|
|
499
|
+
101,
|
|
500
|
+
161,
|
|
501
|
+
147,
|
|
502
|
+
217,
|
|
503
|
+
203,
|
|
504
|
+
225,
|
|
505
|
+
70,
|
|
506
|
+
206,
|
|
507
|
+
235,
|
|
508
|
+
121,
|
|
509
|
+
172,
|
|
510
|
+
28,
|
|
511
|
+
180,
|
|
512
|
+
133,
|
|
513
|
+
237,
|
|
514
|
+
95,
|
|
515
|
+
91,
|
|
516
|
+
55,
|
|
517
|
+
145,
|
|
518
|
+
58,
|
|
519
|
+
140,
|
|
520
|
+
245,
|
|
521
|
+
133,
|
|
522
|
+
126,
|
|
523
|
+
255,
|
|
524
|
+
0,
|
|
525
|
+
169
|
|
526
|
+
]
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
kind: "account",
|
|
530
|
+
path: "treasury_mint"
|
|
531
|
+
}
|
|
532
|
+
],
|
|
533
|
+
program: {
|
|
534
|
+
kind: "const",
|
|
535
|
+
value: [
|
|
536
|
+
140,
|
|
537
|
+
151,
|
|
538
|
+
37,
|
|
539
|
+
143,
|
|
540
|
+
78,
|
|
541
|
+
36,
|
|
542
|
+
137,
|
|
543
|
+
241,
|
|
544
|
+
187,
|
|
545
|
+
61,
|
|
546
|
+
16,
|
|
547
|
+
41,
|
|
548
|
+
20,
|
|
549
|
+
142,
|
|
550
|
+
13,
|
|
551
|
+
131,
|
|
552
|
+
11,
|
|
553
|
+
90,
|
|
554
|
+
19,
|
|
555
|
+
153,
|
|
556
|
+
218,
|
|
557
|
+
255,
|
|
558
|
+
16,
|
|
559
|
+
132,
|
|
560
|
+
4,
|
|
561
|
+
142,
|
|
562
|
+
123,
|
|
563
|
+
216,
|
|
564
|
+
219,
|
|
565
|
+
233,
|
|
566
|
+
248,
|
|
567
|
+
89
|
|
568
|
+
]
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: "voting_mint"
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
name: "treasury_mint"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: "target_wallet"
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: "authority",
|
|
583
|
+
writable: true,
|
|
584
|
+
signer: true
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: "token_program",
|
|
588
|
+
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "associated_token_program",
|
|
592
|
+
address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: "system_program",
|
|
596
|
+
address: "11111111111111111111111111111111"
|
|
597
|
+
}
|
|
598
|
+
],
|
|
599
|
+
args: [
|
|
600
|
+
{
|
|
601
|
+
name: "proposal_id",
|
|
602
|
+
type: "u64"
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
name: "merkle_root",
|
|
606
|
+
type: {
|
|
607
|
+
array: [
|
|
608
|
+
"u8",
|
|
609
|
+
32
|
|
610
|
+
]
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
name: "execution_amount",
|
|
615
|
+
type: "u64"
|
|
616
|
+
}
|
|
617
|
+
]
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: "submit_vote",
|
|
621
|
+
discriminator: [
|
|
622
|
+
115,
|
|
623
|
+
242,
|
|
624
|
+
100,
|
|
625
|
+
0,
|
|
626
|
+
49,
|
|
627
|
+
178,
|
|
628
|
+
242,
|
|
629
|
+
133
|
|
630
|
+
],
|
|
631
|
+
accounts: [
|
|
632
|
+
{
|
|
633
|
+
name: "proposal",
|
|
634
|
+
writable: true
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
name: "nullifier_account",
|
|
638
|
+
writable: true,
|
|
639
|
+
pda: {
|
|
640
|
+
seeds: [
|
|
641
|
+
{
|
|
642
|
+
kind: "const",
|
|
643
|
+
value: [
|
|
644
|
+
110,
|
|
645
|
+
117,
|
|
646
|
+
108,
|
|
647
|
+
108,
|
|
648
|
+
105,
|
|
649
|
+
102,
|
|
650
|
+
105,
|
|
651
|
+
101,
|
|
652
|
+
114
|
|
653
|
+
]
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
kind: "account",
|
|
657
|
+
path: "proposal"
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
kind: "arg",
|
|
661
|
+
path: "nullifier"
|
|
662
|
+
}
|
|
663
|
+
]
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: "relayer",
|
|
668
|
+
writable: true,
|
|
669
|
+
signer: true
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: "system_program",
|
|
673
|
+
address: "11111111111111111111111111111111"
|
|
674
|
+
}
|
|
675
|
+
],
|
|
676
|
+
args: [
|
|
677
|
+
{
|
|
678
|
+
name: "nullifier",
|
|
679
|
+
type: {
|
|
680
|
+
array: [
|
|
681
|
+
"u8",
|
|
682
|
+
32
|
|
683
|
+
]
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: "ciphertext",
|
|
688
|
+
type: "bytes"
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
name: "pubkey",
|
|
692
|
+
type: {
|
|
693
|
+
array: [
|
|
694
|
+
"u8",
|
|
695
|
+
32
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: "nonce",
|
|
701
|
+
type: "u128"
|
|
702
|
+
}
|
|
703
|
+
]
|
|
704
|
+
}
|
|
705
|
+
],
|
|
706
|
+
accounts: [
|
|
707
|
+
{
|
|
708
|
+
name: "NullifierAccount",
|
|
709
|
+
discriminator: [
|
|
710
|
+
250,
|
|
711
|
+
31,
|
|
712
|
+
238,
|
|
713
|
+
177,
|
|
714
|
+
213,
|
|
715
|
+
98,
|
|
716
|
+
48,
|
|
717
|
+
172
|
|
718
|
+
]
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
name: "Proposal",
|
|
722
|
+
discriminator: [
|
|
723
|
+
26,
|
|
724
|
+
94,
|
|
725
|
+
189,
|
|
726
|
+
187,
|
|
727
|
+
116,
|
|
728
|
+
136,
|
|
729
|
+
53,
|
|
730
|
+
33
|
|
731
|
+
]
|
|
732
|
+
}
|
|
733
|
+
],
|
|
734
|
+
errors: [
|
|
735
|
+
{
|
|
736
|
+
code: 6e3,
|
|
737
|
+
name: "AlreadyExecuted",
|
|
738
|
+
msg: "Already executed."
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
code: 6001,
|
|
742
|
+
name: "ProposalNotPassed",
|
|
743
|
+
msg: "Proposal did not pass (State)."
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
code: 6002,
|
|
747
|
+
name: "InvalidProof",
|
|
748
|
+
msg: "Invalid ZK Proof."
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
code: 6003,
|
|
752
|
+
name: "QuorumNotMet",
|
|
753
|
+
msg: "Quorum not met."
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
code: 6004,
|
|
757
|
+
name: "MajorityNotMet",
|
|
758
|
+
msg: "Majority threshold not met."
|
|
759
|
+
}
|
|
760
|
+
],
|
|
761
|
+
types: [
|
|
762
|
+
{
|
|
763
|
+
name: "NullifierAccount",
|
|
764
|
+
type: {
|
|
765
|
+
kind: "struct",
|
|
766
|
+
fields: [
|
|
767
|
+
{
|
|
768
|
+
name: "proposal",
|
|
769
|
+
type: "pubkey"
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
name: "nullifier",
|
|
773
|
+
type: {
|
|
774
|
+
array: [
|
|
775
|
+
"u8",
|
|
776
|
+
32
|
|
777
|
+
]
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: "ciphertext",
|
|
782
|
+
type: "bytes"
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
name: "pubkey",
|
|
786
|
+
type: {
|
|
787
|
+
array: [
|
|
788
|
+
"u8",
|
|
789
|
+
32
|
|
790
|
+
]
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
name: "nonce",
|
|
795
|
+
type: "u128"
|
|
796
|
+
}
|
|
797
|
+
]
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
name: "Proposal",
|
|
802
|
+
type: {
|
|
803
|
+
kind: "struct",
|
|
804
|
+
fields: [
|
|
805
|
+
{
|
|
806
|
+
name: "proposal_id",
|
|
807
|
+
type: "u64"
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
name: "vote_count",
|
|
811
|
+
type: "u64"
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
name: "authority",
|
|
815
|
+
type: "pubkey"
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
name: "merkle_root",
|
|
819
|
+
type: {
|
|
820
|
+
array: [
|
|
821
|
+
"u8",
|
|
822
|
+
32
|
|
823
|
+
]
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
name: "voting_mint",
|
|
828
|
+
type: "pubkey"
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
name: "treasury_mint",
|
|
832
|
+
type: "pubkey"
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
name: "execution_amount",
|
|
836
|
+
type: "u64"
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
name: "target_wallet",
|
|
840
|
+
type: "pubkey"
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: "tally_result",
|
|
844
|
+
type: "u8"
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
name: "is_executed",
|
|
848
|
+
type: "bool"
|
|
849
|
+
}
|
|
850
|
+
]
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
]
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
// src/index.ts
|
|
857
|
+
var PROGRAM_ID = null;
|
|
858
|
+
var getProgramId = () => {
|
|
859
|
+
if (PROGRAM_ID) return PROGRAM_ID;
|
|
860
|
+
const id = globalThis.process?.env?.PROGRAM_ID || process.env.PROGRAM_ID;
|
|
861
|
+
if (!id) {
|
|
862
|
+
throw new Error("PROGRAM_ID environment variable required. Please set process.env.PROGRAM_ID in your application or pass programId to SolvrnClient constructor.");
|
|
863
|
+
}
|
|
864
|
+
PROGRAM_ID = new PublicKey3(id);
|
|
865
|
+
return PROGRAM_ID;
|
|
866
|
+
};
|
|
867
|
+
var TOKEN_PROGRAM_ID = new PublicKey3("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
868
|
+
var ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey3("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
|
869
|
+
var SolvrnClient = class {
|
|
870
|
+
constructor(relayerUrl, arciumProgramId, programId) {
|
|
871
|
+
if (programId) {
|
|
872
|
+
globalThis.process = globalThis.process || {};
|
|
873
|
+
globalThis.process.env = globalThis.process.env || {};
|
|
874
|
+
globalThis.process.env.PROGRAM_ID = programId;
|
|
875
|
+
}
|
|
876
|
+
this.api = new SolvrnApi(relayerUrl);
|
|
877
|
+
this.prover = new SolvrnProver();
|
|
878
|
+
this.encryption = new SolvrnEncryption(arciumProgramId);
|
|
879
|
+
}
|
|
880
|
+
async init(circuitJson) {
|
|
881
|
+
await this.prover.init(circuitJson);
|
|
882
|
+
}
|
|
883
|
+
async createProposal(provider, authorityPubkey, votingMint, metadata, gasBufferSol, proposalIdOverride) {
|
|
884
|
+
let proposalId = proposalIdOverride;
|
|
885
|
+
if (!proposalId) {
|
|
886
|
+
const { nextId, success } = await this.api.getNextProposalId();
|
|
887
|
+
if (!success) throw new Error("Failed to get ID");
|
|
888
|
+
proposalId = nextId;
|
|
889
|
+
}
|
|
890
|
+
const snap = await this.api.initializeSnapshot(
|
|
891
|
+
proposalId,
|
|
892
|
+
votingMint,
|
|
893
|
+
metadata,
|
|
894
|
+
authorityPubkey.toBase58()
|
|
895
|
+
// <--- Force Creator into Snapshot
|
|
896
|
+
);
|
|
897
|
+
if (!snap.success) throw new Error(snap.error || "Snapshot failed.");
|
|
898
|
+
const program = new Program(idl_default, provider);
|
|
899
|
+
console.log("SDK CALLING PROGRAM ID:", program.programId.toBase58());
|
|
900
|
+
const [pda] = PublicKey3.findProgramAddressSync(
|
|
901
|
+
[Buffer.from("svrn_v5"), new BN(proposalId).toArrayLike(Buffer, "le", 8)],
|
|
902
|
+
getProgramId()
|
|
903
|
+
);
|
|
904
|
+
const [vault] = PublicKey3.findProgramAddressSync(
|
|
905
|
+
[
|
|
906
|
+
pda.toBuffer(),
|
|
907
|
+
TOKEN_PROGRAM_ID.toBuffer(),
|
|
908
|
+
// Legacy
|
|
909
|
+
new PublicKey3(votingMint).toBuffer()
|
|
910
|
+
],
|
|
911
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
912
|
+
);
|
|
913
|
+
const tx = await program.methods.initializeProposal(new BN(proposalId), hexToBytes(snap.root), new BN(1e3)).accounts({
|
|
914
|
+
proposal: pda,
|
|
915
|
+
proposalTokenAccount: vault,
|
|
916
|
+
authority: authorityPubkey,
|
|
917
|
+
votingMint: new PublicKey3(votingMint),
|
|
918
|
+
treasuryMint: new PublicKey3(votingMint),
|
|
919
|
+
targetWallet: authorityPubkey,
|
|
920
|
+
tokenProgram: TOKEN_PROGRAM_ID,
|
|
921
|
+
// Strict Legacy
|
|
922
|
+
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
923
|
+
systemProgram: SystemProgram.programId
|
|
924
|
+
}).transaction();
|
|
925
|
+
tx.add(SystemProgram.transfer({
|
|
926
|
+
fromPubkey: authorityPubkey,
|
|
927
|
+
toPubkey: pda,
|
|
928
|
+
lamports: gasBufferSol * LAMPORTS_PER_SOL2
|
|
929
|
+
}));
|
|
930
|
+
const { blockhash } = await provider.connection.getLatestBlockhash();
|
|
931
|
+
tx.recentBlockhash = blockhash;
|
|
932
|
+
tx.feePayer = authorityPubkey;
|
|
933
|
+
const signedTx = await provider.wallet.signTransaction(tx);
|
|
934
|
+
const txid = await provider.connection.sendRawTransaction(signedTx.serialize());
|
|
935
|
+
await provider.connection.confirmTransaction(txid);
|
|
936
|
+
return { proposalId, txid };
|
|
937
|
+
}
|
|
938
|
+
async castVote(provider, walletPubkey, proposalId, choice) {
|
|
939
|
+
if (!walletPubkey || typeof walletPubkey !== "string") {
|
|
940
|
+
throw new Error("Invalid wallet public key");
|
|
941
|
+
}
|
|
942
|
+
if (!Number.isInteger(proposalId) || proposalId < 0) {
|
|
943
|
+
throw new Error("Invalid proposal ID");
|
|
944
|
+
}
|
|
945
|
+
if (!Number.isInteger(choice) || choice !== 0 && choice !== 1) {
|
|
946
|
+
throw new Error("Invalid vote choice: must be 0 (NO) or 1 (YES)");
|
|
947
|
+
}
|
|
948
|
+
const proofData = await this.api.getProof(proposalId, walletPubkey);
|
|
949
|
+
const relayerSecret = proofData.proof.secret;
|
|
950
|
+
const zkProof = await this.prover.generateVoteProof(relayerSecret, proofData, proposalId);
|
|
951
|
+
const weight = Number(proofData.proof.weight);
|
|
952
|
+
const encrypted = await this.encryption.encryptVote(provider, choice, weight);
|
|
953
|
+
const nullifier = zkProof.publicInputs[zkProof.publicInputs.length - 1];
|
|
954
|
+
const relayResponse = await this.api.submitVote(proposalId, nullifier, encrypted);
|
|
955
|
+
return { success: relayResponse.success, tx: relayResponse.tx, error: relayResponse.error };
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
export {
|
|
959
|
+
SolvrnClient
|
|
960
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "solvrn-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Privacy SDK for Solana governance using zero-knowledge proofs and confidential computing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"solana",
|
|
8
|
+
"governance",
|
|
9
|
+
"zk",
|
|
10
|
+
"zero-knowledge",
|
|
11
|
+
"privacy",
|
|
12
|
+
"voting",
|
|
13
|
+
"noir",
|
|
14
|
+
"arcium",
|
|
15
|
+
"mpc",
|
|
16
|
+
"blockchain"
|
|
17
|
+
],
|
|
18
|
+
"author": "Solvrn Labs",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/solvrn-labs/solvrn-sdk.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/solvrn-labs/solvrn-sdk/issues"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/solvrn-labs/solvrn-sdk#readme",
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"dev": "tsup --watch",
|
|
40
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
41
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
|
|
42
|
+
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@arcium-hq/client": "^0.6.2",
|
|
46
|
+
"@aztec/bb.js": "^3.0.0-nightly.20260102",
|
|
47
|
+
"@coral-xyz/anchor": "^0.32.1",
|
|
48
|
+
"@noir-lang/noir_js": "1.0.0-beta.15",
|
|
49
|
+
"@solana/spl-token": "^0.4.14",
|
|
50
|
+
"@solana/web3.js": "^1.95.0",
|
|
51
|
+
"bn.js": "^5.2.2",
|
|
52
|
+
"buffer": "^6.0.3"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/bn.js": "^5.2.0",
|
|
56
|
+
"@types/jest": "^29.5.14",
|
|
57
|
+
"@types/node": "^20.0.0",
|
|
58
|
+
"jest": "^29.7.0",
|
|
59
|
+
"ts-jest": "^29.4.6",
|
|
60
|
+
"tsup": "^8.0.0",
|
|
61
|
+
"typescript": "^5.3.3"
|
|
62
|
+
}
|
|
63
|
+
}
|