forked from Vero-protocol/vero-guardian-dashboard
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontractClient.ts
More file actions
201 lines (175 loc) · 6.38 KB
/
Copy pathcontractClient.ts
File metadata and controls
201 lines (175 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import * as StellarSdk from '@stellar/stellar-sdk';
import { signTransaction } from '@stellar/freighter-api';
import { defaultNetworkConfig, DEFAULT_HORIZON_URL, CONSENSUS_THRESHOLD_KEY } from './rpc';
/**
* Build, Freighter-sign, and submit a vote transaction.
*
* @param prId GitHub PR number registered by the Vero Relayer
* @param publicKey Stellar public key from WalletContext
* @param horizonUrl Optional Horizon URL (defaults to env or testnet)
* @param networkPassphrase Optional network passphrase (defaults to testnet)
* @returns Submitted transaction hash
*/
export async function castVote(
prId: number,
publicKey: string,
horizonUrl: string = defaultNetworkConfig.horizonUrl,
networkPassphrase: string = defaultNetworkConfig.networkPassphrase
): Promise<string> {
const server = new StellarSdk.Horizon.Server(horizonUrl);
const account = await server.loadAccount(publicKey);
const tx = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase,
})
.addOperation(
StellarSdk.Operation.manageData({ name: `vote_${prId}`, value: 'approve' })
)
.setTimeout(30)
.build();
const signed = await signTransaction(tx.toXDR(), {
networkPassphrase,
address: publicKey,
});
if (signed.error) {
throw new Error(signed.error.message ?? 'Freighter failed to sign the vote transaction');
}
const signedTx = StellarSdk.TransactionBuilder.fromXDR(
signed.signedTxXdr,
networkPassphrase
);
const result = await server.submitTransaction(signedTx);
return result.hash;
}
/**
* Fetch the current consensus progress for a given task from on-chain data.
*
* Reads vote weight entries from Horizon account data for the relayer account
* associated with the task. Falls back to default threshold if the on-chain
* `consensus_threshold` data entry is not found.
*
* @param taskId PR task ID to query consensus for
* @param horizonUrl Optional Horizon URL (defaults to env or testnet)
* @param networkPassphrase Optional network passphrase (defaults to testnet)
* @returns ConsensusData with current weight, threshold, approve/reject breakdown
*/
export async function getConsensusProgress(
taskId: string,
horizonUrl: string = defaultNetworkConfig.horizonUrl,
networkPassphrase: string = defaultNetworkConfig.networkPassphrase
): Promise<{ currentWeight: number; threshold: number; approveWeight: number; rejectWeight: number }> {
const server = new StellarSdk.Horizon.Server(horizonUrl);
if (!taskId || typeof taskId !== 'string' || taskId.trim().length === 0) {
throw new Error('Invalid or missing taskId');
}
const triagedId = taskId.trim();
// Load the relayer account that stores the task/vote data entries
const relayerPublicKey = process.env.NEXT_PUBLIC_RELAYER_PUBLIC_KEY;
if (!relayerPublicKey) {
throw new Error('Relayer public key not configured (NEXT_PUBLIC_RELAYER_PUBLIC_KEY)');
}
let account;
try {
account = await server.loadAccount(relayerPublicKey);
} catch (err) {
throw new Error(`Failed to load relayer account for consensus data: ${err instanceof Error ? err.message : 'Unknown error'}`);
}
const dataAttr = (account as any).data_attr as Record<string, string> | undefined;
if (!dataAttr || typeof dataAttr !== 'object') {
return { currentWeight: 0, threshold: 51, approveWeight: 0, rejectWeight: 0 };
}
// Decode base64 Stellar data values
const decodeBase64 = (value: string): string => {
if (typeof atob === 'function') return atob(value);
return Buffer.from(value, 'base64').toString();
};
// Read consensus threshold from data entries (or use default)
let threshold = 51;
const thresholdRaw = dataAttr[CONSENSUS_THRESHOLD_KEY];
if (thresholdRaw) {
try {
const parsed = parseInt(decodeBase64(thresholdRaw).trim(), 10);
if (Number.isFinite(parsed) && parsed > 0) {
threshold = parsed;
}
} catch {
// Fall back to default threshold
}
}
// Read approve votes for this task
const approveKey = `vote_${triagedId}_approve`;
const approveRaw = dataAttr[approveKey];
let approveWeight = 0;
if (approveRaw) {
try {
approveWeight = parseInt(decodeBase64(approveRaw).trim(), 10) || 0;
} catch {
approveWeight = 0;
}
}
// Read reject votes for this task
const rejectKey = `vote_${triagedId}_reject`;
const rejectRaw = dataAttr[rejectKey];
let rejectWeight = 0;
if (rejectRaw) {
try {
rejectWeight = parseInt(decodeBase64(rejectRaw).trim(), 10) || 0;
} catch {
rejectWeight = 0;
}
}
const currentWeight = approveWeight + rejectWeight;
return {
currentWeight,
threshold,
approveWeight,
rejectWeight,
};
}
/**
* Invoke the `halt()` function on the Vero Soroban contract via Freighter.
*
* @param publicKey Stellar public key from WalletContext
* @param contractId Soroban contract ID to halt
* @param sorobanRpcUrl Optional Soroban RPC URL (defaults to env or testnet)
* @param networkPassphrase Optional network passphrase (defaults to testnet)
* @returns Submitted transaction hash
*/
export async function haltContract(
publicKey: string,
contractId: string,
sorobanRpcUrl: string = defaultNetworkConfig.sorobanRpcUrl,
networkPassphrase: string = defaultNetworkConfig.networkPassphrase
): Promise<string> {
const server = new StellarSdk.SorobanRpc.Server(sorobanRpcUrl);
const account = await server.getAccount(publicKey);
const contract = new StellarSdk.Contract(contractId);
const rawTx = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE,
networkPassphrase,
})
.addOperation(contract.call("halt"))
.setTimeout(30)
.build();
const simulation = await server.simulateTransaction(rawTx);
if ('error' in simulation) {
throw new Error(simulation.error);
}
const preparedTx = StellarSdk.SorobanRpc.assembleTransaction(rawTx, simulation) as any;
const signed = await signTransaction(preparedTx.toXDR(), {
networkPassphrase,
address: publicKey,
});
if (signed.error) {
throw new Error(signed.error.message ?? 'Freighter failed to sign the halt transaction');
}
const signedTx = StellarSdk.TransactionBuilder.fromXDR(
signed.signedTxXdr,
networkPassphrase
);
const result = await server.sendTransaction(signedTx);
if (result.status === 'ERROR') {
throw new Error('Transaction submission failed with status ERROR');
}
return result.hash;
}