nebulon-escrow-cli 0.1.0 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -4
- package/package.json +2 -2
- package/src/cli.js +2 -2
- package/src/commands/contract.js +422 -1239
- package/src/commands/init.js +11 -15
- package/src/constants.js +8 -7
- 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,78 @@ 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
|
-
});
|
|
157
|
+
return getProgram(config, signerKeypair, { endpoint, wsEndpoint });
|
|
197
158
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const now = Math.floor(Date.now() / 1000);
|
|
201
|
-
if (!options.forceRefresh && cached && cached.expiresAt > now + 30) {
|
|
202
|
-
return cached.bundle;
|
|
159
|
+
if (options.forceRefresh) {
|
|
160
|
+
return getProgram(config, signerKeypair, { endpoint, wsEndpoint });
|
|
203
161
|
}
|
|
204
|
-
|
|
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;
|
|
162
|
+
return getProgram(config, signerKeypair, { endpoint, wsEndpoint });
|
|
227
163
|
};
|
|
228
164
|
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
165
|
+
const getDelegationValidator = async (config) => {
|
|
166
|
+
const endpoint = (config.ephemeralProviderUrl || "").toLowerCase();
|
|
167
|
+
const wsEndpoint = (config.ephemeralWsUrl || "").toLowerCase();
|
|
168
|
+
if (config && config.__verbose) {
|
|
169
|
+
console.log("DEBUG: delegation resolver endpoints", { endpoint, wsEndpoint });
|
|
232
170
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const cached = teeWsHealthCache.get(cacheKey);
|
|
236
|
-
if (cached && cached.expiresAt > now) {
|
|
237
|
-
return;
|
|
171
|
+
if (endpoint.includes("localhost") || endpoint.includes("127.0.0.1")) {
|
|
172
|
+
return LOCAL_VALIDATOR_IDENTITY;
|
|
238
173
|
}
|
|
239
174
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
console.log("TEE WS health check: subscribing for slot change...");
|
|
175
|
+
const safePublicKey = (value) => {
|
|
176
|
+
try {
|
|
177
|
+
return value ? new PublicKey(value) : null;
|
|
178
|
+
} catch {
|
|
179
|
+
return null;
|
|
246
180
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const shouldUseRouter =
|
|
184
|
+
endpoint.includes("router") ||
|
|
185
|
+
wsEndpoint.includes("router") ||
|
|
186
|
+
endpoint.includes("magicblock.app") ||
|
|
187
|
+
wsEndpoint.includes("magicblock.app");
|
|
188
|
+
if (shouldUseRouter && config.ephemeralProviderUrl) {
|
|
189
|
+
const cacheKey = `${endpoint}|${wsEndpoint}`;
|
|
190
|
+
if (cachedValidator && cachedValidatorEndpoint === cacheKey) {
|
|
191
|
+
if (config && config.__verbose) {
|
|
192
|
+
console.log("DEBUG: using cached validator", cachedValidator.toBase58());
|
|
253
193
|
}
|
|
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");
|
|
194
|
+
return cachedValidator;
|
|
279
195
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
196
|
+
try {
|
|
197
|
+
const router = new ConnectionMagicRouter(config.ephemeralProviderUrl, {
|
|
198
|
+
wsEndpoint: config.ephemeralWsUrl || undefined,
|
|
199
|
+
});
|
|
200
|
+
const closest = await router.getClosestValidator();
|
|
201
|
+
if (config && config.__verbose) {
|
|
202
|
+
console.log("DEBUG: router closest validator:", closest);
|
|
287
203
|
}
|
|
204
|
+
const identity =
|
|
205
|
+
closest?.validatorIdentity || closest?.identity || closest?.validator;
|
|
206
|
+
const candidate = safePublicKey(identity || closest?.pubkey || closest);
|
|
207
|
+
if (candidate) {
|
|
208
|
+
cachedValidator = candidate;
|
|
209
|
+
cachedValidatorEndpoint = cacheKey;
|
|
210
|
+
return cachedValidator;
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
// fall back to configured identity below
|
|
288
214
|
}
|
|
289
215
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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;
|
|
334
|
-
}
|
|
335
|
-
try {
|
|
336
|
-
const ready = await waitUntilPermissionActive(endpoint, pda, 20_000);
|
|
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
|
-
);
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
const getDelegationValidator = async (config) => {
|
|
353
|
-
const endpoint = (config.ephemeralProviderUrl || "").toLowerCase();
|
|
354
|
-
if (endpoint.includes("localhost") || endpoint.includes("127.0.0.1")) {
|
|
355
|
-
return LOCAL_VALIDATOR_IDENTITY;
|
|
356
|
-
}
|
|
357
|
-
if (endpoint.includes("router")) {
|
|
358
|
-
if (cachedValidator && cachedValidatorEndpoint === endpoint) {
|
|
359
|
-
return cachedValidator;
|
|
216
|
+
const fallback = safePublicKey(config.ephemeralValidatorIdentity);
|
|
217
|
+
if (fallback) {
|
|
218
|
+
if (config && config.__verbose) {
|
|
219
|
+
console.log("DEBUG: using configured validator identity", fallback.toBase58());
|
|
360
220
|
}
|
|
361
|
-
|
|
362
|
-
wsEndpoint: config.ephemeralWsUrl || undefined,
|
|
363
|
-
});
|
|
364
|
-
const closest = await router.getClosestValidator();
|
|
365
|
-
cachedValidator = new PublicKey(closest.pubkey || closest.validator || closest);
|
|
366
|
-
cachedValidatorEndpoint = endpoint;
|
|
367
|
-
return cachedValidator;
|
|
221
|
+
return fallback;
|
|
368
222
|
}
|
|
369
|
-
if (config.
|
|
370
|
-
|
|
223
|
+
if (config && config.__verbose) {
|
|
224
|
+
console.warn("DEBUG: no validator identity resolved.");
|
|
371
225
|
}
|
|
372
226
|
return null;
|
|
373
227
|
};
|
|
@@ -454,30 +308,6 @@ const ensureEscrowUndelegated = async (config, keypair, escrowPda) => {
|
|
|
454
308
|
.rpc({ skipPreflight: true });
|
|
455
309
|
};
|
|
456
310
|
|
|
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
311
|
const buildPrivacyContext = (scope, contractId, index) => {
|
|
482
312
|
const tail = index === undefined ? "" : `:${index}`;
|
|
483
313
|
return `nebulon:${scope}:v1:${contractId}${tail}`;
|
|
@@ -748,52 +578,13 @@ const parsePublicKey = (value, label) => {
|
|
|
748
578
|
}
|
|
749
579
|
};
|
|
750
580
|
|
|
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";
|
|
581
|
+
const ensureExecutionMode = async (config) => {
|
|
582
|
+
if (!isLocalnetConfig(config) && !config.ephemeralProviderUrl) {
|
|
583
|
+
console.warn(
|
|
584
|
+
"Warning: MagicBlock ER endpoint is not configured. Run `nebulon init` to fetch endpoints."
|
|
585
|
+
);
|
|
789
586
|
}
|
|
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";
|
|
587
|
+
return "per";
|
|
797
588
|
};
|
|
798
589
|
|
|
799
590
|
const feeFromGross = (amount) => (amount * FEE_BPS) / BPS_DENOMINATOR;
|
|
@@ -821,6 +612,15 @@ const normalizeHashBytes = (value) => {
|
|
|
821
612
|
return Buffer.from(String(value), "utf8").subarray(0, 32);
|
|
822
613
|
};
|
|
823
614
|
|
|
615
|
+
const logProgress = (message) => {
|
|
616
|
+
console.log(chalk.gray(message));
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
const logTx = (label, sig, isEr = false) => {
|
|
620
|
+
const prefix = isEr ? "ER-tx" : "tx";
|
|
621
|
+
console.log(`${label} (${prefix}: ${sig})`);
|
|
622
|
+
};
|
|
623
|
+
|
|
824
624
|
const buildMilestonesHash = (milestones) => {
|
|
825
625
|
const hash = crypto.createHash("sha256");
|
|
826
626
|
const ordered = [...milestones].sort((a, b) => a.index - b.index);
|
|
@@ -882,15 +682,6 @@ const getPrivateMilestoneStatus = async (
|
|
|
882
682
|
) => {
|
|
883
683
|
const { program, programId } = getProgram(config, keypair);
|
|
884
684
|
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
685
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
895
686
|
await getPerProgramBundle(config, keypair, programId, program.provider);
|
|
896
687
|
const milestones = await fetchPrivateMilestones(
|
|
@@ -1248,31 +1039,24 @@ const ensureSessionToken = async (config, provider, programId, authorityKey) =>
|
|
|
1248
1039
|
};
|
|
1249
1040
|
|
|
1250
1041
|
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 };
|
|
1042
|
+
const { sessionSigner, sessionPda } = await ensureSessionToken(
|
|
1043
|
+
config,
|
|
1044
|
+
program.provider,
|
|
1045
|
+
program.programId,
|
|
1046
|
+
keypair.publicKey
|
|
1047
|
+
);
|
|
1048
|
+
return { signer: sessionSigner, sessionPda, payer: sessionSigner.publicKey };
|
|
1261
1049
|
};
|
|
1262
1050
|
|
|
1263
1051
|
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 };
|
|
1052
|
+
const { sessionSigner, sessionPda } = await ensureSessionToken(
|
|
1053
|
+
config,
|
|
1054
|
+
provider,
|
|
1055
|
+
programId,
|
|
1056
|
+
keypair.publicKey
|
|
1057
|
+
);
|
|
1058
|
+
const { program } = await getEphemeralProgram(config, sessionSigner);
|
|
1059
|
+
return { program, sessionSigner, sessionPda };
|
|
1276
1060
|
};
|
|
1277
1061
|
|
|
1278
1062
|
const isAlreadyExistsError = (error) => {
|
|
@@ -1284,91 +1068,28 @@ const isAlreadyExistsError = (error) => {
|
|
|
1284
1068
|
);
|
|
1285
1069
|
};
|
|
1286
1070
|
|
|
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
|
-
};
|
|
1071
|
+
const isVerbose = (options) => Boolean(options && options.verbose);
|
|
1359
1072
|
|
|
1360
1073
|
const ensureDelegatedAccount = async (
|
|
1361
1074
|
config,
|
|
1362
1075
|
program,
|
|
1363
1076
|
keypair,
|
|
1364
1077
|
accountType,
|
|
1365
|
-
pda
|
|
1078
|
+
pda,
|
|
1079
|
+
options
|
|
1366
1080
|
) => {
|
|
1367
|
-
if (config && config.__l1_mode) {
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
1081
|
const info = await program.provider.connection.getAccountInfo(pda, "confirmed");
|
|
1082
|
+
if (isVerbose(options)) {
|
|
1083
|
+
console.log("DEBUG: delegation check", {
|
|
1084
|
+
pda: pda.toBase58(),
|
|
1085
|
+
owner: info?.owner?.toBase58?.(),
|
|
1086
|
+
delegated: Boolean(info && info.owner.equals(DELEGATION_PROGRAM_ID)),
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1371
1089
|
if (info && info.owner.equals(DELEGATION_PROGRAM_ID)) {
|
|
1090
|
+
if (isVerbose(options)) {
|
|
1091
|
+
console.log(`Delegation already active for ${pda.toBase58()}; skipping.`);
|
|
1092
|
+
}
|
|
1372
1093
|
return;
|
|
1373
1094
|
}
|
|
1374
1095
|
const isPerVault = accountType && Object.prototype.hasOwnProperty.call(accountType, "perVault");
|
|
@@ -1383,6 +1104,13 @@ const ensureDelegatedAccount = async (
|
|
|
1383
1104
|
}
|
|
1384
1105
|
try {
|
|
1385
1106
|
const validator = await getDelegationValidator(config);
|
|
1107
|
+
if (validator) {
|
|
1108
|
+
if (isVerbose(options)) {
|
|
1109
|
+
console.log(`Delegating account to validator: ${validator.toBase58()}`);
|
|
1110
|
+
}
|
|
1111
|
+
} else if (isVerbose(options)) {
|
|
1112
|
+
console.warn("Delegation skipped: no validator identity resolved.");
|
|
1113
|
+
}
|
|
1386
1114
|
const remainingAccounts = validator
|
|
1387
1115
|
? [{ pubkey: validator, isWritable: false, isSigner: false }]
|
|
1388
1116
|
: [];
|
|
@@ -1394,7 +1122,7 @@ const ensureDelegatedAccount = async (
|
|
|
1394
1122
|
})
|
|
1395
1123
|
.remainingAccounts(remainingAccounts)
|
|
1396
1124
|
.signers([keypair])
|
|
1397
|
-
.rpc();
|
|
1125
|
+
.rpc({ skipPreflight: true });
|
|
1398
1126
|
} catch (error) {
|
|
1399
1127
|
if (!isAlreadyExistsError(error)) {
|
|
1400
1128
|
throw error;
|
|
@@ -1408,16 +1136,24 @@ const ensureDelegatedEscrow = async (
|
|
|
1408
1136
|
keypair,
|
|
1409
1137
|
escrowId,
|
|
1410
1138
|
escrowPda,
|
|
1411
|
-
client
|
|
1139
|
+
client,
|
|
1140
|
+
options
|
|
1412
1141
|
) => {
|
|
1413
|
-
if (config && config.__l1_mode) {
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
1142
|
const info = await program.provider.connection.getAccountInfo(
|
|
1417
1143
|
escrowPda,
|
|
1418
1144
|
"confirmed"
|
|
1419
1145
|
);
|
|
1146
|
+
if (isVerbose(options)) {
|
|
1147
|
+
console.log("DEBUG: escrow delegation check", {
|
|
1148
|
+
escrow: escrowPda.toBase58(),
|
|
1149
|
+
owner: info?.owner?.toBase58?.(),
|
|
1150
|
+
delegated: Boolean(info && info.owner.equals(DELEGATION_PROGRAM_ID)),
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1420
1153
|
if (info && info.owner.equals(DELEGATION_PROGRAM_ID)) {
|
|
1154
|
+
if (isVerbose(options)) {
|
|
1155
|
+
console.log(`Delegation already active for ${escrowPda.toBase58()}; skipping.`);
|
|
1156
|
+
}
|
|
1421
1157
|
return;
|
|
1422
1158
|
}
|
|
1423
1159
|
if (info && !info.owner.equals(program.programId)) {
|
|
@@ -1427,6 +1163,13 @@ const ensureDelegatedEscrow = async (
|
|
|
1427
1163
|
}
|
|
1428
1164
|
try {
|
|
1429
1165
|
const validator = await getDelegationValidator(config);
|
|
1166
|
+
if (validator) {
|
|
1167
|
+
if (isVerbose(options)) {
|
|
1168
|
+
console.log(`Delegating escrow to validator: ${validator.toBase58()}`);
|
|
1169
|
+
}
|
|
1170
|
+
} else if (isVerbose(options)) {
|
|
1171
|
+
console.warn("Delegation skipped: no validator identity resolved.");
|
|
1172
|
+
}
|
|
1430
1173
|
const remainingAccounts = validator
|
|
1431
1174
|
? [{ pubkey: validator, isWritable: false, isSigner: false }]
|
|
1432
1175
|
: [];
|
|
@@ -1439,7 +1182,7 @@ const ensureDelegatedEscrow = async (
|
|
|
1439
1182
|
})
|
|
1440
1183
|
.remainingAccounts(remainingAccounts)
|
|
1441
1184
|
.signers([keypair])
|
|
1442
|
-
.rpc();
|
|
1185
|
+
.rpc({ skipPreflight: true });
|
|
1443
1186
|
} catch (error) {
|
|
1444
1187
|
if (!isAlreadyExistsError(error)) {
|
|
1445
1188
|
throw error;
|
|
@@ -1447,16 +1190,6 @@ const ensureDelegatedEscrow = async (
|
|
|
1447
1190
|
}
|
|
1448
1191
|
};
|
|
1449
1192
|
|
|
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
1193
|
const ensureAta = async (connection, payer, owner, mint) => {
|
|
1461
1194
|
const ata = await getAssociatedTokenAddress(mint, owner, true);
|
|
1462
1195
|
try {
|
|
@@ -1763,7 +1496,7 @@ const runInitContract = async (config, contract, options) => {
|
|
|
1763
1496
|
})
|
|
1764
1497
|
.signers([keypair])
|
|
1765
1498
|
.rpc();
|
|
1766
|
-
|
|
1499
|
+
logTx("Escrow initialized.", sig, false);
|
|
1767
1500
|
|
|
1768
1501
|
await linkEscrow(config.backendUrl, config.auth.token, contract.id, {
|
|
1769
1502
|
escrowPda: escrowPda.toBase58(),
|
|
@@ -1832,37 +1565,12 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1832
1565
|
return;
|
|
1833
1566
|
}
|
|
1834
1567
|
|
|
1835
|
-
|
|
1836
|
-
const l1Mode = executionMode === "l1";
|
|
1568
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
1837
1569
|
|
|
1838
1570
|
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
|
-
}
|
|
1571
|
+
logProgress("Preparing terms for ER submission");
|
|
1572
|
+
await sleep(200);
|
|
1573
|
+
logProgress("Encrypting milestone details");
|
|
1866
1574
|
let privacyKey;
|
|
1867
1575
|
try {
|
|
1868
1576
|
privacyKey = await getContractPrivacyKey(config, contract.id, wallet);
|
|
@@ -1878,6 +1586,10 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1878
1586
|
index,
|
|
1879
1587
|
title
|
|
1880
1588
|
);
|
|
1589
|
+
if (!isEncryptedPayload(encryptedDetails)) {
|
|
1590
|
+
console.error("Failed to encrypt milestone details.");
|
|
1591
|
+
process.exit(1);
|
|
1592
|
+
}
|
|
1881
1593
|
|
|
1882
1594
|
const encryptedBytes = Buffer.from(encryptedDetails, "utf8");
|
|
1883
1595
|
|
|
@@ -1893,6 +1605,7 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1893
1605
|
.update(encryptedDetails)
|
|
1894
1606
|
.digest();
|
|
1895
1607
|
|
|
1608
|
+
logProgress("Preparing milestone accounts");
|
|
1896
1609
|
const privateMilestonePda = derivePrivateMilestonePda(
|
|
1897
1610
|
escrowPda,
|
|
1898
1611
|
index,
|
|
@@ -1909,26 +1622,13 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1909
1622
|
.signers([keypair])
|
|
1910
1623
|
.rpc();
|
|
1911
1624
|
|
|
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
1625
|
await ensureDelegatedAccount(
|
|
1927
1626
|
config,
|
|
1928
1627
|
program,
|
|
1929
1628
|
keypair,
|
|
1930
|
-
|
|
1931
|
-
privateMilestonePda
|
|
1629
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
1630
|
+
privateMilestonePda,
|
|
1631
|
+
options
|
|
1932
1632
|
);
|
|
1933
1633
|
|
|
1934
1634
|
const perVaultPda = derivePerVaultPda(escrowPda, programId);
|
|
@@ -1937,7 +1637,8 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1937
1637
|
program,
|
|
1938
1638
|
keypair,
|
|
1939
1639
|
{ perVault: { escrow: escrowPda } },
|
|
1940
|
-
perVaultPda
|
|
1640
|
+
perVaultPda,
|
|
1641
|
+
options
|
|
1941
1642
|
);
|
|
1942
1643
|
await ensureDelegatedEscrow(
|
|
1943
1644
|
config,
|
|
@@ -1945,9 +1646,11 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1945
1646
|
keypair,
|
|
1946
1647
|
escrowId,
|
|
1947
1648
|
escrowPda,
|
|
1948
|
-
new PublicKey(contract.client_wallet)
|
|
1649
|
+
new PublicKey(contract.client_wallet),
|
|
1650
|
+
options
|
|
1949
1651
|
);
|
|
1950
1652
|
|
|
1653
|
+
logProgress("Submitting metadata on ER");
|
|
1951
1654
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
1952
1655
|
await getPerProgramBundle(config, keypair, programId, program.provider);
|
|
1953
1656
|
const metaSig = await erProgram.methods
|
|
@@ -1968,8 +1671,7 @@ const runAddMilestone = async (config, contract, title, options) => {
|
|
|
1968
1671
|
})
|
|
1969
1672
|
.signers([sessionSigner])
|
|
1970
1673
|
.rpc({ skipPreflight: true });
|
|
1971
|
-
|
|
1972
|
-
console.log(`Done. (tx: ${metaSig})`);
|
|
1674
|
+
logTx("Metadata submitted.", metaSig, true);
|
|
1973
1675
|
|
|
1974
1676
|
const milestones = Array.isArray(contract.milestones)
|
|
1975
1677
|
? [...contract.milestones]
|
|
@@ -2009,61 +1711,7 @@ const runDisableMilestone = async (config, contract, number, options) => {
|
|
|
2009
1711
|
console.error("Check the list with: nebulon contract <id> check milestone list");
|
|
2010
1712
|
return;
|
|
2011
1713
|
}
|
|
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
|
-
}
|
|
1714
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
2067
1715
|
if (!config.ephemeralProviderUrl) {
|
|
2068
1716
|
console.error(
|
|
2069
1717
|
"Warning: MagicBlock RPC is not configured; milestone status checks may be unreliable."
|
|
@@ -2085,6 +1733,47 @@ const runDisableMilestone = async (config, contract, number, options) => {
|
|
|
2085
1733
|
console.error("Current status: disabled");
|
|
2086
1734
|
return;
|
|
2087
1735
|
}
|
|
1736
|
+
const programBundle = getProgram(config, keypair);
|
|
1737
|
+
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
1738
|
+
const programId = programBundle.programId;
|
|
1739
|
+
const privateMilestonePda = derivePrivateMilestonePda(
|
|
1740
|
+
escrowPda,
|
|
1741
|
+
index,
|
|
1742
|
+
programId
|
|
1743
|
+
);
|
|
1744
|
+
await ensureDelegatedAccount(
|
|
1745
|
+
config,
|
|
1746
|
+
programBundle.program,
|
|
1747
|
+
keypair,
|
|
1748
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
1749
|
+
privateMilestonePda,
|
|
1750
|
+
options
|
|
1751
|
+
);
|
|
1752
|
+
const perVaultPda = derivePerVaultPda(escrowPda, programId);
|
|
1753
|
+
await ensureDelegatedAccount(
|
|
1754
|
+
config,
|
|
1755
|
+
programBundle.program,
|
|
1756
|
+
keypair,
|
|
1757
|
+
{ perVault: { escrow: escrowPda } },
|
|
1758
|
+
perVaultPda,
|
|
1759
|
+
options
|
|
1760
|
+
);
|
|
1761
|
+
const escrowState = await getEscrowState(
|
|
1762
|
+
programBundle.connection,
|
|
1763
|
+
programId,
|
|
1764
|
+
escrowPda
|
|
1765
|
+
);
|
|
1766
|
+
if (escrowState) {
|
|
1767
|
+
await ensureDelegatedEscrow(
|
|
1768
|
+
config,
|
|
1769
|
+
programBundle.program,
|
|
1770
|
+
keypair,
|
|
1771
|
+
escrowState.escrowId,
|
|
1772
|
+
escrowPda,
|
|
1773
|
+
new PublicKey(contract.client_wallet),
|
|
1774
|
+
options
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
2088
1777
|
let currentStatus = null;
|
|
2089
1778
|
try {
|
|
2090
1779
|
const milestone = await getPrivateMilestoneStatus(
|
|
@@ -2151,15 +1840,18 @@ const runDisableMilestone = async (config, contract, number, options) => {
|
|
|
2151
1840
|
}
|
|
2152
1841
|
|
|
2153
1842
|
console.log("Processing.");
|
|
1843
|
+
logProgress("Submitting milestone update on ER");
|
|
1844
|
+
await sleep(200);
|
|
2154
1845
|
const sig = await updatePrivateMilestoneStatus(
|
|
2155
1846
|
config,
|
|
2156
1847
|
contract,
|
|
2157
1848
|
keypair,
|
|
2158
1849
|
walletKey,
|
|
2159
1850
|
index,
|
|
2160
|
-
4
|
|
1851
|
+
4,
|
|
1852
|
+
options
|
|
2161
1853
|
);
|
|
2162
|
-
|
|
1854
|
+
logTx("Milestone disabled.", sig, true);
|
|
2163
1855
|
|
|
2164
1856
|
const nextMilestones = milestones.map((milestone) =>
|
|
2165
1857
|
milestone.index === index
|
|
@@ -2169,6 +1861,8 @@ const runDisableMilestone = async (config, contract, number, options) => {
|
|
|
2169
1861
|
await updateContractIfNegotiating(config, contract, {
|
|
2170
1862
|
milestones: nextMilestones,
|
|
2171
1863
|
});
|
|
1864
|
+
logProgress("Syncing private state to escrow flags");
|
|
1865
|
+
logProgress("Syncing private state to escrow flags");
|
|
2172
1866
|
await runSyncFlags(config, contract, { ...options, confirm: true });
|
|
2173
1867
|
successMessage("Milestone disabled.");
|
|
2174
1868
|
};
|
|
@@ -2178,7 +1872,8 @@ const ensureTermsPrepared = async (
|
|
|
2178
1872
|
contract,
|
|
2179
1873
|
keypair,
|
|
2180
1874
|
escrowPda,
|
|
2181
|
-
escrowId
|
|
1875
|
+
escrowId,
|
|
1876
|
+
options
|
|
2182
1877
|
) => {
|
|
2183
1878
|
const { program, programId } = getProgram(config, keypair);
|
|
2184
1879
|
const termsPda = deriveTermsPda(escrowPda, programId);
|
|
@@ -2193,17 +1888,13 @@ const ensureTermsPrepared = async (
|
|
|
2193
1888
|
.signers([keypair])
|
|
2194
1889
|
.rpc();
|
|
2195
1890
|
|
|
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
1891
|
await ensureDelegatedAccount(
|
|
2202
1892
|
config,
|
|
2203
1893
|
program,
|
|
2204
1894
|
keypair,
|
|
2205
|
-
|
|
2206
|
-
termsPda
|
|
1895
|
+
{ terms: { escrow: escrowPda } },
|
|
1896
|
+
termsPda,
|
|
1897
|
+
options
|
|
2207
1898
|
);
|
|
2208
1899
|
|
|
2209
1900
|
const perVaultPda = derivePerVaultPda(escrowPda, programId);
|
|
@@ -2212,7 +1903,8 @@ const ensureTermsPrepared = async (
|
|
|
2212
1903
|
program,
|
|
2213
1904
|
keypair,
|
|
2214
1905
|
{ perVault: { escrow: escrowPda } },
|
|
2215
|
-
perVaultPda
|
|
1906
|
+
perVaultPda,
|
|
1907
|
+
options
|
|
2216
1908
|
);
|
|
2217
1909
|
await ensureDelegatedEscrow(
|
|
2218
1910
|
config,
|
|
@@ -2220,7 +1912,8 @@ const ensureTermsPrepared = async (
|
|
|
2220
1912
|
keypair,
|
|
2221
1913
|
escrowId,
|
|
2222
1914
|
escrowPda,
|
|
2223
|
-
new PublicKey(contract.client_wallet)
|
|
1915
|
+
new PublicKey(contract.client_wallet),
|
|
1916
|
+
options
|
|
2224
1917
|
);
|
|
2225
1918
|
|
|
2226
1919
|
return { termsPda, perVaultPda, program, programId };
|
|
@@ -2234,19 +1927,26 @@ const submitTerms = async (
|
|
|
2234
1927
|
escrowId,
|
|
2235
1928
|
totalPayment,
|
|
2236
1929
|
deadline,
|
|
2237
|
-
encryptedTerms
|
|
1930
|
+
encryptedTerms,
|
|
1931
|
+
options
|
|
2238
1932
|
) => {
|
|
2239
1933
|
const { termsPda, perVaultPda, program, programId } = await ensureTermsPrepared(
|
|
2240
1934
|
config,
|
|
2241
1935
|
contract,
|
|
2242
1936
|
keypair,
|
|
2243
1937
|
escrowPda,
|
|
2244
|
-
escrowId
|
|
1938
|
+
escrowId,
|
|
1939
|
+
options
|
|
2245
1940
|
);
|
|
2246
1941
|
|
|
2247
1942
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
2248
1943
|
await getPerProgramBundle(config, keypair, programId, program.provider);
|
|
2249
1944
|
const encryptedBytes = Buffer.from(encryptedTerms || "", "utf8");
|
|
1945
|
+
if (encryptedBytes.length > 256) {
|
|
1946
|
+
throw new Error(
|
|
1947
|
+
`Encrypted terms too large (${encryptedBytes.length} bytes). Max is 256 bytes.`
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
2250
1950
|
const attemptCreate = async (forceRefresh = false) => {
|
|
2251
1951
|
const { program } = forceRefresh
|
|
2252
1952
|
? await getEphemeralProgram(config, keypair, { forceRefresh: true })
|
|
@@ -2275,7 +1975,7 @@ const submitTerms = async (
|
|
|
2275
1975
|
const attemptCreateDirect = async () => {
|
|
2276
1976
|
const { program: userErProgram } = await getEphemeralProgram(
|
|
2277
1977
|
config,
|
|
2278
|
-
|
|
1978
|
+
sessionSigner,
|
|
2279
1979
|
{ forceRefresh: true }
|
|
2280
1980
|
);
|
|
2281
1981
|
return userErProgram.methods
|
|
@@ -2329,148 +2029,6 @@ const submitTerms = async (
|
|
|
2329
2029
|
throw lastError;
|
|
2330
2030
|
};
|
|
2331
2031
|
|
|
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
2032
|
const runAddTerm = async (config, contract, field, value, options) => {
|
|
2475
2033
|
const { keypair, wallet, walletKey } = getWalletContext(config);
|
|
2476
2034
|
ensureEditableContract(contract);
|
|
@@ -2534,35 +2092,34 @@ const runAddTerm = async (config, contract, field, value, options) => {
|
|
|
2534
2092
|
return;
|
|
2535
2093
|
}
|
|
2536
2094
|
|
|
2537
|
-
|
|
2538
|
-
const l1Mode = executionMode === "l1";
|
|
2095
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
2539
2096
|
|
|
2540
2097
|
let encryptedTerms = null;
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
process.exit(1);
|
|
2555
|
-
}
|
|
2098
|
+
try {
|
|
2099
|
+
const privacyKey = await getContractPrivacyKey(config, contract.id, wallet);
|
|
2100
|
+
encryptedTerms = encryptTermsPayload(
|
|
2101
|
+
privacyKey,
|
|
2102
|
+
contract.id,
|
|
2103
|
+
deadline,
|
|
2104
|
+
totalPayment
|
|
2105
|
+
);
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
console.error(
|
|
2108
|
+
"Privacy key exchange pending. Wait for the counterparty to accept."
|
|
2109
|
+
);
|
|
2110
|
+
process.exit(1);
|
|
2556
2111
|
}
|
|
2557
2112
|
|
|
2558
2113
|
await updateContractIfNegotiating(config, contract, {
|
|
2559
|
-
|
|
2114
|
+
termsEncrypted: encryptedTerms,
|
|
2560
2115
|
termsHash: buildTermsHashHex(deadline, totalPayment),
|
|
2561
|
-
...(l1Mode ? { deadline, totalPayment } : {}),
|
|
2562
2116
|
});
|
|
2563
2117
|
|
|
2564
2118
|
if (!deadline || !totalPayment) {
|
|
2565
|
-
|
|
2119
|
+
const missingLabel = !deadline ? "deadline" : "payment";
|
|
2120
|
+
console.log(
|
|
2121
|
+
`Saved term. Set ${missingLabel} with \`nebulon contract <id> add term ${missingLabel} <value>\` to submit on-chain.`
|
|
2122
|
+
);
|
|
2566
2123
|
successMessage("Term saved.");
|
|
2567
2124
|
return;
|
|
2568
2125
|
}
|
|
@@ -2576,32 +2133,27 @@ const runAddTerm = async (config, contract, field, value, options) => {
|
|
|
2576
2133
|
|
|
2577
2134
|
console.log("Processing.");
|
|
2578
2135
|
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
|
-
);
|
|
2136
|
+
const sig = await submitTerms(
|
|
2137
|
+
config,
|
|
2138
|
+
contract,
|
|
2139
|
+
keypair,
|
|
2140
|
+
escrowPda,
|
|
2141
|
+
escrowId,
|
|
2142
|
+
totalPayment,
|
|
2143
|
+
deadline,
|
|
2144
|
+
encryptedTerms,
|
|
2145
|
+
options
|
|
2146
|
+
);
|
|
2599
2147
|
if (!sig) {
|
|
2600
2148
|
return;
|
|
2601
2149
|
}
|
|
2602
|
-
|
|
2150
|
+
logTx("Terms submitted.", sig, true);
|
|
2603
2151
|
successMessage("Terms submitted.");
|
|
2604
2152
|
} catch (error) {
|
|
2153
|
+
console.error(error?.message || error);
|
|
2154
|
+
if (error?.stack) {
|
|
2155
|
+
console.error(error.stack);
|
|
2156
|
+
}
|
|
2605
2157
|
await printTxLogs(error);
|
|
2606
2158
|
throw error;
|
|
2607
2159
|
}
|
|
@@ -2613,14 +2165,13 @@ const runSignContract = async (config, contract, options) => {
|
|
|
2613
2165
|
console.error("Contract has no escrow. Unable to sign.");
|
|
2614
2166
|
process.exit(1);
|
|
2615
2167
|
}
|
|
2616
|
-
|
|
2617
|
-
const l1Mode = executionMode === "l1";
|
|
2168
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
2618
2169
|
ensureContractPhase(
|
|
2619
2170
|
contract,
|
|
2620
2171
|
["negotiating", "awaiting_signatures"],
|
|
2621
2172
|
"Finish terms/milestones, then sign."
|
|
2622
2173
|
);
|
|
2623
|
-
if (
|
|
2174
|
+
if (contract.terms_encrypted && !contract.privacyReady) {
|
|
2624
2175
|
console.error("Privacy key exchange pending. Unable to read terms.");
|
|
2625
2176
|
process.exit(1);
|
|
2626
2177
|
}
|
|
@@ -2685,41 +2236,46 @@ const runSignContract = async (config, contract, options) => {
|
|
|
2685
2236
|
}
|
|
2686
2237
|
|
|
2687
2238
|
console.log("Processing.");
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
config,
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
)
|
|
2697
|
-
console.log(`Signature submitted. (tx: ${signSig})`);
|
|
2239
|
+
if (isVerbose(options)) {
|
|
2240
|
+
console.log("DEBUG: PER config", {
|
|
2241
|
+
ephemeralProviderUrl: config.ephemeralProviderUrl,
|
|
2242
|
+
ephemeralWsUrl: config.ephemeralWsUrl,
|
|
2243
|
+
ephemeralValidatorIdentity: config.ephemeralValidatorIdentity,
|
|
2244
|
+
rpcUrl: config.rpcUrl,
|
|
2245
|
+
wsUrl: config.wsUrl,
|
|
2246
|
+
network: config.network,
|
|
2247
|
+
});
|
|
2698
2248
|
try {
|
|
2699
|
-
|
|
2700
|
-
config,
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2249
|
+
if (config.ephemeralProviderUrl) {
|
|
2250
|
+
const router = new ConnectionMagicRouter(config.ephemeralProviderUrl, {
|
|
2251
|
+
wsEndpoint: config.ephemeralWsUrl || undefined,
|
|
2252
|
+
});
|
|
2253
|
+
const closest = await router.getClosestValidator();
|
|
2254
|
+
console.log("DEBUG: router closest validator (sign flow):", closest);
|
|
2255
|
+
}
|
|
2705
2256
|
} catch (error) {
|
|
2706
|
-
|
|
2257
|
+
console.log("DEBUG: router closest validator lookup failed:", error?.message || error);
|
|
2707
2258
|
}
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2259
|
+
}
|
|
2260
|
+
logProgress("Submitting signature on ER");
|
|
2261
|
+
let signSig = null;
|
|
2262
|
+
let commitSig = null;
|
|
2263
|
+
const { termsPda } = await ensureTermsPrepared(
|
|
2264
|
+
config,
|
|
2265
|
+
contract,
|
|
2266
|
+
keypair,
|
|
2267
|
+
escrowPda,
|
|
2268
|
+
escrowId,
|
|
2269
|
+
options
|
|
2270
|
+
);
|
|
2271
|
+
const { program: erProgram, sessionSigner, sessionPda } =
|
|
2272
|
+
await getPerProgramBundle(
|
|
2710
2273
|
config,
|
|
2711
|
-
contract,
|
|
2712
2274
|
keypair,
|
|
2713
|
-
|
|
2714
|
-
|
|
2275
|
+
programId,
|
|
2276
|
+
program.provider
|
|
2715
2277
|
);
|
|
2716
|
-
|
|
2717
|
-
await getPerProgramBundle(
|
|
2718
|
-
config,
|
|
2719
|
-
keypair,
|
|
2720
|
-
programId,
|
|
2721
|
-
program.provider
|
|
2722
|
-
);
|
|
2278
|
+
try {
|
|
2723
2279
|
signSig = await erProgram.methods
|
|
2724
2280
|
.signPrivateTerms(new anchor.BN(escrowId.toString()))
|
|
2725
2281
|
.accounts({
|
|
@@ -2731,25 +2287,33 @@ const runSignContract = async (config, contract, options) => {
|
|
|
2731
2287
|
})
|
|
2732
2288
|
.signers([sessionSigner])
|
|
2733
2289
|
.rpc({ skipPreflight: true });
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
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;
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
console.error("ERROR: signPrivateTerms failed:", error?.message || error);
|
|
2292
|
+
const msg = (error?.message || "").toLowerCase();
|
|
2293
|
+
if (msg.includes("invalidwritableaccount")) {
|
|
2294
|
+
console.error("HINT: InvalidWritableAccount usually means validator mismatch. Run with --verbose and compare router validator vs delegation.");
|
|
2749
2295
|
}
|
|
2296
|
+
throw error;
|
|
2297
|
+
}
|
|
2298
|
+
logTx("Signature submitted.", signSig, true);
|
|
2299
|
+
try {
|
|
2300
|
+
logProgress("Committing terms on ER");
|
|
2301
|
+
commitSig = await erProgram.methods
|
|
2302
|
+
.commitTerms(new anchor.BN(escrowId.toString()))
|
|
2303
|
+
.accounts({
|
|
2304
|
+
user: keypair.publicKey,
|
|
2305
|
+
payer: sessionSigner.publicKey,
|
|
2306
|
+
sessionToken: sessionPda,
|
|
2307
|
+
escrow: escrowPda,
|
|
2308
|
+
terms: termsPda,
|
|
2309
|
+
})
|
|
2310
|
+
.signers([sessionSigner])
|
|
2311
|
+
.rpc({ skipPreflight: true });
|
|
2312
|
+
} catch (error) {
|
|
2313
|
+
commitSig = null;
|
|
2750
2314
|
}
|
|
2751
2315
|
if (commitSig) {
|
|
2752
|
-
|
|
2316
|
+
logTx("Terms committed.", commitSig, true);
|
|
2753
2317
|
console.log("Syncing contract flags...");
|
|
2754
2318
|
let synced = false;
|
|
2755
2319
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
@@ -2818,7 +2382,7 @@ const runFundContract = async (config, contract, options) => {
|
|
|
2818
2382
|
process.exit(1);
|
|
2819
2383
|
}
|
|
2820
2384
|
ensureContractPhase(contract, ["waiting_for_funding"], "Run sign first.");
|
|
2821
|
-
if (
|
|
2385
|
+
if (contract.terms_encrypted && !contract.privacyReady) {
|
|
2822
2386
|
console.error("Privacy key exchange pending. Unable to read terms.");
|
|
2823
2387
|
process.exit(1);
|
|
2824
2388
|
}
|
|
@@ -2934,10 +2498,10 @@ const runFundContract = async (config, contract, options) => {
|
|
|
2934
2498
|
})
|
|
2935
2499
|
.signers([keypair])
|
|
2936
2500
|
.rpc();
|
|
2937
|
-
|
|
2501
|
+
logTx("Funding submitted.", sig, false);
|
|
2938
2502
|
|
|
2939
2503
|
await markFunded(config.backendUrl, config.auth.token, contract.id);
|
|
2940
|
-
|
|
2504
|
+
logProgress("Syncing private state to escrow flags");
|
|
2941
2505
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2942
2506
|
try {
|
|
2943
2507
|
await runSyncFlags(config, contract, { ...options, confirm: true });
|
|
@@ -2989,75 +2553,7 @@ const runUpdateMilestone = async (config, contract, number, options) => {
|
|
|
2989
2553
|
console.error("Check the list with: nebulon contract <id> check milestone list");
|
|
2990
2554
|
return;
|
|
2991
2555
|
}
|
|
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
|
-
}
|
|
2556
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
3061
2557
|
if (!config.ephemeralProviderUrl) {
|
|
3062
2558
|
console.error(
|
|
3063
2559
|
"Warning: MagicBlock RPC is not configured; milestone status checks may be unreliable."
|
|
@@ -3144,9 +2640,10 @@ const runUpdateMilestone = async (config, contract, number, options) => {
|
|
|
3144
2640
|
keypair,
|
|
3145
2641
|
walletKey,
|
|
3146
2642
|
index,
|
|
3147
|
-
1
|
|
2643
|
+
1,
|
|
2644
|
+
options
|
|
3148
2645
|
);
|
|
3149
|
-
|
|
2646
|
+
logTx("Milestone updated.", sig, true);
|
|
3150
2647
|
|
|
3151
2648
|
const milestones = Array.isArray(contract.milestones)
|
|
3152
2649
|
? contract.milestones.map((milestone) =>
|
|
@@ -3161,6 +2658,7 @@ const runUpdateMilestone = async (config, contract, number, options) => {
|
|
|
3161
2658
|
"Warning: backend did not persist milestone status (check backend version)."
|
|
3162
2659
|
);
|
|
3163
2660
|
}
|
|
2661
|
+
logProgress("Syncing private state to escrow flags");
|
|
3164
2662
|
await runSyncFlags(config, contract, { ...options, confirm: true });
|
|
3165
2663
|
successMessage("Milestone updated.");
|
|
3166
2664
|
};
|
|
@@ -3187,80 +2685,7 @@ const runConfirmMilestone = async (config, contract, number, options) => {
|
|
|
3187
2685
|
process.exit(1);
|
|
3188
2686
|
}
|
|
3189
2687
|
|
|
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
|
-
}
|
|
2688
|
+
await ensureExecutionMode(config, contract, keypair, options);
|
|
3264
2689
|
|
|
3265
2690
|
console.log("Verify data and confirm actions please.");
|
|
3266
2691
|
renderContractSummary(contract, wallet);
|
|
@@ -3299,26 +2724,68 @@ const runConfirmMilestone = async (config, contract, number, options) => {
|
|
|
3299
2724
|
walletKey,
|
|
3300
2725
|
contract.mint || config.usdcMint
|
|
3301
2726
|
);
|
|
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;
|
|
2727
|
+
if (!canProceed) {
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
const proceed = await confirmAction(options.confirm, "Proceed? yes/no");
|
|
2732
|
+
if (!proceed) {
|
|
2733
|
+
console.log("Canceled.");
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
console.log("Processing.");
|
|
2738
|
+
const programBundle = getProgram(config, keypair);
|
|
2739
|
+
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
2740
|
+
const programId = programBundle.programId;
|
|
2741
|
+
const privateMilestonePda = derivePrivateMilestonePda(
|
|
2742
|
+
escrowPda,
|
|
2743
|
+
index,
|
|
2744
|
+
programId
|
|
2745
|
+
);
|
|
2746
|
+
await ensureDelegatedAccount(
|
|
2747
|
+
config,
|
|
2748
|
+
programBundle.program,
|
|
2749
|
+
keypair,
|
|
2750
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
2751
|
+
privateMilestonePda,
|
|
2752
|
+
options
|
|
2753
|
+
);
|
|
2754
|
+
const perVaultPda = derivePerVaultPda(escrowPda, programId);
|
|
2755
|
+
await ensureDelegatedAccount(
|
|
2756
|
+
config,
|
|
2757
|
+
programBundle.program,
|
|
2758
|
+
keypair,
|
|
2759
|
+
{ perVault: { escrow: escrowPda } },
|
|
2760
|
+
perVaultPda,
|
|
2761
|
+
options
|
|
2762
|
+
);
|
|
2763
|
+
const escrowState = await getEscrowState(
|
|
2764
|
+
programBundle.connection,
|
|
2765
|
+
programId,
|
|
2766
|
+
escrowPda
|
|
2767
|
+
);
|
|
2768
|
+
if (escrowState) {
|
|
2769
|
+
await ensureDelegatedEscrow(
|
|
2770
|
+
config,
|
|
2771
|
+
programBundle.program,
|
|
2772
|
+
keypair,
|
|
2773
|
+
escrowState.escrowId,
|
|
2774
|
+
escrowPda,
|
|
2775
|
+
new PublicKey(contract.client_wallet),
|
|
2776
|
+
options
|
|
2777
|
+
);
|
|
3310
2778
|
}
|
|
3311
|
-
|
|
3312
|
-
console.log("Processing.");
|
|
3313
2779
|
const sig = await updatePrivateMilestoneStatus(
|
|
3314
2780
|
config,
|
|
3315
2781
|
contract,
|
|
3316
2782
|
keypair,
|
|
3317
2783
|
walletKey,
|
|
3318
2784
|
index,
|
|
3319
|
-
3
|
|
2785
|
+
3,
|
|
2786
|
+
options
|
|
3320
2787
|
);
|
|
3321
|
-
|
|
2788
|
+
logTx("Milestone confirmed.", sig, true);
|
|
3322
2789
|
|
|
3323
2790
|
const milestones = Array.isArray(contract.milestones)
|
|
3324
2791
|
? contract.milestones.map((milestone) =>
|
|
@@ -3415,7 +2882,7 @@ const runClaimFunds = async (config, contract, options) => {
|
|
|
3415
2882
|
})
|
|
3416
2883
|
.signers([keypair])
|
|
3417
2884
|
.rpc();
|
|
3418
|
-
|
|
2885
|
+
logTx("Funds claimed.", sig, false);
|
|
3419
2886
|
successMessage(
|
|
3420
2887
|
`Funds claimed successfully. (+${formatUsdc(rawFunded)} USDC)`
|
|
3421
2888
|
);
|
|
@@ -3445,7 +2912,7 @@ const runClaimFunds = async (config, contract, options) => {
|
|
|
3445
2912
|
})
|
|
3446
2913
|
.signers([keypair])
|
|
3447
2914
|
.rpc();
|
|
3448
|
-
|
|
2915
|
+
logTx("Timeout funds claimed.", sig, false);
|
|
3449
2916
|
successMessage(
|
|
3450
2917
|
`Funds claimed successfully. (+${formatUsdc(rawFunded)} USDC)`
|
|
3451
2918
|
);
|
|
@@ -3504,7 +2971,7 @@ const runClaimFunds = async (config, contract, options) => {
|
|
|
3504
2971
|
})
|
|
3505
2972
|
.signers([keypair])
|
|
3506
2973
|
.rpc();
|
|
3507
|
-
|
|
2974
|
+
logTx("Refund claimed.", sig, false);
|
|
3508
2975
|
successMessage("Refund claimed.");
|
|
3509
2976
|
successMessage("Escrow rent returned to the creator.");
|
|
3510
2977
|
try {
|
|
@@ -3549,7 +3016,8 @@ const updatePrivateMilestoneStatus = async (
|
|
|
3549
3016
|
keypair,
|
|
3550
3017
|
walletKey,
|
|
3551
3018
|
index,
|
|
3552
|
-
status
|
|
3019
|
+
status,
|
|
3020
|
+
options
|
|
3553
3021
|
) => {
|
|
3554
3022
|
const { program, connection, programId } = getProgram(config, keypair);
|
|
3555
3023
|
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
@@ -3566,26 +3034,13 @@ const updatePrivateMilestoneStatus = async (
|
|
|
3566
3034
|
programId
|
|
3567
3035
|
);
|
|
3568
3036
|
|
|
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
3037
|
await ensureDelegatedAccount(
|
|
3584
3038
|
config,
|
|
3585
3039
|
program,
|
|
3586
3040
|
keypair,
|
|
3587
|
-
|
|
3588
|
-
privateMilestonePda
|
|
3041
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
3042
|
+
privateMilestonePda,
|
|
3043
|
+
options
|
|
3589
3044
|
);
|
|
3590
3045
|
await ensureDelegatedEscrow(
|
|
3591
3046
|
config,
|
|
@@ -3593,12 +3048,15 @@ const updatePrivateMilestoneStatus = async (
|
|
|
3593
3048
|
keypair,
|
|
3594
3049
|
escrowId,
|
|
3595
3050
|
escrowPda,
|
|
3596
|
-
new PublicKey(contract.client_wallet)
|
|
3051
|
+
new PublicKey(contract.client_wallet),
|
|
3052
|
+
options
|
|
3597
3053
|
);
|
|
3598
3054
|
|
|
3599
3055
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
3600
3056
|
await getPerProgramBundle(config, keypair, programId, program.provider);
|
|
3601
3057
|
|
|
3058
|
+
logProgress("Submitting milestone update on ER");
|
|
3059
|
+
await sleep(200);
|
|
3602
3060
|
const sig = await erProgram.methods
|
|
3603
3061
|
.updatePrivateMilestoneStatus(
|
|
3604
3062
|
new anchor.BN(escrowId.toString()),
|
|
@@ -3618,204 +3076,12 @@ const updatePrivateMilestoneStatus = async (
|
|
|
3618
3076
|
return sig;
|
|
3619
3077
|
};
|
|
3620
3078
|
|
|
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
3079
|
const runSyncFlags = async (config, contract, options) => {
|
|
3810
3080
|
const { keypair, walletKey } = getWalletContext(config);
|
|
3811
3081
|
if (!contract.escrow_pda) {
|
|
3812
3082
|
console.error("Contract has no escrow.");
|
|
3813
3083
|
process.exit(1);
|
|
3814
3084
|
}
|
|
3815
|
-
if (isL1Mode(contract)) {
|
|
3816
|
-
await runSyncFlagsL1(config, contract, options);
|
|
3817
|
-
return;
|
|
3818
|
-
}
|
|
3819
3085
|
|
|
3820
3086
|
const { program, connection, programId } = getProgram(config, keypair);
|
|
3821
3087
|
const escrowPda = new PublicKey(contract.escrow_pda);
|
|
@@ -3826,12 +3092,13 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
3826
3092
|
}
|
|
3827
3093
|
|
|
3828
3094
|
const escrowId = escrowState.escrowId;
|
|
3829
|
-
const { termsPda, program:
|
|
3095
|
+
const { termsPda, program: baseProgram } = await ensureTermsPrepared(
|
|
3830
3096
|
config,
|
|
3831
3097
|
contract,
|
|
3832
3098
|
keypair,
|
|
3833
3099
|
escrowPda,
|
|
3834
|
-
escrowId
|
|
3100
|
+
escrowId,
|
|
3101
|
+
options
|
|
3835
3102
|
);
|
|
3836
3103
|
|
|
3837
3104
|
const milestoneIndices = Array.isArray(contract.milestones)
|
|
@@ -3844,40 +3111,28 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
3844
3111
|
index,
|
|
3845
3112
|
programId
|
|
3846
3113
|
);
|
|
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
3114
|
await ensureDelegatedAccount(
|
|
3862
3115
|
config,
|
|
3863
|
-
|
|
3116
|
+
baseProgram,
|
|
3864
3117
|
keypair,
|
|
3865
|
-
|
|
3866
|
-
privateMilestonePda
|
|
3118
|
+
{ privateMilestone: { escrow: escrowPda, index } },
|
|
3119
|
+
privateMilestonePda,
|
|
3120
|
+
options
|
|
3867
3121
|
);
|
|
3868
3122
|
}
|
|
3869
3123
|
|
|
3870
3124
|
await ensureDelegatedEscrow(
|
|
3871
3125
|
config,
|
|
3872
|
-
|
|
3126
|
+
baseProgram,
|
|
3873
3127
|
keypair,
|
|
3874
3128
|
escrowId,
|
|
3875
3129
|
escrowPda,
|
|
3876
|
-
new PublicKey(contract.client_wallet)
|
|
3130
|
+
new PublicKey(contract.client_wallet),
|
|
3131
|
+
options
|
|
3877
3132
|
);
|
|
3878
3133
|
|
|
3879
3134
|
const { program: erProgram, sessionSigner, sessionPda } =
|
|
3880
|
-
await getPerProgramBundle(config, keypair, programId,
|
|
3135
|
+
await getPerProgramBundle(config, keypair, programId, baseProgram.provider);
|
|
3881
3136
|
|
|
3882
3137
|
let terms;
|
|
3883
3138
|
try {
|
|
@@ -3963,7 +3218,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
3963
3218
|
})
|
|
3964
3219
|
.signers([sessionSigner])
|
|
3965
3220
|
.rpc({ skipPreflight: true });
|
|
3966
|
-
|
|
3221
|
+
logTx("Milestones committed.", sig, false);
|
|
3967
3222
|
didUpdate = true;
|
|
3968
3223
|
}
|
|
3969
3224
|
}
|
|
@@ -3980,7 +3235,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
3980
3235
|
})
|
|
3981
3236
|
.signers([sessionSigner])
|
|
3982
3237
|
.rpc({ skipPreflight: true });
|
|
3983
|
-
|
|
3238
|
+
logTx("Funding verified.", sig, false);
|
|
3984
3239
|
didUpdate = true;
|
|
3985
3240
|
}
|
|
3986
3241
|
|
|
@@ -4000,7 +3255,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
4000
3255
|
.remainingAccounts(milestoneAccounts)
|
|
4001
3256
|
.signers([sessionSigner])
|
|
4002
3257
|
.rpc({ skipPreflight: true });
|
|
4003
|
-
|
|
3258
|
+
logTx("Ready to claim set.", sig, false);
|
|
4004
3259
|
didUpdate = true;
|
|
4005
3260
|
readyToClaimSet = true;
|
|
4006
3261
|
}
|
|
@@ -4027,7 +3282,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
4027
3282
|
.remainingAccounts(milestoneAccounts)
|
|
4028
3283
|
.signers([sessionSigner])
|
|
4029
3284
|
.rpc({ skipPreflight: true });
|
|
4030
|
-
|
|
3285
|
+
logTx("Timeout refund ready set.", sig, false);
|
|
4031
3286
|
didUpdate = true;
|
|
4032
3287
|
}
|
|
4033
3288
|
|
|
@@ -4053,7 +3308,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
4053
3308
|
.remainingAccounts(milestoneAccounts)
|
|
4054
3309
|
.signers([sessionSigner])
|
|
4055
3310
|
.rpc({ skipPreflight: true });
|
|
4056
|
-
|
|
3311
|
+
logTx("Timeout funds ready set.", sig, false);
|
|
4057
3312
|
didUpdate = true;
|
|
4058
3313
|
}
|
|
4059
3314
|
|
|
@@ -4069,7 +3324,7 @@ const runSyncFlags = async (config, contract, options) => {
|
|
|
4069
3324
|
})
|
|
4070
3325
|
.signers([sessionSigner])
|
|
4071
3326
|
.rpc({ skipPreflight: true });
|
|
4072
|
-
|
|
3327
|
+
logTx("Escrow committed.", sig, false);
|
|
4073
3328
|
} else {
|
|
4074
3329
|
console.log("No escrow flag changes needed.");
|
|
4075
3330
|
}
|
|
@@ -4116,29 +3371,17 @@ const runMilestoneList = async (config, contract) => {
|
|
|
4116
3371
|
milestoneIndices.sort((a, b) => a - b);
|
|
4117
3372
|
|
|
4118
3373
|
let milestones = [];
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
3374
|
+
const { program: erProgram } = await getEphemeralProgram(config, keypair);
|
|
3375
|
+
try {
|
|
3376
|
+
milestones = await fetchPrivateMilestones(
|
|
3377
|
+
erProgram,
|
|
4122
3378
|
escrowPda,
|
|
4123
3379
|
programId,
|
|
4124
3380
|
milestoneIndices
|
|
4125
3381
|
);
|
|
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
|
-
}
|
|
3382
|
+
} catch (error) {
|
|
3383
|
+
console.error("Private milestones are not available yet.");
|
|
3384
|
+
process.exit(1);
|
|
4142
3385
|
}
|
|
4143
3386
|
|
|
4144
3387
|
const metadataByIndex = new Map(
|
|
@@ -4191,31 +3434,18 @@ const runMilestoneStatus = async (config, contract, number) => {
|
|
|
4191
3434
|
}
|
|
4192
3435
|
|
|
4193
3436
|
let milestone;
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
3437
|
+
const { program: erProgram } = await getEphemeralProgram(config, keypair);
|
|
3438
|
+
try {
|
|
3439
|
+
const items = await fetchPrivateMilestones(
|
|
3440
|
+
erProgram,
|
|
4197
3441
|
escrowPda,
|
|
4198
3442
|
programId,
|
|
4199
3443
|
[index]
|
|
4200
3444
|
);
|
|
4201
3445
|
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
|
-
}
|
|
3446
|
+
} catch (error) {
|
|
3447
|
+
console.error("Private milestone is not available yet.");
|
|
3448
|
+
process.exit(1);
|
|
4219
3449
|
}
|
|
4220
3450
|
|
|
4221
3451
|
const meta = (contract.milestones || []).find((m) => m.index === index) || {};
|
|
@@ -4258,7 +3488,7 @@ const runContractDetails = async (config, contract) => {
|
|
|
4258
3488
|
console.log("Contract details");
|
|
4259
3489
|
console.log(`ID: ${contract.id}`);
|
|
4260
3490
|
console.log(`Status: ${contract.status}`);
|
|
4261
|
-
console.log(
|
|
3491
|
+
console.log("Mode: PER");
|
|
4262
3492
|
console.log(`Role: ${role}`);
|
|
4263
3493
|
console.log(
|
|
4264
3494
|
formatParticipant(
|
|
@@ -4279,14 +3509,10 @@ const runContractDetails = async (config, contract) => {
|
|
|
4279
3509
|
console.log(`Escrow: ${contract.escrow_pda || "n/a"}`);
|
|
4280
3510
|
console.log(`Mint: ${contract.mint || "n/a"}`);
|
|
4281
3511
|
console.log(`Vault: ${contract.vault_token || "n/a"}`);
|
|
4282
|
-
const privacyLabel =
|
|
4283
|
-
? "l1"
|
|
4284
|
-
: contract.privacyReady
|
|
4285
|
-
? "ready"
|
|
4286
|
-
: "pending";
|
|
3512
|
+
const privacyLabel = contract.privacyReady ? "ready" : "pending";
|
|
4287
3513
|
console.log(`Privacy: ${privacyLabel}`);
|
|
4288
3514
|
|
|
4289
|
-
if (
|
|
3515
|
+
if (contract.terms_encrypted && !contract.privacyReady) {
|
|
4290
3516
|
console.log("Terms: encrypted (waiting on privacy keys)");
|
|
4291
3517
|
} else {
|
|
4292
3518
|
console.log(`Deadline: ${contract.deadline ? formatDeadlineValue(contract.deadline) : "missing"}`);
|
|
@@ -4346,7 +3572,7 @@ const runContractStatus = async (config, contract) => {
|
|
|
4346
3572
|
console.log(`Role: ${role}`);
|
|
4347
3573
|
}
|
|
4348
3574
|
console.log(`Current status: ${contract.status}`);
|
|
4349
|
-
console.log(
|
|
3575
|
+
console.log("Mode: PER");
|
|
4350
3576
|
if (contract.created_at) {
|
|
4351
3577
|
console.log(`Created: ${formatCreatedAt(contract.created_at)}`);
|
|
4352
3578
|
}
|
|
@@ -4467,46 +3693,6 @@ const runRateContract = async (config, contract, scoreValue) => {
|
|
|
4467
3693
|
console.log(`Rating submitted: ${score} star${score === 1 ? "" : "s"}.`);
|
|
4468
3694
|
};
|
|
4469
3695
|
|
|
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
3696
|
const runCheckTerms = async (config, contract) => {
|
|
4511
3697
|
const { wallet } = getWalletContext(config);
|
|
4512
3698
|
const role = getRole(contract, wallet);
|
|
@@ -4514,7 +3700,7 @@ const runCheckTerms = async (config, contract) => {
|
|
|
4514
3700
|
console.error("Only contract participants can view terms.");
|
|
4515
3701
|
process.exit(1);
|
|
4516
3702
|
}
|
|
4517
|
-
if (
|
|
3703
|
+
if (contract.terms_encrypted && !contract.privacyReady) {
|
|
4518
3704
|
console.error("Privacy key exchange pending. Unable to read terms.");
|
|
4519
3705
|
process.exit(1);
|
|
4520
3706
|
}
|
|
@@ -4580,7 +3766,7 @@ const runOpenDispute = async (config, contract, options) => {
|
|
|
4580
3766
|
})
|
|
4581
3767
|
.signers([keypair])
|
|
4582
3768
|
.rpc();
|
|
4583
|
-
|
|
3769
|
+
logTx("Dispute opened.", sig, false);
|
|
4584
3770
|
console.log("Contact @nortbyt3 on Discord to discuss your case.");
|
|
4585
3771
|
successMessage("Dispute opened.");
|
|
4586
3772
|
} catch (error) {
|
|
@@ -4673,7 +3859,7 @@ const runResolveDispute = async (
|
|
|
4673
3859
|
})
|
|
4674
3860
|
.signers([keypair])
|
|
4675
3861
|
.rpc();
|
|
4676
|
-
|
|
3862
|
+
logTx("Dispute resolved.", sig, false);
|
|
4677
3863
|
successMessage("Dispute resolved.");
|
|
4678
3864
|
} catch (error) {
|
|
4679
3865
|
const message = (error && error.message ? error.message : "").toLowerCase();
|
|
@@ -4691,6 +3877,7 @@ const runResolveDispute = async (
|
|
|
4691
3877
|
|
|
4692
3878
|
const runContractCommand = async (args, options = {}) => {
|
|
4693
3879
|
const config = loadConfig();
|
|
3880
|
+
config.__verbose = Boolean(options && options.verbose);
|
|
4694
3881
|
await ensureHosted(config);
|
|
4695
3882
|
|
|
4696
3883
|
if (!args.length) {
|
|
@@ -4810,10 +3997,6 @@ const runContractCommand = async (args, options = {}) => {
|
|
|
4810
3997
|
await runRateContract(config, contract, rest[1]);
|
|
4811
3998
|
return;
|
|
4812
3999
|
}
|
|
4813
|
-
if (action === "mode") {
|
|
4814
|
-
await runContractMode(config, contract, rest[1], options);
|
|
4815
|
-
return;
|
|
4816
|
-
}
|
|
4817
4000
|
|
|
4818
4001
|
if (action === "disable" && rest[1] === "milestone") {
|
|
4819
4002
|
if (!rest[2]) {
|