nebulon-escrow-cli 0.1.0 → 0.8.4
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/package.json +2 -2
- package/src/cli.js +2 -2
- package/src/commands/contract.js +373 -1246
- package/src/commands/init.js +2 -0
- package/src/constants.js +9 -8
- package/src/version.js +5 -0
package/src/commands/contract.js
CHANGED
|
@@ -20,24 +20,15 @@ const {
|
|
|
20
20
|
} = require("@solana/spl-token");
|
|
21
21
|
const { SessionTokenManager } = require("@magicblock-labs/gum-sdk");
|
|
22
22
|
const {
|
|
23
|
-
createDelegatePermissionInstruction,
|
|
24
|
-
permissionPdaFromAccount,
|
|
25
|
-
PERMISSION_PROGRAM_ID,
|
|
26
|
-
AUTHORITY_FLAG,
|
|
27
|
-
TX_LOGS_FLAG,
|
|
28
23
|
MAGIC_PROGRAM_ID,
|
|
29
24
|
MAGIC_CONTEXT_ID,
|
|
30
25
|
DELEGATION_PROGRAM_ID,
|
|
31
26
|
ConnectionMagicRouter,
|
|
32
|
-
getAuthToken,
|
|
33
|
-
getPermissionStatus,
|
|
34
|
-
waitUntilPermissionActive,
|
|
35
27
|
} = require("@magicblock-labs/ephemeral-rollups-sdk");
|
|
36
28
|
const { loadConfig, saveConfig } = require("../config");
|
|
37
29
|
const { loadWalletKeypair } = require("../wallets");
|
|
38
30
|
const { ensureHostedSession } = require("../session");
|
|
39
31
|
const { successMessage, errorMessage } = require("../ui");
|
|
40
|
-
const { checkTeeAvailability } = require("./tee");
|
|
41
32
|
const {
|
|
42
33
|
getContracts,
|
|
43
34
|
getContract,
|
|
@@ -144,11 +135,6 @@ let cachedValidatorEndpoint = null;
|
|
|
144
135
|
|
|
145
136
|
const normalizeEndpoint = (endpoint) => endpoint.replace(/\/$/, "");
|
|
146
137
|
|
|
147
|
-
const TEE_TOKEN_TTL_SECONDS = 240;
|
|
148
|
-
const teeTokenCache = new Map();
|
|
149
|
-
const teeWsHealthCache = new Map();
|
|
150
|
-
const teeProgramCache = new Map();
|
|
151
|
-
|
|
152
138
|
const isLocalnetConfig = (config) => {
|
|
153
139
|
const network = (config?.network || config?.solanaNetwork || "").toLowerCase();
|
|
154
140
|
if (network === "localnet") {
|
|
@@ -164,210 +150,66 @@ const isLocalnetConfig = (config) => {
|
|
|
164
150
|
);
|
|
165
151
|
};
|
|
166
152
|
|
|
167
|
-
const getTeeEndpoint = async (config, keypair, options = {}) => {
|
|
168
|
-
const base = normalizeEndpoint(
|
|
169
|
-
config.ephemeralTeeEndpoint ||
|
|
170
|
-
config.ephemeralPermissionEndpoint ||
|
|
171
|
-
"https://tee.magicblock.app"
|
|
172
|
-
);
|
|
173
|
-
const cacheKey = `${base}:${keypair.publicKey.toBase58()}`;
|
|
174
|
-
const cached = teeTokenCache.get(cacheKey);
|
|
175
|
-
const now = Math.floor(Date.now() / 1000);
|
|
176
|
-
if (!options.forceRefresh && cached && cached.expiresAt > now) {
|
|
177
|
-
return cached;
|
|
178
|
-
}
|
|
179
|
-
const auth = await getAuthToken(
|
|
180
|
-
base,
|
|
181
|
-
keypair.publicKey,
|
|
182
|
-
(message) => nacl.sign.detached(message, keypair.secretKey)
|
|
183
|
-
);
|
|
184
|
-
const endpoint = `${base}?token=${auth.token}`;
|
|
185
|
-
const expiresAt = now + TEE_TOKEN_TTL_SECONDS;
|
|
186
|
-
const record = { endpoint, token: auth.token, expiresAt };
|
|
187
|
-
teeTokenCache.set(cacheKey, record);
|
|
188
|
-
return record;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
153
|
const getEphemeralProgram = async (config, signerKeypair, options = {}) => {
|
|
154
|
+
const endpoint = config.ephemeralProviderUrl || config.rpcUrl;
|
|
155
|
+
const wsEndpoint = config.ephemeralWsUrl || undefined;
|
|
192
156
|
if (isLocalnetConfig(config)) {
|
|
193
|
-
return getProgram(config, signerKeypair, {
|
|
194
|
-
endpoint: config.ephemeralProviderUrl || config.rpcUrl,
|
|
195
|
-
wsEndpoint: config.ephemeralWsUrl || undefined,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
const cacheKey = signerKeypair.publicKey.toBase58();
|
|
199
|
-
const cached = teeProgramCache.get(cacheKey);
|
|
200
|
-
const now = Math.floor(Date.now() / 1000);
|
|
201
|
-
if (!options.forceRefresh && cached && cached.expiresAt > now + 30) {
|
|
202
|
-
return cached.bundle;
|
|
203
|
-
}
|
|
204
|
-
const { endpoint, token, expiresAt } = await getTeeEndpoint(
|
|
205
|
-
config,
|
|
206
|
-
signerKeypair,
|
|
207
|
-
options
|
|
208
|
-
);
|
|
209
|
-
let wsEndpoint =
|
|
210
|
-
config.ephemeralTeeWsEndpoint ||
|
|
211
|
-
endpoint.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
|
|
212
|
-
if (token && !wsEndpoint.includes("token=")) {
|
|
213
|
-
wsEndpoint += wsEndpoint.includes("?") ? `&token=${token}` : `?token=${token}`;
|
|
214
|
-
}
|
|
215
|
-
if (process.env.NEBULON_TEE_DEBUG === "1") {
|
|
216
|
-
console.log("TEE signer:", signerKeypair.publicKey.toBase58());
|
|
217
|
-
console.log("TEE RPC endpoint:", endpoint);
|
|
218
|
-
console.log("TEE WS endpoint:", wsEndpoint);
|
|
219
|
-
}
|
|
220
|
-
const programBundle = getProgram(config, signerKeypair, {
|
|
221
|
-
endpoint,
|
|
222
|
-
wsEndpoint,
|
|
223
|
-
});
|
|
224
|
-
await ensureTeeWsHealthy(programBundle.provider.connection, wsEndpoint);
|
|
225
|
-
teeProgramCache.set(cacheKey, { bundle: programBundle, expiresAt });
|
|
226
|
-
return programBundle;
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
const ensureTeeWsHealthy = async (connection, wsEndpoint) => {
|
|
230
|
-
if (!connection || !wsEndpoint) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
const cacheKey = wsEndpoint;
|
|
234
|
-
const now = Date.now();
|
|
235
|
-
const cached = teeWsHealthCache.get(cacheKey);
|
|
236
|
-
if (cached && cached.expiresAt > now) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const timeoutMs = 4000;
|
|
241
|
-
let subscriptionId = null;
|
|
242
|
-
let resolved = false;
|
|
243
|
-
try {
|
|
244
|
-
if (process.env.NEBULON_TEE_DEBUG === "1") {
|
|
245
|
-
console.log("TEE WS health check: subscribing for slot change...");
|
|
246
|
-
}
|
|
247
|
-
subscriptionId = connection.onSlotChange((info) => {
|
|
248
|
-
if (!resolved) {
|
|
249
|
-
resolved = true;
|
|
250
|
-
if (process.env.NEBULON_TEE_DEBUG === "1") {
|
|
251
|
-
console.log(`TEE WS health check: slot ${info.slot}`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
await new Promise((resolve, reject) => {
|
|
256
|
-
const timeout = setTimeout(() => {
|
|
257
|
-
if (!resolved) {
|
|
258
|
-
reject(
|
|
259
|
-
new Error(
|
|
260
|
-
"TEE WS health check failed (no slot notifications). Check MagicBlock WS connectivity."
|
|
261
|
-
)
|
|
262
|
-
);
|
|
263
|
-
} else {
|
|
264
|
-
resolve();
|
|
265
|
-
}
|
|
266
|
-
}, timeoutMs);
|
|
267
|
-
const poll = () => {
|
|
268
|
-
if (resolved) {
|
|
269
|
-
clearTimeout(timeout);
|
|
270
|
-
resolve();
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
setTimeout(poll, 50);
|
|
274
|
-
};
|
|
275
|
-
poll();
|
|
276
|
-
});
|
|
277
|
-
if (process.env.NEBULON_TEE_DEBUG === "1") {
|
|
278
|
-
console.log("TEE WS health check: OK");
|
|
279
|
-
}
|
|
280
|
-
teeWsHealthCache.set(cacheKey, { expiresAt: now + 15000 });
|
|
281
|
-
} finally {
|
|
282
|
-
if (subscriptionId !== null) {
|
|
283
|
-
try {
|
|
284
|
-
await connection.removeSlotChangeListener(subscriptionId);
|
|
285
|
-
} catch {
|
|
286
|
-
// ignore cleanup errors
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const buildPermissionEndpoint = async (config, keypair) => {
|
|
293
|
-
const network = (config?.network || config?.solanaNetwork || "").toLowerCase();
|
|
294
|
-
const rpc = String(config?.rpcUrl || "");
|
|
295
|
-
const isLocal =
|
|
296
|
-
network === "localnet" ||
|
|
297
|
-
rpc.includes("localhost:8899") ||
|
|
298
|
-
rpc.includes("127.0.0.1:8899");
|
|
299
|
-
if (isLocal) {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
const base = normalizeEndpoint(
|
|
303
|
-
config.ephemeralPermissionEndpoint || "https://tee.magicblock.app"
|
|
304
|
-
);
|
|
305
|
-
if (base.includes("localhost") || base.includes("127.0.0.1")) {
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
try {
|
|
309
|
-
const auth = await getAuthToken(
|
|
310
|
-
base,
|
|
311
|
-
keypair.publicKey,
|
|
312
|
-
(message) => nacl.sign.detached(message, keypair.secretKey)
|
|
313
|
-
);
|
|
314
|
-
return `${base}?token=${auth.token}`;
|
|
315
|
-
} catch (error) {
|
|
316
|
-
console.warn("Permission token fetch failed. Skipping permission checks.");
|
|
317
|
-
return base;
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
const waitForPermissionActive = async (config, keypair, pda, label) => {
|
|
322
|
-
const network = (config?.network || config?.solanaNetwork || "").toLowerCase();
|
|
323
|
-
const rpc = String(config?.rpcUrl || "");
|
|
324
|
-
if (
|
|
325
|
-
network === "localnet" ||
|
|
326
|
-
rpc.includes("localhost:8899") ||
|
|
327
|
-
rpc.includes("127.0.0.1:8899")
|
|
328
|
-
) {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
const endpoint = await buildPermissionEndpoint(config, keypair);
|
|
332
|
-
if (!endpoint) {
|
|
333
|
-
return;
|
|
157
|
+
return getProgram(config, signerKeypair, { endpoint, wsEndpoint });
|
|
334
158
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (ready) {
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
const status = await getPermissionStatus(endpoint, pda).catch(() => null);
|
|
341
|
-
const state = status?.status || "unknown";
|
|
342
|
-
throw new Error(
|
|
343
|
-
`${label} permission is not active yet (status: ${state}). Try again in a few seconds.`
|
|
344
|
-
);
|
|
345
|
-
} catch (error) {
|
|
346
|
-
console.warn(
|
|
347
|
-
`Warning: permission check failed for ${label}. Proceeding without confirmation.`
|
|
348
|
-
);
|
|
159
|
+
if (options.forceRefresh) {
|
|
160
|
+
return getProgram(config, signerKeypair, { endpoint, wsEndpoint });
|
|
349
161
|
}
|
|
162
|
+
return getProgram(config, signerKeypair, { endpoint, wsEndpoint });
|
|
350
163
|
};
|
|
351
164
|
|
|
352
165
|
const getDelegationValidator = async (config) => {
|
|
353
166
|
const endpoint = (config.ephemeralProviderUrl || "").toLowerCase();
|
|
167
|
+
const wsEndpoint = (config.ephemeralWsUrl || "").toLowerCase();
|
|
354
168
|
if (endpoint.includes("localhost") || endpoint.includes("127.0.0.1")) {
|
|
355
169
|
return LOCAL_VALIDATOR_IDENTITY;
|
|
356
170
|
}
|
|
357
|
-
|
|
358
|
-
|
|
171
|
+
|
|
172
|
+
const safePublicKey = (value) => {
|
|
173
|
+
try {
|
|
174
|
+
return value ? new PublicKey(value) : null;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const shouldUseRouter =
|
|
181
|
+
endpoint.includes("router") ||
|
|
182
|
+
wsEndpoint.includes("router") ||
|
|
183
|
+
endpoint.includes("magicblock.app") ||
|
|
184
|
+
wsEndpoint.includes("magicblock.app");
|
|
185
|
+
if (shouldUseRouter && config.ephemeralProviderUrl) {
|
|
186
|
+
const cacheKey = `${endpoint}|${wsEndpoint}`;
|
|
187
|
+
if (cachedValidator && cachedValidatorEndpoint === cacheKey) {
|
|
359
188
|
return cachedValidator;
|
|
360
189
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
190
|
+
try {
|
|
191
|
+
const router = new ConnectionMagicRouter(config.ephemeralProviderUrl, {
|
|
192
|
+
wsEndpoint: config.ephemeralWsUrl || undefined,
|
|
193
|
+
});
|
|
194
|
+
const closest = await router.getClosestValidator();
|
|
195
|
+
if (config && config.__verbose) {
|
|
196
|
+
console.log("Router closest:", JSON.stringify(closest));
|
|
197
|
+
}
|
|
198
|
+
const identity =
|
|
199
|
+
closest?.validatorIdentity || closest?.identity || closest?.validator;
|
|
200
|
+
const candidate = safePublicKey(identity || closest?.pubkey || closest);
|
|
201
|
+
if (candidate) {
|
|
202
|
+
cachedValidator = candidate;
|
|
203
|
+
cachedValidatorEndpoint = cacheKey;
|
|
204
|
+
return cachedValidator;
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
// fall back to configured identity below
|
|
208
|
+
}
|
|
368
209
|
}
|
|
369
|
-
|
|
370
|
-
|
|
210
|
+
const fallback = safePublicKey(config.ephemeralValidatorIdentity);
|
|
211
|
+
if (fallback) {
|
|
212
|
+
return fallback;
|
|
371
213
|
}
|
|
372
214
|
return null;
|
|
373
215
|
};
|
|
@@ -454,30 +296,6 @@ const ensureEscrowUndelegated = async (config, keypair, escrowPda) => {
|
|
|
454
296
|
.rpc({ skipPreflight: true });
|
|
455
297
|
};
|
|
456
298
|
|
|
457
|
-
const ensureEscrowOwnedByProgramL1 = async (config, keypair, escrowPda) => {
|
|
458
|
-
const { connection, programId } = getProgram(config, keypair);
|
|
459
|
-
const info = await connection.getAccountInfo(escrowPda, "confirmed");
|
|
460
|
-
if (!info || !info.owner) {
|
|
461
|
-
console.error("Escrow not found on-chain.");
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
if (info.owner.equals(DELEGATION_PROGRAM_ID)) {
|
|
465
|
-
console.error(
|
|
466
|
-
"Escrow is delegated to MagicBlock (PER). L1 MODE cannot modify it while delegated."
|
|
467
|
-
);
|
|
468
|
-
console.error(
|
|
469
|
-
"Switch back to PER mode or wait for TEE availability and run `nebulon contract <id> sync` to undelegate."
|
|
470
|
-
);
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
if (!info.owner.equals(programId)) {
|
|
474
|
-
console.error("Escrow is owned by an unexpected program.");
|
|
475
|
-
console.error(`Owner: ${info.owner.toBase58()}`);
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
478
|
-
return true;
|
|
479
|
-
};
|
|
480
|
-
|
|
481
299
|
const buildPrivacyContext = (scope, contractId, index) => {
|
|
482
300
|
const tail = index === undefined ? "" : `:${index}`;
|
|
483
301
|
return `nebulon:${scope}:v1:${contractId}${tail}`;
|
|
@@ -748,52 +566,13 @@ const parsePublicKey = (value, label) => {
|
|
|
748
566
|
}
|
|
749
567
|
};
|
|
750
568
|
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
.
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
const isL1Mode = (contract) => getExecutionMode(contract) === "l1";
|
|
759
|
-
|
|
760
|
-
const ensureExecutionMode = async (config, contract, keypair, options = {}) => {
|
|
761
|
-
if (isL1Mode(contract)) {
|
|
762
|
-
config.__l1_mode = true;
|
|
763
|
-
return "l1";
|
|
764
|
-
}
|
|
765
|
-
config.__l1_mode = false;
|
|
766
|
-
if (isLocalnetConfig(config)) {
|
|
767
|
-
config.__l1_mode = false;
|
|
768
|
-
return "per";
|
|
769
|
-
}
|
|
770
|
-
const check = await checkTeeAvailability(config, keypair, {
|
|
771
|
-
timeoutMs: 4000,
|
|
772
|
-
});
|
|
773
|
-
if (check.ok) {
|
|
774
|
-
config.__l1_mode = false;
|
|
775
|
-
return "per";
|
|
776
|
-
}
|
|
777
|
-
console.warn("Warning: MagicBlock TEE is not available from this location.");
|
|
778
|
-
console.warn(
|
|
779
|
-
"You can proceed in L1 MODE (no privacy, but functional on-chain flow)."
|
|
780
|
-
);
|
|
781
|
-
const proceed = await confirmAction(
|
|
782
|
-
options.confirm,
|
|
783
|
-
"Enable L1 MODE for this contract? yes/no"
|
|
784
|
-
);
|
|
785
|
-
if (!proceed) {
|
|
786
|
-
console.log("Continuing without L1 mode (TEE may still fail).");
|
|
787
|
-
config.__l1_mode = false;
|
|
788
|
-
return "per";
|
|
569
|
+
const ensureExecutionMode = async (config) => {
|
|
570
|
+
if (!isLocalnetConfig(config) && !config.ephemeralProviderUrl) {
|
|
571
|
+
console.warn(
|
|
572
|
+
"Warning: MagicBlock ER endpoint is not configured. Run `nebulon init` to fetch endpoints."
|
|
573
|
+
);
|
|
789
574
|
}
|
|
790
|
-
|
|
791
|
-
executionMode: "l1",
|
|
792
|
-
});
|
|
793
|
-
contract.execution_mode = "l1";
|
|
794
|
-
config.__l1_mode = true;
|
|
795
|
-
console.log("L1 MODE enabled for this contract.");
|
|
796
|
-
return "l1";
|
|
575
|
+
return "per";
|
|
797
576
|
};
|
|
798
577
|
|
|
799
578
|
const feeFromGross = (amount) => (amount * FEE_BPS) / BPS_DENOMINATOR;
|
|
@@ -821,6 +600,15 @@ const normalizeHashBytes = (value) => {
|
|
|
821
600
|
return Buffer.from(String(value), "utf8").subarray(0, 32);
|
|
822
601
|
};
|
|
823
602
|
|
|
603
|
+
const logProgress = (message) => {
|
|
604
|
+
console.log(chalk.gray(message));
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
const logTx = (label, sig, isEr = false) => {
|
|
608
|
+
const prefix = isEr ? "ER-tx" : "tx";
|
|
609
|
+
console.log(`${label} (${prefix}: ${sig})`);
|
|
610
|
+
};
|
|
611
|
+
|
|
824
612
|
const buildMilestonesHash = (milestones) => {
|
|
825
613
|
const hash = crypto.createHash("sha256");
|
|
826
614
|
const ordered = [...milestones].sort((a, b) => a.index - b.index);
|
|
@@ -882,15 +670,6 @@ const getPrivateMilestoneStatus = async (
|
|
|
882
670
|
) => {
|
|
883
671
|
const { program, programId } = getProgram(config, keypair);
|
|
884
672
|
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
885
|
-
if (isL1Mode(contract)) {
|
|
886
|
-
const milestones = await fetchPublicMilestones(
|
|
887
|
-
program,
|
|
888
|
-
escrowPda,
|
|
889
|
-
programId,
|
|
890
|
-
[index]
|
|
891
|
-
);
|
|
892
|
-
return milestones[0];
|
|
893
|
-
}
|
|
894
673
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
895
674
|
await getPerProgramBundle(config, keypair, programId, program.provider);
|
|
896
675
|
const milestones = await fetchPrivateMilestones(
|
|
@@ -1248,31 +1027,24 @@ const ensureSessionToken = async (config, provider, programId, authorityKey) =>
|
|
|
1248
1027
|
};
|
|
1249
1028
|
|
|
1250
1029
|
const getSessionContext = async (config, keypair, program) => {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
return { signer: sessionSigner, sessionPda, payer: sessionSigner.publicKey };
|
|
1259
|
-
}
|
|
1260
|
-
return { signer: keypair, sessionPda: null, payer: keypair.publicKey };
|
|
1030
|
+
const { sessionSigner, sessionPda } = await ensureSessionToken(
|
|
1031
|
+
config,
|
|
1032
|
+
program.provider,
|
|
1033
|
+
program.programId,
|
|
1034
|
+
keypair.publicKey
|
|
1035
|
+
);
|
|
1036
|
+
return { signer: sessionSigner, sessionPda, payer: sessionSigner.publicKey };
|
|
1261
1037
|
};
|
|
1262
1038
|
|
|
1263
1039
|
const getPerProgramBundle = async (config, keypair, programId, provider) => {
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
return { program, sessionSigner, sessionPda };
|
|
1273
|
-
}
|
|
1274
|
-
const { program } = await getEphemeralProgram(config, keypair);
|
|
1275
|
-
return { program, sessionSigner: keypair, sessionPda: null };
|
|
1040
|
+
const { sessionSigner, sessionPda } = await ensureSessionToken(
|
|
1041
|
+
config,
|
|
1042
|
+
provider,
|
|
1043
|
+
programId,
|
|
1044
|
+
keypair.publicKey
|
|
1045
|
+
);
|
|
1046
|
+
const { program } = await getEphemeralProgram(config, sessionSigner);
|
|
1047
|
+
return { program, sessionSigner, sessionPda };
|
|
1276
1048
|
};
|
|
1277
1049
|
|
|
1278
1050
|
const isAlreadyExistsError = (error) => {
|
|
@@ -1284,91 +1056,21 @@ const isAlreadyExistsError = (error) => {
|
|
|
1284
1056
|
);
|
|
1285
1057
|
};
|
|
1286
1058
|
|
|
1287
|
-
const
|
|
1288
|
-
_config,
|
|
1289
|
-
program,
|
|
1290
|
-
keypair,
|
|
1291
|
-
permissionedAccount,
|
|
1292
|
-
members
|
|
1293
|
-
) => {
|
|
1294
|
-
try {
|
|
1295
|
-
await program.methods
|
|
1296
|
-
.createPermission(members.accountType, members.entries)
|
|
1297
|
-
.accountsPartial({
|
|
1298
|
-
payer: keypair.publicKey,
|
|
1299
|
-
permissionedAccount,
|
|
1300
|
-
permission: permissionPdaFromAccount(permissionedAccount),
|
|
1301
|
-
permissionProgram: PERMISSION_PROGRAM_ID,
|
|
1302
|
-
systemProgram: SystemProgram.programId,
|
|
1303
|
-
})
|
|
1304
|
-
.signers([keypair])
|
|
1305
|
-
.rpc();
|
|
1306
|
-
return true;
|
|
1307
|
-
} catch (error) {
|
|
1308
|
-
if (isAlreadyExistsError(error)) {
|
|
1309
|
-
return false;
|
|
1310
|
-
}
|
|
1311
|
-
throw error;
|
|
1312
|
-
}
|
|
1313
|
-
};
|
|
1314
|
-
|
|
1315
|
-
const ensureDelegatedPermission = async (
|
|
1316
|
-
config,
|
|
1317
|
-
provider,
|
|
1318
|
-
keypair,
|
|
1319
|
-
permissionedAccount
|
|
1320
|
-
) => {
|
|
1321
|
-
if (config && config.__l1_mode) {
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
const permissionPda = permissionPdaFromAccount(permissionedAccount);
|
|
1325
|
-
const info = await provider.connection.getAccountInfo(
|
|
1326
|
-
permissionPda,
|
|
1327
|
-
"confirmed"
|
|
1328
|
-
);
|
|
1329
|
-
if (info && info.owner.equals(DELEGATION_PROGRAM_ID)) {
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
if (info && !info.owner.equals(PERMISSION_PROGRAM_ID)) {
|
|
1333
|
-
throw new Error(
|
|
1334
|
-
`Permission ${permissionPda.toBase58()} is owned by ${info.owner.toBase58()}, expected ${PERMISSION_PROGRAM_ID.toBase58()}.`
|
|
1335
|
-
);
|
|
1336
|
-
}
|
|
1337
|
-
const validator = await getDelegationValidator(config);
|
|
1338
|
-
const ix = createDelegatePermissionInstruction({
|
|
1339
|
-
payer: keypair.publicKey,
|
|
1340
|
-
validator,
|
|
1341
|
-
permissionedAccount: [permissionedAccount, false],
|
|
1342
|
-
authority: [keypair.publicKey, true],
|
|
1343
|
-
});
|
|
1344
|
-
const permissionMeta = ix.keys.find((key) =>
|
|
1345
|
-
key.pubkey.equals(permissionedAccount)
|
|
1346
|
-
);
|
|
1347
|
-
if (permissionMeta) {
|
|
1348
|
-
permissionMeta.isWritable = true;
|
|
1349
|
-
}
|
|
1350
|
-
const tx = new Transaction().add(ix);
|
|
1351
|
-
try {
|
|
1352
|
-
await provider.sendAndConfirm(tx, [keypair]);
|
|
1353
|
-
} catch (error) {
|
|
1354
|
-
if (!isAlreadyExistsError(error)) {
|
|
1355
|
-
throw error;
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
};
|
|
1059
|
+
const isVerbose = (options) => Boolean(options && options.verbose);
|
|
1359
1060
|
|
|
1360
1061
|
const ensureDelegatedAccount = async (
|
|
1361
1062
|
config,
|
|
1362
1063
|
program,
|
|
1363
1064
|
keypair,
|
|
1364
1065
|
accountType,
|
|
1365
|
-
pda
|
|
1066
|
+
pda,
|
|
1067
|
+
options
|
|
1366
1068
|
) => {
|
|
1367
|
-
if (config && config.__l1_mode) {
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
1069
|
const info = await program.provider.connection.getAccountInfo(pda, "confirmed");
|
|
1371
1070
|
if (info && info.owner.equals(DELEGATION_PROGRAM_ID)) {
|
|
1071
|
+
if (isVerbose(options)) {
|
|
1072
|
+
console.log(`Delegation already active for ${pda.toBase58()}; skipping.`);
|
|
1073
|
+
}
|
|
1372
1074
|
return;
|
|
1373
1075
|
}
|
|
1374
1076
|
const isPerVault = accountType && Object.prototype.hasOwnProperty.call(accountType, "perVault");
|
|
@@ -1383,6 +1085,13 @@ const ensureDelegatedAccount = async (
|
|
|
1383
1085
|
}
|
|
1384
1086
|
try {
|
|
1385
1087
|
const validator = await getDelegationValidator(config);
|
|
1088
|
+
if (validator) {
|
|
1089
|
+
if (isVerbose(options)) {
|
|
1090
|
+
console.log(`Delegating account to validator: ${validator.toBase58()}`);
|
|
1091
|
+
}
|
|
1092
|
+
} else if (isVerbose(options)) {
|
|
1093
|
+
console.warn("Delegation skipped: no validator identity resolved.");
|
|
1094
|
+
}
|
|
1386
1095
|
const remainingAccounts = validator
|
|
1387
1096
|
? [{ pubkey: validator, isWritable: false, isSigner: false }]
|
|
1388
1097
|
: [];
|
|
@@ -1394,7 +1103,7 @@ const ensureDelegatedAccount = async (
|
|
|
1394
1103
|
})
|
|
1395
1104
|
.remainingAccounts(remainingAccounts)
|
|
1396
1105
|
.signers([keypair])
|
|
1397
|
-
.rpc();
|
|
1106
|
+
.rpc({ skipPreflight: true });
|
|
1398
1107
|
} catch (error) {
|
|
1399
1108
|
if (!isAlreadyExistsError(error)) {
|
|
1400
1109
|
throw error;
|
|
@@ -1408,16 +1117,17 @@ const ensureDelegatedEscrow = async (
|
|
|
1408
1117
|
keypair,
|
|
1409
1118
|
escrowId,
|
|
1410
1119
|
escrowPda,
|
|
1411
|
-
client
|
|
1120
|
+
client,
|
|
1121
|
+
options
|
|
1412
1122
|
) => {
|
|
1413
|
-
if (config && config.__l1_mode) {
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
1123
|
const info = await program.provider.connection.getAccountInfo(
|
|
1417
1124
|
escrowPda,
|
|
1418
1125
|
"confirmed"
|
|
1419
1126
|
);
|
|
1420
1127
|
if (info && info.owner.equals(DELEGATION_PROGRAM_ID)) {
|
|
1128
|
+
if (isVerbose(options)) {
|
|
1129
|
+
console.log(`Delegation already active for ${escrowPda.toBase58()}; skipping.`);
|
|
1130
|
+
}
|
|
1421
1131
|
return;
|
|
1422
1132
|
}
|
|
1423
1133
|
if (info && !info.owner.equals(program.programId)) {
|
|
@@ -1427,6 +1137,13 @@ const ensureDelegatedEscrow = async (
|
|
|
1427
1137
|
}
|
|
1428
1138
|
try {
|
|
1429
1139
|
const validator = await getDelegationValidator(config);
|
|
1140
|
+
if (validator) {
|
|
1141
|
+
if (isVerbose(options)) {
|
|
1142
|
+
console.log(`Delegating escrow to validator: ${validator.toBase58()}`);
|
|
1143
|
+
}
|
|
1144
|
+
} else if (isVerbose(options)) {
|
|
1145
|
+
console.warn("Delegation skipped: no validator identity resolved.");
|
|
1146
|
+
}
|
|
1430
1147
|
const remainingAccounts = validator
|
|
1431
1148
|
? [{ pubkey: validator, isWritable: false, isSigner: false }]
|
|
1432
1149
|
: [];
|
|
@@ -1439,7 +1156,7 @@ const ensureDelegatedEscrow = async (
|
|
|
1439
1156
|
})
|
|
1440
1157
|
.remainingAccounts(remainingAccounts)
|
|
1441
1158
|
.signers([keypair])
|
|
1442
|
-
.rpc();
|
|
1159
|
+
.rpc({ skipPreflight: true });
|
|
1443
1160
|
} catch (error) {
|
|
1444
1161
|
if (!isAlreadyExistsError(error)) {
|
|
1445
1162
|
throw error;
|
|
@@ -1447,16 +1164,6 @@ const ensureDelegatedEscrow = async (
|
|
|
1447
1164
|
}
|
|
1448
1165
|
};
|
|
1449
1166
|
|
|
1450
|
-
const buildMembers = (contract) => {
|
|
1451
|
-
const flags = AUTHORITY_FLAG | TX_LOGS_FLAG;
|
|
1452
|
-
return {
|
|
1453
|
-
entries: [
|
|
1454
|
-
{ flags, pubkey: new PublicKey(contract.client_wallet) },
|
|
1455
|
-
{ flags, pubkey: new PublicKey(contract.contractor_wallet) },
|
|
1456
|
-
],
|
|
1457
|
-
};
|
|
1458
|
-
};
|
|
1459
|
-
|
|
1460
1167
|
const ensureAta = async (connection, payer, owner, mint) => {
|
|
1461
1168
|
const ata = await getAssociatedTokenAddress(mint, owner, true);
|
|
1462
1169
|
try {
|
|
@@ -1763,7 +1470,7 @@ const runInitContract = async (config, contract, options) => {
|
|
|
1763
1470
|
})
|
|
1764
1471
|
.signers([keypair])
|
|
1765
1472
|
.rpc();
|
|
1766
|
-
|
|
1473
|
+
logTx("Escrow initialized.", sig, false);
|
|
1767
1474
|
|
|
1768
1475
|
await linkEscrow(config.backendUrl, config.auth.token, contract.id, {
|
|
1769
1476
|
escrowPda: escrowPda.toBase58(),
|
|
@@ -1832,37 +1539,12 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1832
1539
|
return;
|
|
1833
1540
|
}
|
|
1834
1541
|
|
|
1835
|
-
|
|
1836
|
-
const l1Mode = executionMode === "l1";
|
|
1542
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
1837
1543
|
|
|
1838
1544
|
console.log("Processing.");
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
.addMilestone(new anchor.BN(escrowId.toString()), index)
|
|
1843
|
-
.accounts({
|
|
1844
|
-
actor: walletKey,
|
|
1845
|
-
escrow: escrowPda,
|
|
1846
|
-
milestone: milestonePda,
|
|
1847
|
-
systemProgram: SystemProgram.programId,
|
|
1848
|
-
})
|
|
1849
|
-
.signers([keypair])
|
|
1850
|
-
.rpc();
|
|
1851
|
-
console.log(`Milestone created. (tx: ${sig})`);
|
|
1852
|
-
|
|
1853
|
-
const milestones = Array.isArray(contract.milestones)
|
|
1854
|
-
? [...contract.milestones]
|
|
1855
|
-
: [];
|
|
1856
|
-
milestones.push({ index, details: title, status: "created" });
|
|
1857
|
-
const persisted = await updateContractMilestones(config, contract, milestones);
|
|
1858
|
-
if (!persisted) {
|
|
1859
|
-
console.log(
|
|
1860
|
-
"Warning: backend did not persist milestone status (check backend version)."
|
|
1861
|
-
);
|
|
1862
|
-
}
|
|
1863
|
-
successMessage("Milestone added.");
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1545
|
+
logProgress("Preparing terms for ER submission");
|
|
1546
|
+
await sleep(200);
|
|
1547
|
+
logProgress("Encrypting milestone details");
|
|
1866
1548
|
let privacyKey;
|
|
1867
1549
|
try {
|
|
1868
1550
|
privacyKey = await getContractPrivacyKey(config, contract.id, wallet);
|
|
@@ -1878,6 +1560,10 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1878
1560
|
index,
|
|
1879
1561
|
title
|
|
1880
1562
|
);
|
|
1563
|
+
if (!isEncryptedPayload(encryptedDetails)) {
|
|
1564
|
+
console.error("Failed to encrypt milestone details.");
|
|
1565
|
+
process.exit(1);
|
|
1566
|
+
}
|
|
1881
1567
|
|
|
1882
1568
|
const encryptedBytes = Buffer.from(encryptedDetails, "utf8");
|
|
1883
1569
|
|
|
@@ -1893,6 +1579,7 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1893
1579
|
.update(encryptedDetails)
|
|
1894
1580
|
.digest();
|
|
1895
1581
|
|
|
1582
|
+
logProgress("Preparing milestone accounts");
|
|
1896
1583
|
const privateMilestonePda = derivePrivateMilestonePda(
|
|
1897
1584
|
escrowPda,
|
|
1898
1585
|
index,
|
|
@@ -1909,26 +1596,13 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1909
1596
|
.signers([keypair])
|
|
1910
1597
|
.rpc();
|
|
1911
1598
|
|
|
1912
|
-
const members = buildMembers(contract);
|
|
1913
|
-
members.accountType = {
|
|
1914
|
-
privateMilestone: {
|
|
1915
|
-
escrow: escrowPda,
|
|
1916
|
-
index,
|
|
1917
|
-
},
|
|
1918
|
-
};
|
|
1919
|
-
await ensurePermission(config, program, keypair, privateMilestonePda, members);
|
|
1920
|
-
await ensureDelegatedPermission(
|
|
1921
|
-
config,
|
|
1922
|
-
program.provider,
|
|
1923
|
-
keypair,
|
|
1924
|
-
privateMilestonePda
|
|
1925
|
-
);
|
|
1926
1599
|
await ensureDelegatedAccount(
|
|
1927
1600
|
config,
|
|
1928
1601
|
program,
|
|
1929
1602
|
keypair,
|
|
1930
|
-
|
|
1931
|
-
privateMilestonePda
|
|
1603
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
1604
|
+
privateMilestonePda,
|
|
1605
|
+
options
|
|
1932
1606
|
);
|
|
1933
1607
|
|
|
1934
1608
|
const perVaultPda = derivePerVaultPda(escrowPda, programId);
|
|
@@ -1937,7 +1611,8 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1937
1611
|
program,
|
|
1938
1612
|
keypair,
|
|
1939
1613
|
{ perVault: { escrow: escrowPda } },
|
|
1940
|
-
perVaultPda
|
|
1614
|
+
perVaultPda,
|
|
1615
|
+
options
|
|
1941
1616
|
);
|
|
1942
1617
|
await ensureDelegatedEscrow(
|
|
1943
1618
|
config,
|
|
@@ -1945,9 +1620,11 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1945
1620
|
keypair,
|
|
1946
1621
|
escrowId,
|
|
1947
1622
|
escrowPda,
|
|
1948
|
-
new PublicKey(contract.client_wallet)
|
|
1623
|
+
new PublicKey(contract.client_wallet),
|
|
1624
|
+
options
|
|
1949
1625
|
);
|
|
1950
1626
|
|
|
1627
|
+
logProgress("Submitting metadata on ER");
|
|
1951
1628
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
1952
1629
|
await getPerProgramBundle(config, keypair, programId, program.provider);
|
|
1953
1630
|
const metaSig = await erProgram.methods
|
|
@@ -1968,8 +1645,7 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1968
1645
|
})
|
|
1969
1646
|
.signers([sessionSigner])
|
|
1970
1647
|
.rpc({ skipPreflight: true });
|
|
1971
|
-
|
|
1972
|
-
console.log(`Done. (tx: ${metaSig})`);
|
|
1648
|
+
logTx("Metadata submitted.", metaSig, true);
|
|
1973
1649
|
|
|
1974
1650
|
const milestones = Array.isArray(contract.milestones)
|
|
1975
1651
|
? [...contract.milestones]
|
|
@@ -2009,61 +1685,7 @@ const runDisableMilestone = async (config, contract, number, options) => {
|
|
|
2009
1685
|
console.error("Check the list with: nebulon contract <id> check milestone list");
|
|
2010
1686
|
return;
|
|
2011
1687
|
}
|
|
2012
|
-
|
|
2013
|
-
const l1Mode = executionMode === "l1";
|
|
2014
|
-
if (l1Mode) {
|
|
2015
|
-
const { program, connection, programId } = getProgram(config, keypair);
|
|
2016
|
-
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
2017
|
-
const escrowState = await getEscrowState(connection, programId, escrowPda);
|
|
2018
|
-
if (!escrowState) {
|
|
2019
|
-
console.error("Escrow not found on-chain.");
|
|
2020
|
-
process.exit(1);
|
|
2021
|
-
}
|
|
2022
|
-
const ok = await ensureEscrowOwnedByProgramL1(config, keypair, escrowPda);
|
|
2023
|
-
if (!ok) {
|
|
2024
|
-
return;
|
|
2025
|
-
}
|
|
2026
|
-
const escrowId = escrowState.escrowId;
|
|
2027
|
-
const milestonePda = deriveMilestonePda(escrowPda, index, programId);
|
|
2028
|
-
console.log("Verify data and confirm actions please.");
|
|
2029
|
-
renderContractSummary(contract, wallet);
|
|
2030
|
-
console.log("-Action-");
|
|
2031
|
-
console.log("Disable milestone");
|
|
2032
|
-
console.log(`Number : ${index + 1}`);
|
|
2033
|
-
const milestones = Array.isArray(contract.milestones) ? contract.milestones : [];
|
|
2034
|
-
const current = milestones.find((m) => m.index === index);
|
|
2035
|
-
console.log(`Details : \"${current ? getMilestoneLabel(current) : "unknown"}\"`);
|
|
2036
|
-
console.log("");
|
|
2037
|
-
const proceed = await confirmAction(options.confirm, "Proceed? yes/no");
|
|
2038
|
-
if (!proceed) {
|
|
2039
|
-
console.log("Canceled.");
|
|
2040
|
-
return;
|
|
2041
|
-
}
|
|
2042
|
-
console.log("Processing.");
|
|
2043
|
-
const sig = await program.methods
|
|
2044
|
-
.disableMilestone(new anchor.BN(escrowId.toString()), index)
|
|
2045
|
-
.accounts({
|
|
2046
|
-
actor: walletKey,
|
|
2047
|
-
escrow: escrowPda,
|
|
2048
|
-
milestone: milestonePda,
|
|
2049
|
-
})
|
|
2050
|
-
.signers([keypair])
|
|
2051
|
-
.rpc();
|
|
2052
|
-
console.log(`Milestone disabled. (tx: ${sig})`);
|
|
2053
|
-
const nextMilestones = milestones.map((milestone) =>
|
|
2054
|
-
milestone.index === index
|
|
2055
|
-
? { ...milestone, status: "disabled" }
|
|
2056
|
-
: milestone
|
|
2057
|
-
);
|
|
2058
|
-
const persisted = await updateContractMilestones(config, contract, nextMilestones);
|
|
2059
|
-
if (!persisted) {
|
|
2060
|
-
console.log(
|
|
2061
|
-
"Warning: backend did not persist milestone status (check backend version)."
|
|
2062
|
-
);
|
|
2063
|
-
}
|
|
2064
|
-
successMessage("Milestone disabled.");
|
|
2065
|
-
return;
|
|
2066
|
-
}
|
|
1688
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
2067
1689
|
if (!config.ephemeralProviderUrl) {
|
|
2068
1690
|
console.error(
|
|
2069
1691
|
"Warning: MagicBlock RPC is not configured; milestone status checks may be unreliable."
|
|
@@ -2085,6 +1707,47 @@ const runDisableMilestone = async (config, contract, number, options) => {
|
|
|
2085
1707
|
console.error("Current status: disabled");
|
|
2086
1708
|
return;
|
|
2087
1709
|
}
|
|
1710
|
+
const programBundle = getProgram(config, keypair);
|
|
1711
|
+
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
1712
|
+
const programId = programBundle.programId;
|
|
1713
|
+
const privateMilestonePda = derivePrivateMilestonePda(
|
|
1714
|
+
escrowPda,
|
|
1715
|
+
index,
|
|
1716
|
+
programId
|
|
1717
|
+
);
|
|
1718
|
+
await ensureDelegatedAccount(
|
|
1719
|
+
config,
|
|
1720
|
+
programBundle.program,
|
|
1721
|
+
keypair,
|
|
1722
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
1723
|
+
privateMilestonePda,
|
|
1724
|
+
options
|
|
1725
|
+
);
|
|
1726
|
+
const perVaultPda = derivePerVaultPda(escrowPda, programId);
|
|
1727
|
+
await ensureDelegatedAccount(
|
|
1728
|
+
config,
|
|
1729
|
+
programBundle.program,
|
|
1730
|
+
keypair,
|
|
1731
|
+
{ perVault: { escrow: escrowPda } },
|
|
1732
|
+
perVaultPda,
|
|
1733
|
+
options
|
|
1734
|
+
);
|
|
1735
|
+
const escrowState = await getEscrowState(
|
|
1736
|
+
programBundle.connection,
|
|
1737
|
+
programId,
|
|
1738
|
+
escrowPda
|
|
1739
|
+
);
|
|
1740
|
+
if (escrowState) {
|
|
1741
|
+
await ensureDelegatedEscrow(
|
|
1742
|
+
config,
|
|
1743
|
+
programBundle.program,
|
|
1744
|
+
keypair,
|
|
1745
|
+
escrowState.escrowId,
|
|
1746
|
+
escrowPda,
|
|
1747
|
+
new PublicKey(contract.client_wallet),
|
|
1748
|
+
options
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
2088
1751
|
let currentStatus = null;
|
|
2089
1752
|
try {
|
|
2090
1753
|
const milestone = await getPrivateMilestoneStatus(
|
|
@@ -2151,15 +1814,18 @@ const runDisableMilestone = async (config, contract, number, options) => {
|
|
|
2151
1814
|
}
|
|
2152
1815
|
|
|
2153
1816
|
console.log("Processing.");
|
|
1817
|
+
logProgress("Submitting milestone update on ER");
|
|
1818
|
+
await sleep(200);
|
|
2154
1819
|
const sig = await updatePrivateMilestoneStatus(
|
|
2155
1820
|
config,
|
|
2156
1821
|
contract,
|
|
2157
1822
|
keypair,
|
|
2158
1823
|
walletKey,
|
|
2159
1824
|
index,
|
|
2160
|
-
4
|
|
1825
|
+
4,
|
|
1826
|
+
options
|
|
2161
1827
|
);
|
|
2162
|
-
|
|
1828
|
+
logTx("Milestone disabled.", sig, true);
|
|
2163
1829
|
|
|
2164
1830
|
const nextMilestones = milestones.map((milestone) =>
|
|
2165
1831
|
milestone.index === index
|
|
@@ -2169,6 +1835,8 @@ const runDisableMilestone = async (config, contract, number, options) => {
|
|
|
2169
1835
|
await updateContractIfNegotiating(config, contract, {
|
|
2170
1836
|
milestones: nextMilestones,
|
|
2171
1837
|
});
|
|
1838
|
+
logProgress("Syncing private state to escrow flags");
|
|
1839
|
+
logProgress("Syncing private state to escrow flags");
|
|
2172
1840
|
await runSyncFlags(config, contract, { ...options, confirm: true });
|
|
2173
1841
|
successMessage("Milestone disabled.");
|
|
2174
1842
|
};
|
|
@@ -2178,7 +1846,8 @@ const ensureTermsPrepared = async (
|
|
|
2178
1846
|
contract,
|
|
2179
1847
|
keypair,
|
|
2180
1848
|
escrowPda,
|
|
2181
|
-
escrowId
|
|
1849
|
+
escrowId,
|
|
1850
|
+
options
|
|
2182
1851
|
) => {
|
|
2183
1852
|
const { program, programId } = getProgram(config, keypair);
|
|
2184
1853
|
const termsPda = deriveTermsPda(escrowPda, programId);
|
|
@@ -2193,17 +1862,13 @@ const ensureTermsPrepared = async (
|
|
|
2193
1862
|
.signers([keypair])
|
|
2194
1863
|
.rpc();
|
|
2195
1864
|
|
|
2196
|
-
const members = buildMembers(contract);
|
|
2197
|
-
members.accountType = { terms: { escrow: escrowPda } };
|
|
2198
|
-
await ensurePermission(config, program, keypair, termsPda, members);
|
|
2199
|
-
await ensureDelegatedPermission(config, program.provider, keypair, termsPda);
|
|
2200
|
-
await waitForPermissionActive(config, keypair, termsPda, "Terms");
|
|
2201
1865
|
await ensureDelegatedAccount(
|
|
2202
1866
|
config,
|
|
2203
1867
|
program,
|
|
2204
1868
|
keypair,
|
|
2205
|
-
|
|
2206
|
-
termsPda
|
|
1869
|
+
{ terms: { escrow: escrowPda } },
|
|
1870
|
+
termsPda,
|
|
1871
|
+
options
|
|
2207
1872
|
);
|
|
2208
1873
|
|
|
2209
1874
|
const perVaultPda = derivePerVaultPda(escrowPda, programId);
|
|
@@ -2212,7 +1877,8 @@ const ensureTermsPrepared = async (
|
|
|
2212
1877
|
program,
|
|
2213
1878
|
keypair,
|
|
2214
1879
|
{ perVault: { escrow: escrowPda } },
|
|
2215
|
-
perVaultPda
|
|
1880
|
+
perVaultPda,
|
|
1881
|
+
options
|
|
2216
1882
|
);
|
|
2217
1883
|
await ensureDelegatedEscrow(
|
|
2218
1884
|
config,
|
|
@@ -2220,7 +1886,8 @@ const ensureTermsPrepared = async (
|
|
|
2220
1886
|
keypair,
|
|
2221
1887
|
escrowId,
|
|
2222
1888
|
escrowPda,
|
|
2223
|
-
new PublicKey(contract.client_wallet)
|
|
1889
|
+
new PublicKey(contract.client_wallet),
|
|
1890
|
+
options
|
|
2224
1891
|
);
|
|
2225
1892
|
|
|
2226
1893
|
return { termsPda, perVaultPda, program, programId };
|
|
@@ -2234,19 +1901,26 @@ const submitTerms = async (
|
|
|
2234
1901
|
escrowId,
|
|
2235
1902
|
totalPayment,
|
|
2236
1903
|
deadline,
|
|
2237
|
-
encryptedTerms
|
|
1904
|
+
encryptedTerms,
|
|
1905
|
+
options
|
|
2238
1906
|
) => {
|
|
2239
1907
|
const { termsPda, perVaultPda, program, programId } = await ensureTermsPrepared(
|
|
2240
1908
|
config,
|
|
2241
1909
|
contract,
|
|
2242
1910
|
keypair,
|
|
2243
1911
|
escrowPda,
|
|
2244
|
-
escrowId
|
|
1912
|
+
escrowId,
|
|
1913
|
+
options
|
|
2245
1914
|
);
|
|
2246
1915
|
|
|
2247
1916
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
2248
1917
|
await getPerProgramBundle(config, keypair, programId, program.provider);
|
|
2249
1918
|
const encryptedBytes = Buffer.from(encryptedTerms || "", "utf8");
|
|
1919
|
+
if (encryptedBytes.length > 256) {
|
|
1920
|
+
throw new Error(
|
|
1921
|
+
`Encrypted terms too large (${encryptedBytes.length} bytes). Max is 256 bytes.`
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
2250
1924
|
const attemptCreate = async (forceRefresh = false) => {
|
|
2251
1925
|
const { program } = forceRefresh
|
|
2252
1926
|
? await getEphemeralProgram(config, keypair, { forceRefresh: true })
|
|
@@ -2275,7 +1949,7 @@ const submitTerms = async (
|
|
|
2275
1949
|
const attemptCreateDirect = async () => {
|
|
2276
1950
|
const { program: userErProgram } = await getEphemeralProgram(
|
|
2277
1951
|
config,
|
|
2278
|
-
|
|
1952
|
+
sessionSigner,
|
|
2279
1953
|
{ forceRefresh: true }
|
|
2280
1954
|
);
|
|
2281
1955
|
return userErProgram.methods
|
|
@@ -2329,148 +2003,6 @@ const submitTerms = async (
|
|
|
2329
2003
|
throw lastError;
|
|
2330
2004
|
};
|
|
2331
2005
|
|
|
2332
|
-
const submitPublicTerms = async (
|
|
2333
|
-
config,
|
|
2334
|
-
contract,
|
|
2335
|
-
keypair,
|
|
2336
|
-
escrowPda,
|
|
2337
|
-
escrowId,
|
|
2338
|
-
totalPayment,
|
|
2339
|
-
deadline
|
|
2340
|
-
) => {
|
|
2341
|
-
const { program, programId } = getProgram(config, keypair);
|
|
2342
|
-
const termsPda = deriveTermsPda(escrowPda, programId);
|
|
2343
|
-
const ok = await ensureEscrowOwnedByProgramL1(config, keypair, escrowPda);
|
|
2344
|
-
if (!ok) {
|
|
2345
|
-
return null;
|
|
2346
|
-
}
|
|
2347
|
-
const ix = await program.methods
|
|
2348
|
-
.setPublicTerms(
|
|
2349
|
-
new anchor.BN(escrowId.toString()),
|
|
2350
|
-
buildTermsHash(deadline, totalPayment),
|
|
2351
|
-
new anchor.BN(totalPayment.toString()),
|
|
2352
|
-
new anchor.BN(deadline.toString())
|
|
2353
|
-
)
|
|
2354
|
-
.accounts({
|
|
2355
|
-
user: keypair.publicKey,
|
|
2356
|
-
escrow: escrowPda,
|
|
2357
|
-
terms: termsPda,
|
|
2358
|
-
systemProgram: SystemProgram.programId,
|
|
2359
|
-
})
|
|
2360
|
-
.instruction();
|
|
2361
|
-
ix.keys = ix.keys.map((key) => {
|
|
2362
|
-
if (key.pubkey.equals(escrowPda) || key.pubkey.equals(termsPda)) {
|
|
2363
|
-
return { ...key, isWritable: true };
|
|
2364
|
-
}
|
|
2365
|
-
return key;
|
|
2366
|
-
});
|
|
2367
|
-
const tx = new Transaction().add(ix);
|
|
2368
|
-
const sig = await program.provider.sendAndConfirm(tx, [keypair]);
|
|
2369
|
-
return sig;
|
|
2370
|
-
};
|
|
2371
|
-
|
|
2372
|
-
const signPublicTermsOnChain = async (
|
|
2373
|
-
config,
|
|
2374
|
-
keypair,
|
|
2375
|
-
escrowPda,
|
|
2376
|
-
escrowId
|
|
2377
|
-
) => {
|
|
2378
|
-
const { program, programId } = getProgram(config, keypair);
|
|
2379
|
-
const termsPda = deriveTermsPda(escrowPda, programId);
|
|
2380
|
-
try {
|
|
2381
|
-
const escrowState = await program.account.escrow.fetch(escrowPda);
|
|
2382
|
-
const termsState = await program.account.terms.fetch(termsPda);
|
|
2383
|
-
const escrowIdOnChain = escrowState.escrowId?.toString?.() || String(escrowState.escrowId);
|
|
2384
|
-
const expectedEscrowId = escrowId.toString();
|
|
2385
|
-
if (escrowIdOnChain !== expectedEscrowId) {
|
|
2386
|
-
console.error(
|
|
2387
|
-
`L1 sign precheck: escrow_id mismatch (chain=${escrowIdOnChain}, local=${expectedEscrowId})`
|
|
2388
|
-
);
|
|
2389
|
-
}
|
|
2390
|
-
if (termsState.escrow && termsState.escrow.toBase58) {
|
|
2391
|
-
const termsEscrow = termsState.escrow.toBase58();
|
|
2392
|
-
if (termsEscrow !== escrowPda.toBase58()) {
|
|
2393
|
-
console.error(
|
|
2394
|
-
`L1 sign precheck: terms.escrow mismatch (terms=${termsEscrow}, escrow=${escrowPda.toBase58()})`
|
|
2395
|
-
);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
if (escrowState.fundedAmount && escrowState.fundedAmount.toString) {
|
|
2399
|
-
const funded = escrowState.fundedAmount.toString();
|
|
2400
|
-
if (funded !== "0") {
|
|
2401
|
-
console.error(`L1 sign precheck: funded_amount is ${funded}, expected 0`);
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
if (escrowState.fundingOk) {
|
|
2405
|
-
console.error("L1 sign precheck: funding_ok already true");
|
|
2406
|
-
}
|
|
2407
|
-
} catch (error) {
|
|
2408
|
-
console.error("L1 sign precheck failed to fetch escrow/terms.");
|
|
2409
|
-
}
|
|
2410
|
-
const ix = await program.methods
|
|
2411
|
-
.signPublicTerms(new anchor.BN(escrowId.toString()))
|
|
2412
|
-
.accounts({
|
|
2413
|
-
user: keypair.publicKey,
|
|
2414
|
-
escrow: escrowPda,
|
|
2415
|
-
terms: termsPda,
|
|
2416
|
-
})
|
|
2417
|
-
.instruction();
|
|
2418
|
-
ix.keys = ix.keys.map((key) => {
|
|
2419
|
-
if (key.pubkey.equals(escrowPda) || key.pubkey.equals(termsPda)) {
|
|
2420
|
-
return { ...key, isWritable: true };
|
|
2421
|
-
}
|
|
2422
|
-
return key;
|
|
2423
|
-
});
|
|
2424
|
-
const tx = new Transaction().add(ix);
|
|
2425
|
-
const sig = await program.provider.sendAndConfirm(tx, [keypair]);
|
|
2426
|
-
return { sig, termsPda };
|
|
2427
|
-
};
|
|
2428
|
-
|
|
2429
|
-
const commitPublicTermsOnChain = async (
|
|
2430
|
-
config,
|
|
2431
|
-
keypair,
|
|
2432
|
-
escrowPda,
|
|
2433
|
-
escrowId
|
|
2434
|
-
) => {
|
|
2435
|
-
const { program, programId } = getProgram(config, keypair);
|
|
2436
|
-
const termsPda = deriveTermsPda(escrowPda, programId);
|
|
2437
|
-
const ix = await program.methods
|
|
2438
|
-
.commitPublicTerms(new anchor.BN(escrowId.toString()))
|
|
2439
|
-
.accounts({
|
|
2440
|
-
user: keypair.publicKey,
|
|
2441
|
-
escrow: escrowPda,
|
|
2442
|
-
terms: termsPda,
|
|
2443
|
-
})
|
|
2444
|
-
.instruction();
|
|
2445
|
-
ix.keys = ix.keys.map((key) => {
|
|
2446
|
-
if (key.pubkey.equals(escrowPda) || key.pubkey.equals(termsPda)) {
|
|
2447
|
-
return { ...key, isWritable: true };
|
|
2448
|
-
}
|
|
2449
|
-
return key;
|
|
2450
|
-
});
|
|
2451
|
-
const tx = new Transaction().add(ix);
|
|
2452
|
-
return program.provider.sendAndConfirm(tx, [keypair]);
|
|
2453
|
-
};
|
|
2454
|
-
|
|
2455
|
-
const fetchPublicMilestones = async (
|
|
2456
|
-
program,
|
|
2457
|
-
escrowPda,
|
|
2458
|
-
programId,
|
|
2459
|
-
indices
|
|
2460
|
-
) => {
|
|
2461
|
-
const milestones = [];
|
|
2462
|
-
for (const index of indices) {
|
|
2463
|
-
const pda = deriveMilestonePda(escrowPda, index, programId);
|
|
2464
|
-
try {
|
|
2465
|
-
const state = await program.account.milestone.fetch(pda);
|
|
2466
|
-
milestones.push({ index, status: Number(state.status), pda });
|
|
2467
|
-
} catch {
|
|
2468
|
-
milestones.push({ index, status: null, pda });
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
return milestones;
|
|
2472
|
-
};
|
|
2473
|
-
|
|
2474
2006
|
const runAddTerm = async (config, contract, field, value, options) => {
|
|
2475
2007
|
const { keypair, wallet, walletKey } = getWalletContext(config);
|
|
2476
2008
|
ensureEditableContract(contract);
|
|
@@ -2534,35 +2066,34 @@ const runAddTerm = async (config, contract, field, value, options) => {
|
|
|
2534
2066
|
return;
|
|
2535
2067
|
}
|
|
2536
2068
|
|
|
2537
|
-
|
|
2538
|
-
const l1Mode = executionMode === "l1";
|
|
2069
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
2539
2070
|
|
|
2540
2071
|
let encryptedTerms = null;
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
process.exit(1);
|
|
2555
|
-
}
|
|
2072
|
+
try {
|
|
2073
|
+
const privacyKey = await getContractPrivacyKey(config, contract.id, wallet);
|
|
2074
|
+
encryptedTerms = encryptTermsPayload(
|
|
2075
|
+
privacyKey,
|
|
2076
|
+
contract.id,
|
|
2077
|
+
deadline,
|
|
2078
|
+
totalPayment
|
|
2079
|
+
);
|
|
2080
|
+
} catch (error) {
|
|
2081
|
+
console.error(
|
|
2082
|
+
"Privacy key exchange pending. Wait for the counterparty to accept."
|
|
2083
|
+
);
|
|
2084
|
+
process.exit(1);
|
|
2556
2085
|
}
|
|
2557
2086
|
|
|
2558
2087
|
await updateContractIfNegotiating(config, contract, {
|
|
2559
|
-
|
|
2088
|
+
termsEncrypted: encryptedTerms,
|
|
2560
2089
|
termsHash: buildTermsHashHex(deadline, totalPayment),
|
|
2561
|
-
...(l1Mode ? { deadline, totalPayment } : {}),
|
|
2562
2090
|
});
|
|
2563
2091
|
|
|
2564
2092
|
if (!deadline || !totalPayment) {
|
|
2565
|
-
|
|
2093
|
+
const missingLabel = !deadline ? "deadline" : "payment";
|
|
2094
|
+
console.log(
|
|
2095
|
+
`Saved term. Set ${missingLabel} with \`nebulon contract <id> add term ${missingLabel} <value>\` to submit on-chain.`
|
|
2096
|
+
);
|
|
2566
2097
|
successMessage("Term saved.");
|
|
2567
2098
|
return;
|
|
2568
2099
|
}
|
|
@@ -2576,32 +2107,27 @@ const runAddTerm = async (config, contract, field, value, options) => {
|
|
|
2576
2107
|
|
|
2577
2108
|
console.log("Processing.");
|
|
2578
2109
|
try {
|
|
2579
|
-
const sig =
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
config,
|
|
2591
|
-
contract,
|
|
2592
|
-
keypair,
|
|
2593
|
-
escrowPda,
|
|
2594
|
-
escrowId,
|
|
2595
|
-
totalPayment,
|
|
2596
|
-
deadline,
|
|
2597
|
-
encryptedTerms
|
|
2598
|
-
);
|
|
2110
|
+
const sig = await submitTerms(
|
|
2111
|
+
config,
|
|
2112
|
+
contract,
|
|
2113
|
+
keypair,
|
|
2114
|
+
escrowPda,
|
|
2115
|
+
escrowId,
|
|
2116
|
+
totalPayment,
|
|
2117
|
+
deadline,
|
|
2118
|
+
encryptedTerms,
|
|
2119
|
+
options
|
|
2120
|
+
);
|
|
2599
2121
|
if (!sig) {
|
|
2600
2122
|
return;
|
|
2601
2123
|
}
|
|
2602
|
-
|
|
2124
|
+
logTx("Terms submitted.", sig, true);
|
|
2603
2125
|
successMessage("Terms submitted.");
|
|
2604
2126
|
} catch (error) {
|
|
2127
|
+
console.error(error?.message || error);
|
|
2128
|
+
if (error?.stack) {
|
|
2129
|
+
console.error(error.stack);
|
|
2130
|
+
}
|
|
2605
2131
|
await printTxLogs(error);
|
|
2606
2132
|
throw error;
|
|
2607
2133
|
}
|
|
@@ -2613,14 +2139,13 @@ const runSignContract = async (config, contract, options) => {
|
|
|
2613
2139
|
console.error("Contract has no escrow. Unable to sign.");
|
|
2614
2140
|
process.exit(1);
|
|
2615
2141
|
}
|
|
2616
|
-
|
|
2617
|
-
const l1Mode = executionMode === "l1";
|
|
2142
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
2618
2143
|
ensureContractPhase(
|
|
2619
2144
|
contract,
|
|
2620
2145
|
["negotiating", "awaiting_signatures"],
|
|
2621
2146
|
"Finish terms/milestones, then sign."
|
|
2622
2147
|
);
|
|
2623
|
-
if (
|
|
2148
|
+
if (contract.terms_encrypted && !contract.privacyReady) {
|
|
2624
2149
|
console.error("Privacy key exchange pending. Unable to read terms.");
|
|
2625
2150
|
process.exit(1);
|
|
2626
2151
|
}
|
|
@@ -2685,43 +2210,40 @@ const runSignContract = async (config, contract, options) => {
|
|
|
2685
2210
|
}
|
|
2686
2211
|
|
|
2687
2212
|
console.log("Processing.");
|
|
2213
|
+
logProgress("Submitting signature on ER");
|
|
2688
2214
|
let signSig = null;
|
|
2689
2215
|
let commitSig = null;
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
config,
|
|
2701
|
-
keypair,
|
|
2702
|
-
escrowPda,
|
|
2703
|
-
escrowId
|
|
2704
|
-
);
|
|
2705
|
-
} catch (error) {
|
|
2706
|
-
commitSig = null;
|
|
2707
|
-
}
|
|
2708
|
-
} else {
|
|
2709
|
-
const { termsPda } = await ensureTermsPrepared(
|
|
2216
|
+
const { termsPda } = await ensureTermsPrepared(
|
|
2217
|
+
config,
|
|
2218
|
+
contract,
|
|
2219
|
+
keypair,
|
|
2220
|
+
escrowPda,
|
|
2221
|
+
escrowId,
|
|
2222
|
+
options
|
|
2223
|
+
);
|
|
2224
|
+
const { program: erProgram, sessionSigner, sessionPda } =
|
|
2225
|
+
await getPerProgramBundle(
|
|
2710
2226
|
config,
|
|
2711
|
-
contract,
|
|
2712
2227
|
keypair,
|
|
2713
|
-
|
|
2714
|
-
|
|
2228
|
+
programId,
|
|
2229
|
+
program.provider
|
|
2715
2230
|
);
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2231
|
+
signSig = await erProgram.methods
|
|
2232
|
+
.signPrivateTerms(new anchor.BN(escrowId.toString()))
|
|
2233
|
+
.accounts({
|
|
2234
|
+
user: keypair.publicKey,
|
|
2235
|
+
payer: sessionSigner.publicKey,
|
|
2236
|
+
sessionToken: sessionPda,
|
|
2237
|
+
escrow: escrowPda,
|
|
2238
|
+
terms: termsPda,
|
|
2239
|
+
})
|
|
2240
|
+
.signers([sessionSigner])
|
|
2241
|
+
.rpc({ skipPreflight: true });
|
|
2242
|
+
logTx("Signature submitted.", signSig, true);
|
|
2243
|
+
try {
|
|
2244
|
+
logProgress("Committing terms on ER");
|
|
2245
|
+
commitSig = await erProgram.methods
|
|
2246
|
+
.commitTerms(new anchor.BN(escrowId.toString()))
|
|
2725
2247
|
.accounts({
|
|
2726
2248
|
user: keypair.publicKey,
|
|
2727
2249
|
payer: sessionSigner.publicKey,
|
|
@@ -2731,25 +2253,11 @@ const runSignContract = async (config, contract, options) => {
|
|
|
2731
2253
|
})
|
|
2732
2254
|
.signers([sessionSigner])
|
|
2733
2255
|
.rpc({ skipPreflight: true });
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
commitSig = await erProgram.methods
|
|
2737
|
-
.commitTerms(new anchor.BN(escrowId.toString()))
|
|
2738
|
-
.accounts({
|
|
2739
|
-
user: keypair.publicKey,
|
|
2740
|
-
payer: sessionSigner.publicKey,
|
|
2741
|
-
sessionToken: sessionPda,
|
|
2742
|
-
escrow: escrowPda,
|
|
2743
|
-
terms: termsPda,
|
|
2744
|
-
})
|
|
2745
|
-
.signers([sessionSigner])
|
|
2746
|
-
.rpc({ skipPreflight: true });
|
|
2747
|
-
} catch (error) {
|
|
2748
|
-
commitSig = null;
|
|
2749
|
-
}
|
|
2256
|
+
} catch (error) {
|
|
2257
|
+
commitSig = null;
|
|
2750
2258
|
}
|
|
2751
2259
|
if (commitSig) {
|
|
2752
|
-
|
|
2260
|
+
logTx("Terms committed.", commitSig, true);
|
|
2753
2261
|
console.log("Syncing contract flags...");
|
|
2754
2262
|
let synced = false;
|
|
2755
2263
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
@@ -2818,7 +2326,7 @@ const runFundContract = async (config, contract, options) => {
|
|
|
2818
2326
|
process.exit(1);
|
|
2819
2327
|
}
|
|
2820
2328
|
ensureContractPhase(contract, ["waiting_for_funding"], "Run sign first.");
|
|
2821
|
-
if (
|
|
2329
|
+
if (contract.terms_encrypted && !contract.privacyReady) {
|
|
2822
2330
|
console.error("Privacy key exchange pending. Unable to read terms.");
|
|
2823
2331
|
process.exit(1);
|
|
2824
2332
|
}
|
|
@@ -2934,10 +2442,10 @@ const runFundContract = async (config, contract, options) => {
|
|
|
2934
2442
|
})
|
|
2935
2443
|
.signers([keypair])
|
|
2936
2444
|
.rpc();
|
|
2937
|
-
|
|
2445
|
+
logTx("Funding submitted.", sig, false);
|
|
2938
2446
|
|
|
2939
2447
|
await markFunded(config.backendUrl, config.auth.token, contract.id);
|
|
2940
|
-
|
|
2448
|
+
logProgress("Syncing private state to escrow flags");
|
|
2941
2449
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2942
2450
|
try {
|
|
2943
2451
|
await runSyncFlags(config, contract, { ...options, confirm: true });
|
|
@@ -2989,75 +2497,7 @@ const runUpdateMilestone = async (config, contract, number, options) => {
|
|
|
2989
2497
|
console.error("Check the list with: nebulon contract <id> check milestone list");
|
|
2990
2498
|
return;
|
|
2991
2499
|
}
|
|
2992
|
-
|
|
2993
|
-
const l1Mode = executionMode === "l1";
|
|
2994
|
-
if (l1Mode) {
|
|
2995
|
-
const { program, connection, programId } = getProgram(config, keypair);
|
|
2996
|
-
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
2997
|
-
const escrowState = await getEscrowState(connection, programId, escrowPda);
|
|
2998
|
-
if (!escrowState) {
|
|
2999
|
-
console.error("Escrow not found on-chain.");
|
|
3000
|
-
process.exit(1);
|
|
3001
|
-
}
|
|
3002
|
-
const ok = await ensureEscrowOwnedByProgramL1(config, keypair, escrowPda);
|
|
3003
|
-
if (!ok) {
|
|
3004
|
-
return;
|
|
3005
|
-
}
|
|
3006
|
-
const escrowId = escrowState.escrowId;
|
|
3007
|
-
const milestonePda = deriveMilestonePda(escrowPda, index, programId);
|
|
3008
|
-
|
|
3009
|
-
console.log("Verify data and confirm actions please.");
|
|
3010
|
-
renderContractSummary(contract, wallet);
|
|
3011
|
-
console.log("-Action-");
|
|
3012
|
-
console.log("Update milestone");
|
|
3013
|
-
console.log(`Number : ${index + 1}`);
|
|
3014
|
-
console.log("Status : ready");
|
|
3015
|
-
console.log("");
|
|
3016
|
-
|
|
3017
|
-
const canProceed = await renderBalancesOrAbort(
|
|
3018
|
-
config,
|
|
3019
|
-
walletKey,
|
|
3020
|
-
contract.mint || config.usdcMint
|
|
3021
|
-
);
|
|
3022
|
-
if (!canProceed) {
|
|
3023
|
-
return;
|
|
3024
|
-
}
|
|
3025
|
-
|
|
3026
|
-
const proceed = await confirmAction(options.confirm, "Proceed? yes/no");
|
|
3027
|
-
if (!proceed) {
|
|
3028
|
-
console.log("Canceled.");
|
|
3029
|
-
return;
|
|
3030
|
-
}
|
|
3031
|
-
|
|
3032
|
-
console.log("Processing.");
|
|
3033
|
-
const sig = await program.methods
|
|
3034
|
-
.submitMilestone(new anchor.BN(escrowId.toString()), index)
|
|
3035
|
-
.accounts({
|
|
3036
|
-
contractor: walletKey,
|
|
3037
|
-
escrow: escrowPda,
|
|
3038
|
-
milestone: milestonePda,
|
|
3039
|
-
})
|
|
3040
|
-
.signers([keypair])
|
|
3041
|
-
.rpc();
|
|
3042
|
-
console.log(`Milestone updated. (tx: ${sig})`);
|
|
3043
|
-
|
|
3044
|
-
const milestones = Array.isArray(contract.milestones)
|
|
3045
|
-
? contract.milestones.map((milestone) =>
|
|
3046
|
-
milestone.index === index
|
|
3047
|
-
? { ...milestone, status: "ready" }
|
|
3048
|
-
: milestone
|
|
3049
|
-
)
|
|
3050
|
-
: [];
|
|
3051
|
-
const persisted = await updateContractMilestones(config, contract, milestones);
|
|
3052
|
-
if (!persisted) {
|
|
3053
|
-
console.log(
|
|
3054
|
-
"Warning: backend did not persist milestone status (check backend version)."
|
|
3055
|
-
);
|
|
3056
|
-
}
|
|
3057
|
-
await runSyncFlags(config, contract, { ...options, confirm: true });
|
|
3058
|
-
successMessage("Milestone updated.");
|
|
3059
|
-
return;
|
|
3060
|
-
}
|
|
2500
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
3061
2501
|
if (!config.ephemeralProviderUrl) {
|
|
3062
2502
|
console.error(
|
|
3063
2503
|
"Warning: MagicBlock RPC is not configured; milestone status checks may be unreliable."
|
|
@@ -3144,9 +2584,10 @@ const runUpdateMilestone = async (config, contract, number, options) => {
|
|
|
3144
2584
|
keypair,
|
|
3145
2585
|
walletKey,
|
|
3146
2586
|
index,
|
|
3147
|
-
1
|
|
2587
|
+
1,
|
|
2588
|
+
options
|
|
3148
2589
|
);
|
|
3149
|
-
|
|
2590
|
+
logTx("Milestone updated.", sig, true);
|
|
3150
2591
|
|
|
3151
2592
|
const milestones = Array.isArray(contract.milestones)
|
|
3152
2593
|
? contract.milestones.map((milestone) =>
|
|
@@ -3161,6 +2602,7 @@ const runUpdateMilestone = async (config, contract, number, options) => {
|
|
|
3161
2602
|
"Warning: backend did not persist milestone status (check backend version)."
|
|
3162
2603
|
);
|
|
3163
2604
|
}
|
|
2605
|
+
logProgress("Syncing private state to escrow flags");
|
|
3164
2606
|
await runSyncFlags(config, contract, { ...options, confirm: true });
|
|
3165
2607
|
successMessage("Milestone updated.");
|
|
3166
2608
|
};
|
|
@@ -3187,80 +2629,7 @@ const runConfirmMilestone = async (config, contract, number, options) => {
|
|
|
3187
2629
|
process.exit(1);
|
|
3188
2630
|
}
|
|
3189
2631
|
|
|
3190
|
-
|
|
3191
|
-
const l1Mode = executionMode === "l1";
|
|
3192
|
-
if (l1Mode) {
|
|
3193
|
-
const { program, connection, programId } = getProgram(config, keypair);
|
|
3194
|
-
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
3195
|
-
const escrowState = await getEscrowState(connection, programId, escrowPda);
|
|
3196
|
-
if (!escrowState) {
|
|
3197
|
-
console.error("Escrow not found on-chain.");
|
|
3198
|
-
process.exit(1);
|
|
3199
|
-
}
|
|
3200
|
-
const ok = await ensureEscrowOwnedByProgramL1(config, keypair, escrowPda);
|
|
3201
|
-
if (!ok) {
|
|
3202
|
-
return;
|
|
3203
|
-
}
|
|
3204
|
-
const escrowId = escrowState.escrowId;
|
|
3205
|
-
const milestonePda = deriveMilestonePda(escrowPda, index, programId);
|
|
3206
|
-
|
|
3207
|
-
console.log("Verify data and confirm actions please.");
|
|
3208
|
-
renderContractSummary(contract, wallet);
|
|
3209
|
-
console.log("-Action-");
|
|
3210
|
-
console.log("Confirm milestone");
|
|
3211
|
-
console.log(`Number : ${index + 1}`);
|
|
3212
|
-
console.log("");
|
|
3213
|
-
|
|
3214
|
-
const canProceed = await renderBalancesOrAbort(
|
|
3215
|
-
config,
|
|
3216
|
-
walletKey,
|
|
3217
|
-
contract.mint || config.usdcMint
|
|
3218
|
-
);
|
|
3219
|
-
if (!canProceed) {
|
|
3220
|
-
return;
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3223
|
-
const proceed = await confirmAction(options.confirm, "Proceed? yes/no");
|
|
3224
|
-
if (!proceed) {
|
|
3225
|
-
console.log("Canceled.");
|
|
3226
|
-
return;
|
|
3227
|
-
}
|
|
3228
|
-
|
|
3229
|
-
console.log("Processing.");
|
|
3230
|
-
const sig = await program.methods
|
|
3231
|
-
.approveMilestone(new anchor.BN(escrowId.toString()), index)
|
|
3232
|
-
.accounts({
|
|
3233
|
-
client: walletKey,
|
|
3234
|
-
escrow: escrowPda,
|
|
3235
|
-
milestone: milestonePda,
|
|
3236
|
-
})
|
|
3237
|
-
.signers([keypair])
|
|
3238
|
-
.rpc();
|
|
3239
|
-
console.log(`Milestone confirmed. (tx: ${sig})`);
|
|
3240
|
-
|
|
3241
|
-
const milestones = Array.isArray(contract.milestones)
|
|
3242
|
-
? contract.milestones.map((milestone) =>
|
|
3243
|
-
milestone.index === index
|
|
3244
|
-
? { ...milestone, status: "approved" }
|
|
3245
|
-
: milestone
|
|
3246
|
-
)
|
|
3247
|
-
: [];
|
|
3248
|
-
const persisted = await updateContractMilestones(config, contract, milestones);
|
|
3249
|
-
if (!persisted) {
|
|
3250
|
-
console.log(
|
|
3251
|
-
"Warning: backend did not persist milestone status (check backend version)."
|
|
3252
|
-
);
|
|
3253
|
-
}
|
|
3254
|
-
await runSyncFlags(config, contract, { ...options, confirm: true });
|
|
3255
|
-
try {
|
|
3256
|
-
const refreshed = await getEscrowState(connection, programId, escrowPda);
|
|
3257
|
-
if (refreshed && refreshed.readyToClaim) {
|
|
3258
|
-
successMessage("Contract funds are now ready to claim by the service provider.");
|
|
3259
|
-
}
|
|
3260
|
-
} catch {}
|
|
3261
|
-
successMessage("Milestone confirmed.");
|
|
3262
|
-
return;
|
|
3263
|
-
}
|
|
2632
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
3264
2633
|
|
|
3265
2634
|
console.log("Verify data and confirm actions please.");
|
|
3266
2635
|
renderContractSummary(contract, wallet);
|
|
@@ -3299,26 +2668,68 @@ const runConfirmMilestone = async (config, contract, number, options) => {
|
|
|
3299
2668
|
walletKey,
|
|
3300
2669
|
contract.mint || config.usdcMint
|
|
3301
2670
|
);
|
|
3302
|
-
if (!canProceed) {
|
|
3303
|
-
return;
|
|
3304
|
-
}
|
|
3305
|
-
|
|
3306
|
-
const proceed = await confirmAction(options.confirm, "Proceed? yes/no");
|
|
3307
|
-
if (!proceed) {
|
|
3308
|
-
console.log("Canceled.");
|
|
3309
|
-
return;
|
|
2671
|
+
if (!canProceed) {
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
const proceed = await confirmAction(options.confirm, "Proceed? yes/no");
|
|
2676
|
+
if (!proceed) {
|
|
2677
|
+
console.log("Canceled.");
|
|
2678
|
+
return;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
console.log("Processing.");
|
|
2682
|
+
const programBundle = getProgram(config, keypair);
|
|
2683
|
+
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
2684
|
+
const programId = programBundle.programId;
|
|
2685
|
+
const privateMilestonePda = derivePrivateMilestonePda(
|
|
2686
|
+
escrowPda,
|
|
2687
|
+
index,
|
|
2688
|
+
programId
|
|
2689
|
+
);
|
|
2690
|
+
await ensureDelegatedAccount(
|
|
2691
|
+
config,
|
|
2692
|
+
programBundle.program,
|
|
2693
|
+
keypair,
|
|
2694
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
2695
|
+
privateMilestonePda,
|
|
2696
|
+
options
|
|
2697
|
+
);
|
|
2698
|
+
const perVaultPda = derivePerVaultPda(escrowPda, programId);
|
|
2699
|
+
await ensureDelegatedAccount(
|
|
2700
|
+
config,
|
|
2701
|
+
programBundle.program,
|
|
2702
|
+
keypair,
|
|
2703
|
+
{ perVault: { escrow: escrowPda } },
|
|
2704
|
+
perVaultPda,
|
|
2705
|
+
options
|
|
2706
|
+
);
|
|
2707
|
+
const escrowState = await getEscrowState(
|
|
2708
|
+
programBundle.connection,
|
|
2709
|
+
programId,
|
|
2710
|
+
escrowPda
|
|
2711
|
+
);
|
|
2712
|
+
if (escrowState) {
|
|
2713
|
+
await ensureDelegatedEscrow(
|
|
2714
|
+
config,
|
|
2715
|
+
programBundle.program,
|
|
2716
|
+
keypair,
|
|
2717
|
+
escrowState.escrowId,
|
|
2718
|
+
escrowPda,
|
|
2719
|
+
new PublicKey(contract.client_wallet),
|
|
2720
|
+
options
|
|
2721
|
+
);
|
|
3310
2722
|
}
|
|
3311
|
-
|
|
3312
|
-
console.log("Processing.");
|
|
3313
2723
|
const sig = await updatePrivateMilestoneStatus(
|
|
3314
2724
|
config,
|
|
3315
2725
|
contract,
|
|
3316
2726
|
keypair,
|
|
3317
2727
|
walletKey,
|
|
3318
2728
|
index,
|
|
3319
|
-
3
|
|
2729
|
+
3,
|
|
2730
|
+
options
|
|
3320
2731
|
);
|
|
3321
|
-
|
|
2732
|
+
logTx("Milestone confirmed.", sig, true);
|
|
3322
2733
|
|
|
3323
2734
|
const milestones = Array.isArray(contract.milestones)
|
|
3324
2735
|
? contract.milestones.map((milestone) =>
|
|
@@ -3415,7 +2826,7 @@ const runClaimFunds = async (config, contract, options) => {
|
|
|
3415
2826
|
})
|
|
3416
2827
|
.signers([keypair])
|
|
3417
2828
|
.rpc();
|
|
3418
|
-
|
|
2829
|
+
logTx("Funds claimed.", sig, false);
|
|
3419
2830
|
successMessage(
|
|
3420
2831
|
`Funds claimed successfully. (+${formatUsdc(rawFunded)} USDC)`
|
|
3421
2832
|
);
|
|
@@ -3445,7 +2856,7 @@ const runClaimFunds = async (config, contract, options) => {
|
|
|
3445
2856
|
})
|
|
3446
2857
|
.signers([keypair])
|
|
3447
2858
|
.rpc();
|
|
3448
|
-
|
|
2859
|
+
logTx("Timeout funds claimed.", sig, false);
|
|
3449
2860
|
successMessage(
|
|
3450
2861
|
`Funds claimed successfully. (+${formatUsdc(rawFunded)} USDC)`
|
|
3451
2862
|
);
|
|
@@ -3504,7 +2915,7 @@ const runClaimFunds = async (config, contract, options) => {
|
|
|
3504
2915
|
})
|
|
3505
2916
|
.signers([keypair])
|
|
3506
2917
|
.rpc();
|
|
3507
|
-
|
|
2918
|
+
logTx("Refund claimed.", sig, false);
|
|
3508
2919
|
successMessage("Refund claimed.");
|
|
3509
2920
|
successMessage("Escrow rent returned to the creator.");
|
|
3510
2921
|
try {
|
|
@@ -3549,7 +2960,8 @@ const updatePrivateMilestoneStatus = async (
|
|
|
3549
2960
|
keypair,
|
|
3550
2961
|
walletKey,
|
|
3551
2962
|
index,
|
|
3552
|
-
status
|
|
2963
|
+
status,
|
|
2964
|
+
options
|
|
3553
2965
|
) => {
|
|
3554
2966
|
const { program, connection, programId } = getProgram(config, keypair);
|
|
3555
2967
|
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
@@ -3566,26 +2978,13 @@ const updatePrivateMilestoneStatus = async (
|
|
|
3566
2978
|
programId
|
|
3567
2979
|
);
|
|
3568
2980
|
|
|
3569
|
-
const members = buildMembers(contract);
|
|
3570
|
-
members.accountType = {
|
|
3571
|
-
privateMilestone: {
|
|
3572
|
-
escrow: escrowPda,
|
|
3573
|
-
index,
|
|
3574
|
-
},
|
|
3575
|
-
};
|
|
3576
|
-
await ensurePermission(config, program, keypair, privateMilestonePda, members);
|
|
3577
|
-
await ensureDelegatedPermission(
|
|
3578
|
-
config,
|
|
3579
|
-
program.provider,
|
|
3580
|
-
keypair,
|
|
3581
|
-
privateMilestonePda
|
|
3582
|
-
);
|
|
3583
2981
|
await ensureDelegatedAccount(
|
|
3584
2982
|
config,
|
|
3585
2983
|
program,
|
|
3586
2984
|
keypair,
|
|
3587
|
-
|
|
3588
|
-
privateMilestonePda
|
|
2985
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
2986
|
+
privateMilestonePda,
|
|
2987
|
+
options
|
|
3589
2988
|
);
|
|
3590
2989
|
await ensureDelegatedEscrow(
|
|
3591
2990
|
config,
|
|
@@ -3593,12 +2992,15 @@ const updatePrivateMilestoneStatus = async (
|
|
|
3593
2992
|
keypair,
|
|
3594
2993
|
escrowId,
|
|
3595
2994
|
escrowPda,
|
|
3596
|
-
new PublicKey(contract.client_wallet)
|
|
2995
|
+
new PublicKey(contract.client_wallet),
|
|
2996
|
+
options
|
|
3597
2997
|
);
|
|
3598
2998
|
|
|
3599
2999
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
3600
3000
|
await getPerProgramBundle(config, keypair, programId, program.provider);
|
|
3601
3001
|
|
|
3002
|
+
logProgress("Submitting milestone update on ER");
|
|
3003
|
+
await sleep(200);
|
|
3602
3004
|
const sig = await erProgram.methods
|
|
3603
3005
|
.updatePrivateMilestoneStatus(
|
|
3604
3006
|
new anchor.BN(escrowId.toString()),
|
|
@@ -3618,204 +3020,12 @@ const updatePrivateMilestoneStatus = async (
|
|
|
3618
3020
|
return sig;
|
|
3619
3021
|
};
|
|
3620
3022
|
|
|
3621
|
-
const runSyncFlagsL1 = async (config, contract, options) => {
|
|
3622
|
-
const { keypair, walletKey } = getWalletContext(config);
|
|
3623
|
-
if (!contract.escrow_pda) {
|
|
3624
|
-
console.error("Contract has no escrow.");
|
|
3625
|
-
process.exit(1);
|
|
3626
|
-
}
|
|
3627
|
-
|
|
3628
|
-
const { program, connection, programId } = getProgram(config, keypair);
|
|
3629
|
-
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
3630
|
-
const escrowState = await getEscrowState(connection, programId, escrowPda);
|
|
3631
|
-
if (!escrowState) {
|
|
3632
|
-
console.error("Escrow not found on-chain.");
|
|
3633
|
-
process.exit(1);
|
|
3634
|
-
}
|
|
3635
|
-
const ok = await ensureEscrowOwnedByProgramL1(config, keypair, escrowPda);
|
|
3636
|
-
if (!ok) {
|
|
3637
|
-
return;
|
|
3638
|
-
}
|
|
3639
|
-
|
|
3640
|
-
const escrowId = escrowState.escrowId;
|
|
3641
|
-
const termsPda = deriveTermsPda(escrowPda, programId);
|
|
3642
|
-
|
|
3643
|
-
let terms;
|
|
3644
|
-
try {
|
|
3645
|
-
terms = await program.account.terms.fetch(termsPda);
|
|
3646
|
-
} catch (error) {
|
|
3647
|
-
console.error("Terms not available on-chain yet.");
|
|
3648
|
-
process.exit(1);
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
|
-
const milestoneIndices = Array.isArray(contract.milestones)
|
|
3652
|
-
? contract.milestones.map((milestone) => milestone.index)
|
|
3653
|
-
: [];
|
|
3654
|
-
milestoneIndices.sort((a, b) => a - b);
|
|
3655
|
-
const milestones = await fetchPublicMilestones(
|
|
3656
|
-
program,
|
|
3657
|
-
escrowPda,
|
|
3658
|
-
programId,
|
|
3659
|
-
milestoneIndices
|
|
3660
|
-
);
|
|
3661
|
-
const milestonesHash = buildMilestonesHash(milestones);
|
|
3662
|
-
|
|
3663
|
-
const fundedAmount =
|
|
3664
|
-
typeof escrowState.fundedAmount === "bigint"
|
|
3665
|
-
? escrowState.fundedAmount
|
|
3666
|
-
: BigInt(escrowState.fundedAmount || 0);
|
|
3667
|
-
const canCommitMilestones = fundedAmount === 0n && !escrowState.fundingOk;
|
|
3668
|
-
const totalPayment = BigInt(terms.totalPayment.toString());
|
|
3669
|
-
const requiredNet = netFromGross(totalPayment);
|
|
3670
|
-
const fundingOk = fundedAmount >= requiredNet;
|
|
3671
|
-
const hasTermsHash = escrowState.termsHash
|
|
3672
|
-
? escrowState.termsHash.some((byte) => byte !== 0)
|
|
3673
|
-
: false;
|
|
3674
|
-
if (!hasTermsHash) {
|
|
3675
|
-
console.log("Terms hash not committed on-chain yet.");
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
|
-
const deadline = resolveTermsDeadline(
|
|
3679
|
-
Number(terms.deadline.toString()),
|
|
3680
|
-
escrowState.fundedAt
|
|
3681
|
-
);
|
|
3682
|
-
const now = Math.floor(Date.now() / 1000);
|
|
3683
|
-
const deadlinePassed = deadline ? now > deadline : false;
|
|
3684
|
-
|
|
3685
|
-
const statuses = milestones.map((milestone) =>
|
|
3686
|
-
Number.isFinite(milestone.status) ? milestone.status : 0
|
|
3687
|
-
);
|
|
3688
|
-
const allApproved =
|
|
3689
|
-
milestones.length === 0 ||
|
|
3690
|
-
statuses.every((status) => status === 3 || status === 4);
|
|
3691
|
-
const allSubmitted =
|
|
3692
|
-
milestones.length === 0 ||
|
|
3693
|
-
statuses.every((status) => status === 1 || status === 3 || status === 4);
|
|
3694
|
-
const hasUnsubmitted =
|
|
3695
|
-
milestones.length === 0 ||
|
|
3696
|
-
statuses.some((status) => status === 0 || status === 2);
|
|
3697
|
-
|
|
3698
|
-
const milestoneCount = milestones.length;
|
|
3699
|
-
if (milestoneCount > 255) {
|
|
3700
|
-
console.error("Too many milestones to sync.");
|
|
3701
|
-
process.exit(1);
|
|
3702
|
-
}
|
|
3703
|
-
|
|
3704
|
-
const proceed = await confirmAction(options.confirm, "Update escrow flags? yes/no");
|
|
3705
|
-
if (!proceed) {
|
|
3706
|
-
console.log("Canceled.");
|
|
3707
|
-
return;
|
|
3708
|
-
}
|
|
3709
|
-
|
|
3710
|
-
let didUpdate = false;
|
|
3711
|
-
const milestoneAccounts = milestones.map((milestone) => ({
|
|
3712
|
-
pubkey: milestone.pda,
|
|
3713
|
-
isWritable: false,
|
|
3714
|
-
isSigner: false,
|
|
3715
|
-
}));
|
|
3716
|
-
|
|
3717
|
-
if (canCommitMilestones && milestonesHash && escrowState.milestonesHash) {
|
|
3718
|
-
const currentHash = Buffer.from(escrowState.milestonesHash);
|
|
3719
|
-
if (!currentHash.equals(milestonesHash)) {
|
|
3720
|
-
const sig = await program.methods
|
|
3721
|
-
.commitPublicMilestones(
|
|
3722
|
-
new anchor.BN(escrowId.toString()),
|
|
3723
|
-
Array.from(milestonesHash)
|
|
3724
|
-
)
|
|
3725
|
-
.accounts({
|
|
3726
|
-
user: walletKey,
|
|
3727
|
-
escrow: escrowPda,
|
|
3728
|
-
})
|
|
3729
|
-
.signers([keypair])
|
|
3730
|
-
.rpc();
|
|
3731
|
-
console.log(`Milestones committed. (tx: ${sig})`);
|
|
3732
|
-
didUpdate = true;
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
|
|
3736
|
-
if (hasTermsHash && fundingOk && !escrowState.fundingOk) {
|
|
3737
|
-
const sig = await program.methods
|
|
3738
|
-
.setFundingOkPublic(new anchor.BN(escrowId.toString()))
|
|
3739
|
-
.accounts({
|
|
3740
|
-
user: walletKey,
|
|
3741
|
-
escrow: escrowPda,
|
|
3742
|
-
terms: termsPda,
|
|
3743
|
-
})
|
|
3744
|
-
.signers([keypair])
|
|
3745
|
-
.rpc();
|
|
3746
|
-
console.log(`Funding verified. (tx: ${sig})`);
|
|
3747
|
-
didUpdate = true;
|
|
3748
|
-
}
|
|
3749
|
-
|
|
3750
|
-
let readyToClaimSet = false;
|
|
3751
|
-
if (hasTermsHash && fundingOk && allApproved && !escrowState.readyToClaim) {
|
|
3752
|
-
const sig = await program.methods
|
|
3753
|
-
.setReadyToClaimPublic(new anchor.BN(escrowId.toString()), milestoneCount)
|
|
3754
|
-
.accounts({
|
|
3755
|
-
user: walletKey,
|
|
3756
|
-
escrow: escrowPda,
|
|
3757
|
-
})
|
|
3758
|
-
.remainingAccounts(milestoneAccounts)
|
|
3759
|
-
.signers([keypair])
|
|
3760
|
-
.rpc();
|
|
3761
|
-
console.log(`Ready to claim set. (tx: ${sig})`);
|
|
3762
|
-
didUpdate = true;
|
|
3763
|
-
readyToClaimSet = true;
|
|
3764
|
-
}
|
|
3765
|
-
|
|
3766
|
-
if (!readyToClaimSet && deadlinePassed && fundingOk && hasUnsubmitted && !escrowState.timeoutRefundReady) {
|
|
3767
|
-
const sig = await program.methods
|
|
3768
|
-
.setTimeoutRefundReadyPublic(
|
|
3769
|
-
new anchor.BN(escrowId.toString()),
|
|
3770
|
-
milestoneCount
|
|
3771
|
-
)
|
|
3772
|
-
.accounts({
|
|
3773
|
-
user: walletKey,
|
|
3774
|
-
escrow: escrowPda,
|
|
3775
|
-
terms: termsPda,
|
|
3776
|
-
})
|
|
3777
|
-
.remainingAccounts(milestoneAccounts)
|
|
3778
|
-
.signers([keypair])
|
|
3779
|
-
.rpc();
|
|
3780
|
-
console.log(`Timeout refund ready set. (tx: ${sig})`);
|
|
3781
|
-
didUpdate = true;
|
|
3782
|
-
}
|
|
3783
|
-
|
|
3784
|
-
if (!readyToClaimSet && deadlinePassed && fundingOk && allSubmitted && !escrowState.timeoutFundsReady) {
|
|
3785
|
-
const sig = await program.methods
|
|
3786
|
-
.setTimeoutFundsReadyPublic(
|
|
3787
|
-
new anchor.BN(escrowId.toString()),
|
|
3788
|
-
milestoneCount
|
|
3789
|
-
)
|
|
3790
|
-
.accounts({
|
|
3791
|
-
user: walletKey,
|
|
3792
|
-
escrow: escrowPda,
|
|
3793
|
-
terms: termsPda,
|
|
3794
|
-
})
|
|
3795
|
-
.remainingAccounts(milestoneAccounts)
|
|
3796
|
-
.signers([keypair])
|
|
3797
|
-
.rpc();
|
|
3798
|
-
console.log(`Timeout funds ready set. (tx: ${sig})`);
|
|
3799
|
-
didUpdate = true;
|
|
3800
|
-
}
|
|
3801
|
-
|
|
3802
|
-
if (didUpdate) {
|
|
3803
|
-
successMessage("Sync completed.");
|
|
3804
|
-
return;
|
|
3805
|
-
}
|
|
3806
|
-
console.log("No updates were required.");
|
|
3807
|
-
};
|
|
3808
|
-
|
|
3809
3023
|
const runSyncFlags = async (config, contract, options) => {
|
|
3810
3024
|
const { keypair, walletKey } = getWalletContext(config);
|
|
3811
3025
|
if (!contract.escrow_pda) {
|
|
3812
3026
|
console.error("Contract has no escrow.");
|
|
3813
3027
|
process.exit(1);
|
|
3814
3028
|
}
|
|
3815
|
-
if (isL1Mode(contract)) {
|
|
3816
|
-
await runSyncFlagsL1(config, contract, options);
|
|
3817
|
-
return;
|
|
3818
|
-
}
|
|
3819
3029
|
|
|
3820
3030
|
const { program, connection, programId } = getProgram(config, keypair);
|
|
3821
3031
|
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
@@ -3826,12 +3036,13 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
3826
3036
|
}
|
|
3827
3037
|
|
|
3828
3038
|
const escrowId = escrowState.escrowId;
|
|
3829
|
-
const { termsPda, program:
|
|
3039
|
+
const { termsPda, program: baseProgram } = await ensureTermsPrepared(
|
|
3830
3040
|
config,
|
|
3831
3041
|
contract,
|
|
3832
3042
|
keypair,
|
|
3833
3043
|
escrowPda,
|
|
3834
|
-
escrowId
|
|
3044
|
+
escrowId,
|
|
3045
|
+
options
|
|
3835
3046
|
);
|
|
3836
3047
|
|
|
3837
3048
|
const milestoneIndices = Array.isArray(contract.milestones)
|
|
@@ -3844,40 +3055,28 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
3844
3055
|
index,
|
|
3845
3056
|
programId
|
|
3846
3057
|
);
|
|
3847
|
-
const members = buildMembers(contract);
|
|
3848
|
-
members.accountType = {
|
|
3849
|
-
privateMilestone: {
|
|
3850
|
-
escrow: escrowPda,
|
|
3851
|
-
index,
|
|
3852
|
-
},
|
|
3853
|
-
};
|
|
3854
|
-
await ensurePermission(config, l1Program, keypair, privateMilestonePda, members);
|
|
3855
|
-
await ensureDelegatedPermission(
|
|
3856
|
-
config,
|
|
3857
|
-
l1Program.provider,
|
|
3858
|
-
keypair,
|
|
3859
|
-
privateMilestonePda
|
|
3860
|
-
);
|
|
3861
3058
|
await ensureDelegatedAccount(
|
|
3862
3059
|
config,
|
|
3863
|
-
|
|
3060
|
+
baseProgram,
|
|
3864
3061
|
keypair,
|
|
3865
|
-
|
|
3866
|
-
privateMilestonePda
|
|
3062
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
3063
|
+
privateMilestonePda,
|
|
3064
|
+
options
|
|
3867
3065
|
);
|
|
3868
3066
|
}
|
|
3869
3067
|
|
|
3870
3068
|
await ensureDelegatedEscrow(
|
|
3871
3069
|
config,
|
|
3872
|
-
|
|
3070
|
+
baseProgram,
|
|
3873
3071
|
keypair,
|
|
3874
3072
|
escrowId,
|
|
3875
3073
|
escrowPda,
|
|
3876
|
-
new PublicKey(contract.client_wallet)
|
|
3074
|
+
new PublicKey(contract.client_wallet),
|
|
3075
|
+
options
|
|
3877
3076
|
);
|
|
3878
3077
|
|
|
3879
3078
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
3880
|
-
await getPerProgramBundle(config, keypair, programId,
|
|
3079
|
+
await getPerProgramBundle(config, keypair, programId, baseProgram.provider);
|
|
3881
3080
|
|
|
3882
3081
|
let terms;
|
|
3883
3082
|
try {
|
|
@@ -3963,7 +3162,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
3963
3162
|
})
|
|
3964
3163
|
.signers([sessionSigner])
|
|
3965
3164
|
.rpc({ skipPreflight: true });
|
|
3966
|
-
|
|
3165
|
+
logTx("Milestones committed.", sig, false);
|
|
3967
3166
|
didUpdate = true;
|
|
3968
3167
|
}
|
|
3969
3168
|
}
|
|
@@ -3980,7 +3179,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
3980
3179
|
})
|
|
3981
3180
|
.signers([sessionSigner])
|
|
3982
3181
|
.rpc({ skipPreflight: true });
|
|
3983
|
-
|
|
3182
|
+
logTx("Funding verified.", sig, false);
|
|
3984
3183
|
didUpdate = true;
|
|
3985
3184
|
}
|
|
3986
3185
|
|
|
@@ -4000,7 +3199,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
4000
3199
|
.remainingAccounts(milestoneAccounts)
|
|
4001
3200
|
.signers([sessionSigner])
|
|
4002
3201
|
.rpc({ skipPreflight: true });
|
|
4003
|
-
|
|
3202
|
+
logTx("Ready to claim set.", sig, false);
|
|
4004
3203
|
didUpdate = true;
|
|
4005
3204
|
readyToClaimSet = true;
|
|
4006
3205
|
}
|
|
@@ -4027,7 +3226,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
4027
3226
|
.remainingAccounts(milestoneAccounts)
|
|
4028
3227
|
.signers([sessionSigner])
|
|
4029
3228
|
.rpc({ skipPreflight: true });
|
|
4030
|
-
|
|
3229
|
+
logTx("Timeout refund ready set.", sig, false);
|
|
4031
3230
|
didUpdate = true;
|
|
4032
3231
|
}
|
|
4033
3232
|
|
|
@@ -4053,7 +3252,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
4053
3252
|
.remainingAccounts(milestoneAccounts)
|
|
4054
3253
|
.signers([sessionSigner])
|
|
4055
3254
|
.rpc({ skipPreflight: true });
|
|
4056
|
-
|
|
3255
|
+
logTx("Timeout funds ready set.", sig, false);
|
|
4057
3256
|
didUpdate = true;
|
|
4058
3257
|
}
|
|
4059
3258
|
|
|
@@ -4069,7 +3268,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
4069
3268
|
})
|
|
4070
3269
|
.signers([sessionSigner])
|
|
4071
3270
|
.rpc({ skipPreflight: true });
|
|
4072
|
-
|
|
3271
|
+
logTx("Escrow committed.", sig, false);
|
|
4073
3272
|
} else {
|
|
4074
3273
|
console.log("No escrow flag changes needed.");
|
|
4075
3274
|
}
|
|
@@ -4116,29 +3315,17 @@ const runMilestoneList = async (config, contract) => {
|
|
|
4116
3315
|
milestoneIndices.sort((a, b) => a - b);
|
|
4117
3316
|
|
|
4118
3317
|
let milestones = [];
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
3318
|
+
const { program: erProgram } = await getEphemeralProgram(config, keypair);
|
|
3319
|
+
try {
|
|
3320
|
+
milestones = await fetchPrivateMilestones(
|
|
3321
|
+
erProgram,
|
|
4122
3322
|
escrowPda,
|
|
4123
3323
|
programId,
|
|
4124
3324
|
milestoneIndices
|
|
4125
3325
|
);
|
|
4126
|
-
}
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
keypair
|
|
4130
|
-
);
|
|
4131
|
-
try {
|
|
4132
|
-
milestones = await fetchPrivateMilestones(
|
|
4133
|
-
erProgram,
|
|
4134
|
-
escrowPda,
|
|
4135
|
-
programId,
|
|
4136
|
-
milestoneIndices
|
|
4137
|
-
);
|
|
4138
|
-
} catch (error) {
|
|
4139
|
-
console.error("Private milestones are not available yet.");
|
|
4140
|
-
process.exit(1);
|
|
4141
|
-
}
|
|
3326
|
+
} catch (error) {
|
|
3327
|
+
console.error("Private milestones are not available yet.");
|
|
3328
|
+
process.exit(1);
|
|
4142
3329
|
}
|
|
4143
3330
|
|
|
4144
3331
|
const metadataByIndex = new Map(
|
|
@@ -4191,31 +3378,18 @@ const runMilestoneStatus = async (config, contract, number) => {
|
|
|
4191
3378
|
}
|
|
4192
3379
|
|
|
4193
3380
|
let milestone;
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
3381
|
+
const { program: erProgram } = await getEphemeralProgram(config, keypair);
|
|
3382
|
+
try {
|
|
3383
|
+
const items = await fetchPrivateMilestones(
|
|
3384
|
+
erProgram,
|
|
4197
3385
|
escrowPda,
|
|
4198
3386
|
programId,
|
|
4199
3387
|
[index]
|
|
4200
3388
|
);
|
|
4201
3389
|
milestone = items[0];
|
|
4202
|
-
}
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
keypair
|
|
4206
|
-
);
|
|
4207
|
-
try {
|
|
4208
|
-
const items = await fetchPrivateMilestones(
|
|
4209
|
-
erProgram,
|
|
4210
|
-
escrowPda,
|
|
4211
|
-
programId,
|
|
4212
|
-
[index]
|
|
4213
|
-
);
|
|
4214
|
-
milestone = items[0];
|
|
4215
|
-
} catch (error) {
|
|
4216
|
-
console.error("Private milestone is not available yet.");
|
|
4217
|
-
process.exit(1);
|
|
4218
|
-
}
|
|
3390
|
+
} catch (error) {
|
|
3391
|
+
console.error("Private milestone is not available yet.");
|
|
3392
|
+
process.exit(1);
|
|
4219
3393
|
}
|
|
4220
3394
|
|
|
4221
3395
|
const meta = (contract.milestones || []).find((m) => m.index === index) || {};
|
|
@@ -4258,7 +3432,7 @@ const runContractDetails = async (config, contract) => {
|
|
|
4258
3432
|
console.log("Contract details");
|
|
4259
3433
|
console.log(`ID: ${contract.id}`);
|
|
4260
3434
|
console.log(`Status: ${contract.status}`);
|
|
4261
|
-
console.log(
|
|
3435
|
+
console.log("Mode: PER");
|
|
4262
3436
|
console.log(`Role: ${role}`);
|
|
4263
3437
|
console.log(
|
|
4264
3438
|
formatParticipant(
|
|
@@ -4279,14 +3453,10 @@ const runContractDetails = async (config, contract) => {
|
|
|
4279
3453
|
console.log(`Escrow: ${contract.escrow_pda || "n/a"}`);
|
|
4280
3454
|
console.log(`Mint: ${contract.mint || "n/a"}`);
|
|
4281
3455
|
console.log(`Vault: ${contract.vault_token || "n/a"}`);
|
|
4282
|
-
const privacyLabel =
|
|
4283
|
-
? "l1"
|
|
4284
|
-
: contract.privacyReady
|
|
4285
|
-
? "ready"
|
|
4286
|
-
: "pending";
|
|
3456
|
+
const privacyLabel = contract.privacyReady ? "ready" : "pending";
|
|
4287
3457
|
console.log(`Privacy: ${privacyLabel}`);
|
|
4288
3458
|
|
|
4289
|
-
if (
|
|
3459
|
+
if (contract.terms_encrypted && !contract.privacyReady) {
|
|
4290
3460
|
console.log("Terms: encrypted (waiting on privacy keys)");
|
|
4291
3461
|
} else {
|
|
4292
3462
|
console.log(`Deadline: ${contract.deadline ? formatDeadlineValue(contract.deadline) : "missing"}`);
|
|
@@ -4346,7 +3516,7 @@ const runContractStatus = async (config, contract) => {
|
|
|
4346
3516
|
console.log(`Role: ${role}`);
|
|
4347
3517
|
}
|
|
4348
3518
|
console.log(`Current status: ${contract.status}`);
|
|
4349
|
-
console.log(
|
|
3519
|
+
console.log("Mode: PER");
|
|
4350
3520
|
if (contract.created_at) {
|
|
4351
3521
|
console.log(`Created: ${formatCreatedAt(contract.created_at)}`);
|
|
4352
3522
|
}
|
|
@@ -4467,46 +3637,6 @@ const runRateContract = async (config, contract, scoreValue) => {
|
|
|
4467
3637
|
console.log(`Rating submitted: ${score} star${score === 1 ? "" : "s"}.`);
|
|
4468
3638
|
};
|
|
4469
3639
|
|
|
4470
|
-
const runContractMode = async (config, contract, mode, options) => {
|
|
4471
|
-
const { wallet } = getWalletContext(config);
|
|
4472
|
-
const role = getRole(contract, wallet);
|
|
4473
|
-
if (role !== "client" && role !== "contractor") {
|
|
4474
|
-
console.error("Only contract participants can change execution mode.");
|
|
4475
|
-
process.exit(1);
|
|
4476
|
-
}
|
|
4477
|
-
const desired = (mode || "").toString().toLowerCase();
|
|
4478
|
-
if (desired !== "per" && desired !== "l1") {
|
|
4479
|
-
console.error("Usage: nebulon contract <id> mode per|l1");
|
|
4480
|
-
return;
|
|
4481
|
-
}
|
|
4482
|
-
const allowedStatuses = new Set(["waiting_for_init", "negotiating", "awaiting_signatures"]);
|
|
4483
|
-
if (!allowedStatuses.has(contract.status)) {
|
|
4484
|
-
console.error("Execution mode can only be changed before funding.");
|
|
4485
|
-
return;
|
|
4486
|
-
}
|
|
4487
|
-
if (desired === getExecutionMode(contract)) {
|
|
4488
|
-
console.log(`Execution mode is already ${desired.toUpperCase()}.`);
|
|
4489
|
-
return;
|
|
4490
|
-
}
|
|
4491
|
-
const proceed = await confirmAction(
|
|
4492
|
-
options.confirm,
|
|
4493
|
-
`Set execution mode to ${desired.toUpperCase()}? yes/no`
|
|
4494
|
-
);
|
|
4495
|
-
if (!proceed) {
|
|
4496
|
-
console.log("Canceled.");
|
|
4497
|
-
return;
|
|
4498
|
-
}
|
|
4499
|
-
await updateContract(config.backendUrl, config.auth.token, contract.id, {
|
|
4500
|
-
executionMode: desired,
|
|
4501
|
-
});
|
|
4502
|
-
contract.execution_mode = desired;
|
|
4503
|
-
if (desired === "l1") {
|
|
4504
|
-
console.log("L1 MODE enabled for this contract (no TEE privacy).");
|
|
4505
|
-
} else {
|
|
4506
|
-
console.log("PER mode enabled for this contract.");
|
|
4507
|
-
}
|
|
4508
|
-
};
|
|
4509
|
-
|
|
4510
3640
|
const runCheckTerms = async (config, contract) => {
|
|
4511
3641
|
const { wallet } = getWalletContext(config);
|
|
4512
3642
|
const role = getRole(contract, wallet);
|
|
@@ -4514,7 +3644,7 @@ const runCheckTerms = async (config, contract) => {
|
|
|
4514
3644
|
console.error("Only contract participants can view terms.");
|
|
4515
3645
|
process.exit(1);
|
|
4516
3646
|
}
|
|
4517
|
-
if (
|
|
3647
|
+
if (contract.terms_encrypted && !contract.privacyReady) {
|
|
4518
3648
|
console.error("Privacy key exchange pending. Unable to read terms.");
|
|
4519
3649
|
process.exit(1);
|
|
4520
3650
|
}
|
|
@@ -4580,7 +3710,7 @@ const runOpenDispute = async (config, contract, options) => {
|
|
|
4580
3710
|
})
|
|
4581
3711
|
.signers([keypair])
|
|
4582
3712
|
.rpc();
|
|
4583
|
-
|
|
3713
|
+
logTx("Dispute opened.", sig, false);
|
|
4584
3714
|
console.log("Contact @nortbyt3 on Discord to discuss your case.");
|
|
4585
3715
|
successMessage("Dispute opened.");
|
|
4586
3716
|
} catch (error) {
|
|
@@ -4673,7 +3803,7 @@ const runResolveDispute = async (
|
|
|
4673
3803
|
})
|
|
4674
3804
|
.signers([keypair])
|
|
4675
3805
|
.rpc();
|
|
4676
|
-
|
|
3806
|
+
logTx("Dispute resolved.", sig, false);
|
|
4677
3807
|
successMessage("Dispute resolved.");
|
|
4678
3808
|
} catch (error) {
|
|
4679
3809
|
const message = (error && error.message ? error.message : "").toLowerCase();
|
|
@@ -4691,6 +3821,7 @@ const runResolveDispute = async (
|
|
|
4691
3821
|
|
|
4692
3822
|
const runContractCommand = async (args, options = {}) => {
|
|
4693
3823
|
const config = loadConfig();
|
|
3824
|
+
config.__verbose = Boolean(options && options.verbose);
|
|
4694
3825
|
await ensureHosted(config);
|
|
4695
3826
|
|
|
4696
3827
|
if (!args.length) {
|
|
@@ -4810,10 +3941,6 @@ const runContractCommand = async (args, options = {}) => {
|
|
|
4810
3941
|
await runRateContract(config, contract, rest[1]);
|
|
4811
3942
|
return;
|
|
4812
3943
|
}
|
|
4813
|
-
if (action === "mode") {
|
|
4814
|
-
await runContractMode(config, contract, rest[1], options);
|
|
4815
|
-
return;
|
|
4816
|
-
}
|
|
4817
3944
|
|
|
4818
3945
|
if (action === "disable" && rest[1] === "milestone") {
|
|
4819
3946
|
if (!rest[2]) {
|