bulletin-deploy 0.7.21 → 0.7.22-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bug-report.js +4 -4
- package/dist/{chunk-2VNGK2MU.js → chunk-3LGLMHQI.js} +26 -3
- package/dist/{chunk-VTTN4BX7.js → chunk-4YO5SOAY.js} +151 -2
- package/dist/chunk-74ETPOKH.js +284 -0
- package/dist/chunk-A5IQ5MKO.js +207 -0
- package/dist/chunk-EJ5TNGAY.js +24 -0
- package/dist/{chunk-BFXHVC23.js → chunk-HEUS42MX.js} +21 -17
- package/dist/{chunk-ZY6QLNKQ.js → chunk-KTOZL74I.js} +1 -1
- package/dist/{chunk-MJTQOXBC.js → chunk-LEYQOOWC.js} +3 -2
- package/dist/{chunk-G2P5UIPX.js → chunk-LZO2QX4T.js} +4 -3
- package/dist/chunk-QHOZEY5X.js +231 -0
- package/dist/{chunk-RJAFD4LO.js → chunk-SL7LV4DS.js} +1 -1
- package/dist/chunk-T7EEVWNU.js +32 -0
- package/dist/{chunk-SA37SLYF.js → chunk-TA3NCL6U.js} +2 -2
- package/dist/chunk-UPWEOGLQ.js +37 -0
- package/dist/{chunk-4TS6R26J.js → chunk-WVCIU6WM.js} +22 -9
- package/dist/chunk-ZYVGHDMU.js +117 -0
- package/dist/chunk-probe.js +3 -3
- package/dist/deploy.d.ts +4 -2
- package/dist/deploy.js +9 -9
- package/dist/dotns.d.ts +74 -1
- package/dist/dotns.js +8 -4
- package/dist/incremental-stats.d.ts +3 -1
- package/dist/incremental-stats.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -9
- package/dist/memory-report.js +2 -2
- package/dist/merkle.js +9 -9
- package/dist/personhood/bind-paid-alias.d.ts +43 -0
- package/dist/personhood/bind-paid-alias.js +10 -0
- package/dist/personhood/bind-personal-id.d.ts +55 -0
- package/dist/personhood/bind-personal-id.js +12 -0
- package/dist/personhood/bootstrap.d.ts +85 -0
- package/dist/personhood/bootstrap.js +245 -0
- package/dist/personhood/claim-pgas.d.ts +61 -0
- package/dist/personhood/claim-pgas.js +12 -0
- package/dist/personhood/constants.d.ts +23 -0
- package/dist/personhood/constants.js +22 -0
- package/dist/personhood/encoding.d.ts +49 -0
- package/dist/personhood/encoding.js +24 -0
- package/dist/personhood/hashing.d.ts +4 -0
- package/dist/personhood/hashing.js +8 -0
- package/dist/personhood/member-key.d.ts +12 -0
- package/dist/personhood/member-key.js +10 -0
- package/dist/personhood/people-client.d.ts +14 -0
- package/dist/personhood/people-client.js +48 -0
- package/dist/personhood/reprove.d.ts +43 -0
- package/dist/personhood/reprove.js +225 -0
- package/dist/pool.d.ts +7 -2
- package/dist/pool.js +3 -1
- package/dist/run-state.js +1 -1
- package/dist/telemetry.d.ts +7 -1
- package/dist/telemetry.js +8 -2
- package/dist/version-check.js +3 -3
- package/docs/e2e-bootstrap.md +36 -10
- package/package.json +4 -3
package/dist/bug-report.js
CHANGED
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
offerBugReport,
|
|
10
10
|
scrubSecrets,
|
|
11
11
|
setDeployContext
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-TA3NCL6U.js";
|
|
13
|
+
import "./chunk-SL7LV4DS.js";
|
|
14
|
+
import "./chunk-3LGLMHQI.js";
|
|
15
|
+
import "./chunk-LZO2QX4T.js";
|
|
16
16
|
export {
|
|
17
17
|
buildCliFlagsSummary,
|
|
18
18
|
buildLabels,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
package_default,
|
|
3
3
|
writeRunState
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LZO2QX4T.js";
|
|
5
5
|
|
|
6
6
|
// src/memory-report.ts
|
|
7
7
|
import * as fs2 from "fs";
|
|
@@ -246,13 +246,26 @@ function computeDeployOutcome(errorCategory, isSad, sadReason) {
|
|
|
246
246
|
if (isSad) return `sad_${sadReason}`;
|
|
247
247
|
return "clean";
|
|
248
248
|
}
|
|
249
|
+
function classifyErrorKind(msg) {
|
|
250
|
+
if (/Contract reverted|Contract execution would revert|revert(?:ed|ing)?\s*\(flags=[0-9]+\)/i.test(msg)) return "contract-revert";
|
|
251
|
+
if (/timed out after \d+s waiting for block|Transaction not included after \d+s|Transaction did not settle within/i.test(msg)) return "chain-timeout";
|
|
252
|
+
if (/\bstale\b.*nonce|nonce.*\bstale\b|"type"\s*:\s*"Future"|Invalid::Future|tx rejected by pool/i.test(msg)) return "nonce-stale";
|
|
253
|
+
if (/heartbeat timeout|WS halt|Unable to connect|ChainHead disjointed|websocket.*closed|socket closed|disconnect/i.test(msg)) return "connection";
|
|
254
|
+
return "unknown";
|
|
255
|
+
}
|
|
256
|
+
function sanitizeErrorMessage(msg) {
|
|
257
|
+
return scrubPaths(msg.slice(0, 500));
|
|
258
|
+
}
|
|
249
259
|
async function withSpan(op, description, attributes, fn) {
|
|
250
260
|
if (!Sentry) return fn();
|
|
251
261
|
return Sentry.startSpan({ op, name: description, attributes }, async (span) => {
|
|
252
262
|
try {
|
|
253
263
|
return await fn();
|
|
254
264
|
} catch (error) {
|
|
255
|
-
|
|
265
|
+
const msg = error.message ?? String(error);
|
|
266
|
+
span.setAttribute("error.message", msg);
|
|
267
|
+
span.setAttribute("deploy.error_kind", classifyErrorKind(msg));
|
|
268
|
+
span.setAttribute("deploy.error_message", sanitizeErrorMessage(msg));
|
|
256
269
|
span.setStatus({ code: 2, message: "internal_error" });
|
|
257
270
|
throw error;
|
|
258
271
|
}
|
|
@@ -345,11 +358,13 @@ async function withDeploySpan(domain, fn) {
|
|
|
345
358
|
span.setAttribute("deploy.status", "ok");
|
|
346
359
|
return result;
|
|
347
360
|
} catch (error) {
|
|
348
|
-
const msg = error.message;
|
|
361
|
+
const msg = error.message ?? String(error);
|
|
349
362
|
span.setAttribute("deploy.status", "error");
|
|
350
363
|
span.setAttribute("deploy.error", msg.slice(0, 500));
|
|
351
364
|
const errorCategory = classifyDeployError(msg);
|
|
352
365
|
span.setAttribute("deploy.error_category", errorCategory);
|
|
366
|
+
span.setAttribute("deploy.error_kind", classifyErrorKind(msg));
|
|
367
|
+
span.setAttribute("deploy.error_message", sanitizeErrorMessage(msg));
|
|
353
368
|
currentErrorCategory = errorCategory;
|
|
354
369
|
const isExpected = isExpectedError(msg);
|
|
355
370
|
span.setAttribute("deploy.expected", isExpected ? "true" : "false");
|
|
@@ -429,6 +444,11 @@ function setDeployAttribute(key, value) {
|
|
|
429
444
|
function __setDeployRootSpanForTest(span) {
|
|
430
445
|
deployRootSpan = span;
|
|
431
446
|
}
|
|
447
|
+
function __setSentryForTest(stub) {
|
|
448
|
+
const prev = Sentry;
|
|
449
|
+
Sentry = stub;
|
|
450
|
+
return prev;
|
|
451
|
+
}
|
|
432
452
|
function getCurrentSentryTraceId() {
|
|
433
453
|
if (!Sentry) return void 0;
|
|
434
454
|
const span = Sentry.getActiveSpan();
|
|
@@ -603,12 +623,15 @@ export {
|
|
|
603
623
|
classifyDeployError,
|
|
604
624
|
classifySadReason,
|
|
605
625
|
computeDeployOutcome,
|
|
626
|
+
classifyErrorKind,
|
|
627
|
+
sanitizeErrorMessage,
|
|
606
628
|
withSpan,
|
|
607
629
|
sampleMemory,
|
|
608
630
|
withDeploySpan,
|
|
609
631
|
setDeployReportContext,
|
|
610
632
|
setDeployAttribute,
|
|
611
633
|
__setDeployRootSpanForTest,
|
|
634
|
+
__setSentryForTest,
|
|
612
635
|
getCurrentSentryTraceId,
|
|
613
636
|
setDeploySentryTag,
|
|
614
637
|
captureWarning,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isTestnetSpecName
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WVCIU6WM.js";
|
|
4
4
|
import {
|
|
5
5
|
captureWarning,
|
|
6
6
|
setDeployAttribute,
|
|
7
7
|
withSpan
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3LGLMHQI.js";
|
|
9
9
|
|
|
10
10
|
// src/dotns.ts
|
|
11
11
|
import crypto from "crypto";
|
|
@@ -236,6 +236,7 @@ function formatWeight(weight) {
|
|
|
236
236
|
if (!weight) return "unknown";
|
|
237
237
|
return `ref_time=${weight.referenceTime.toString()} proof_size=${weight.proofSize.toString()}`;
|
|
238
238
|
}
|
|
239
|
+
var BARE_REVERT_DIAGNOSTIC_FUNCTIONS = /* @__PURE__ */ new Set(["register", "commit", "setContenthash", "setSubnodeOwner", "setResolver"]);
|
|
239
240
|
function formatContractDryRunFailure(gasEstimate, context) {
|
|
240
241
|
const functionName = context.functionName ?? "unknown";
|
|
241
242
|
const contractName = dotnsContractName(context.contractAddress, context.contracts);
|
|
@@ -253,8 +254,23 @@ function formatContractDryRunFailure(gasEstimate, context) {
|
|
|
253
254
|
lines.push(` calldata: ${context.encodedData}`);
|
|
254
255
|
if (context.args) lines.push(` args: ${stringifyDebugValue(context.args)}`);
|
|
255
256
|
}
|
|
257
|
+
const revertData = gasEstimate.revertData;
|
|
258
|
+
const isBareRevert = (revertData === void 0 || revertData.trim() === "0x") && gasEstimate.revertFlags === 1n;
|
|
259
|
+
if (isBareRevert && BARE_REVERT_DIAGNOSTIC_FUNCTIONS.has(functionName)) {
|
|
260
|
+
lines.push(
|
|
261
|
+
` diagnostic: bare-revert (empty 0x). Most common causes:`,
|
|
262
|
+
` 1. Origin not actually mapped on-chain. Try BULLETIN_DEPLOY_DEBUG_DOTNS_TX=1 to log calldata, then run`,
|
|
263
|
+
` \`node tools/dotns-dry-run.mjs <label>\` against the env's Asset Hub RPC to reproduce in isolation.`,
|
|
264
|
+
` 2. Contract precondition (e.g. commitment too new). dotns-dry-run.mjs will surface the contract-side state.`,
|
|
265
|
+
` hint: prepend --fresh to dotns-dry-run.mjs to use a brand-new unmapped substrate origin \u2014`,
|
|
266
|
+
` if the revert ONLY reproduces with --fresh, it's the account-mapping bug.`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
256
269
|
return lines.join("\n");
|
|
257
270
|
}
|
|
271
|
+
function __formatContractDryRunFailureForTest(gasEstimate, context) {
|
|
272
|
+
return formatContractDryRunFailure(gasEstimate, context);
|
|
273
|
+
}
|
|
258
274
|
function unwrapExecutionResult(rawResult) {
|
|
259
275
|
if (!rawResult) return { ok: null, err: null, successFlag: null };
|
|
260
276
|
if (typeof rawResult.success === "boolean") {
|
|
@@ -579,6 +595,17 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
579
595
|
async submitTransaction(contractAddress, value, encodedData, signerSubstrateAddress, signer, statusCallback, { rpcs, useNoncePolling, functionName, args, contracts }) {
|
|
580
596
|
await this.ensureAccountMapped(signerSubstrateAddress, signer);
|
|
581
597
|
const signerEvmAddress = await this.getEvmAddress(signerSubstrateAddress);
|
|
598
|
+
if (functionName === "register") {
|
|
599
|
+
try {
|
|
600
|
+
const stillMapped = await this.checkIfAccountMapped(signerSubstrateAddress);
|
|
601
|
+
if (!stillMapped) {
|
|
602
|
+
captureWarning("account mapping not confirmed on-chain immediately before register dry-run", {
|
|
603
|
+
signer: signerSubstrateAddress
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
} catch {
|
|
607
|
+
}
|
|
608
|
+
}
|
|
582
609
|
const gasEstimate = await this.estimateGasForCall(signerSubstrateAddress, contractAddress, value, encodedData);
|
|
583
610
|
if (!gasEstimate.success) {
|
|
584
611
|
throw new Error(formatContractDryRunFailure(gasEstimate, {
|
|
@@ -608,6 +635,22 @@ var ReviveClientWrapper = class _ReviveClientWrapper {
|
|
|
608
635
|
return await this.signAndSubmitWithRetry(buildExtrinsic, signer, statusCallback, "Revive.call", { nonceFallback });
|
|
609
636
|
}
|
|
610
637
|
};
|
|
638
|
+
var DOTNS_CONTEXT_HEX_LOWER = "0x646f746e73000000000000000000000000000000000000000000000000000000";
|
|
639
|
+
function formatPersonhoodRemediation(state, environmentId) {
|
|
640
|
+
if (environmentId !== "paseo-next-v2") {
|
|
641
|
+
return "Self-attestation is no longer available. Contact the DotNS team for whitelisting / Personhood status help.";
|
|
642
|
+
}
|
|
643
|
+
switch (state.state) {
|
|
644
|
+
case "not-bound":
|
|
645
|
+
return "Your account has no DotNS alias binding on paseo-next-v2. Visit https://sudo.personhood.dev/dotns-bootstrap to complete the recognition and binding steps, or run `tools/personhood-bootstrap.mjs --mnemonic <your-mnemonic>` once Stage 2 of the bootstrap tooling lands.";
|
|
646
|
+
case "bound-likely-stale":
|
|
647
|
+
return "Your alias binding exists but may have a stale ring revision. Run `node tools/reprove-alias.mjs --mnemonic <your-mnemonic> --env paseo-next-v2` to refresh the proof, then retry the registration.";
|
|
648
|
+
case "wrong-context":
|
|
649
|
+
return "Your alias binding exists but is for a different application context" + (state.storedContextHex ? ` (context: ${state.storedContextHex})` : "") + ". Re-bind under the 'dotns' context using the bootstrap flow: https://sudo.personhood.dev/dotns-bootstrap";
|
|
650
|
+
case "bound-fresh":
|
|
651
|
+
return "Self-attestation is no longer available. Contact the DotNS team for whitelisting / Personhood status help.";
|
|
652
|
+
}
|
|
653
|
+
}
|
|
611
654
|
var DotNS = class {
|
|
612
655
|
client;
|
|
613
656
|
clientWrapper;
|
|
@@ -623,6 +666,7 @@ var DotNS = class {
|
|
|
623
666
|
_usesExternalSigner = false;
|
|
624
667
|
_contracts = CONTRACTS;
|
|
625
668
|
_nativeToEthRatio = NATIVE_TO_ETH_RATIO;
|
|
669
|
+
_environmentId = null;
|
|
626
670
|
constructor() {
|
|
627
671
|
this.client = null;
|
|
628
672
|
this.clientWrapper = null;
|
|
@@ -640,6 +684,9 @@ var DotNS = class {
|
|
|
640
684
|
if (options.contracts && Object.keys(options.contracts).length > 0) {
|
|
641
685
|
this._contracts = { ...CONTRACTS, ...options.contracts };
|
|
642
686
|
}
|
|
687
|
+
if (options.environmentId) {
|
|
688
|
+
this._environmentId = options.environmentId;
|
|
689
|
+
}
|
|
643
690
|
const rpc = options.rpc || process.env.DOTNS_RPC || this.assetHubEndpoints[0];
|
|
644
691
|
this.rpc = rpc;
|
|
645
692
|
this._usesExternalSigner = Boolean(options.signer && options.signerAddress);
|
|
@@ -788,6 +835,31 @@ var DotNS = class {
|
|
|
788
835
|
this._testnetCache = isTestnetSpecName(rpc) || rpc.includes("paseo") || rpc.includes("westend") || rpc.includes("rococo");
|
|
789
836
|
return this._testnetCache;
|
|
790
837
|
}
|
|
838
|
+
/**
|
|
839
|
+
* Classify the AliasAccounts state for a substrate address.
|
|
840
|
+
* Only called on paseo-next-v2 testnets inside the preflight's NoStatus branch.
|
|
841
|
+
* Returns "not-bound" if the chain is unreachable (safe fallback to generic advice).
|
|
842
|
+
*/
|
|
843
|
+
async classifyAliasAccountState(ss58) {
|
|
844
|
+
if (!this.clientWrapper) return { state: "not-bound" };
|
|
845
|
+
try {
|
|
846
|
+
const api = this.clientWrapper.client;
|
|
847
|
+
const row = await api.query.AliasAccounts.AccountToAlias.getValue(ss58, { at: "best" });
|
|
848
|
+
if (!row) return { state: "not-bound" };
|
|
849
|
+
const contextHex = typeof row.ca?.context === "string" ? row.ca.context.toLowerCase() : "";
|
|
850
|
+
const paid = Boolean(row.paid);
|
|
851
|
+
const revision = Number(row.revision ?? 0);
|
|
852
|
+
if (paid && contextHex === DOTNS_CONTEXT_HEX_LOWER) {
|
|
853
|
+
return { state: "bound-likely-stale", storedContextHex: contextHex, paid, revision };
|
|
854
|
+
}
|
|
855
|
+
if (!paid || contextHex !== DOTNS_CONTEXT_HEX_LOWER) {
|
|
856
|
+
return { state: "wrong-context", storedContextHex: contextHex, paid, revision };
|
|
857
|
+
}
|
|
858
|
+
return { state: "bound-fresh", storedContextHex: contextHex, paid, revision };
|
|
859
|
+
} catch {
|
|
860
|
+
return { state: "not-bound" };
|
|
861
|
+
}
|
|
862
|
+
}
|
|
791
863
|
// Free PAS balance for a substrate address on the connected DotNS chain.
|
|
792
864
|
// Used by preflight to gate the deploy on whether the signer can pay tx
|
|
793
865
|
// fees before the chunk upload runs. Returns 0n if the account doesn't
|
|
@@ -1342,6 +1414,30 @@ var DotNS = class {
|
|
|
1342
1414
|
signerFreeBalance
|
|
1343
1415
|
};
|
|
1344
1416
|
}
|
|
1417
|
+
if (userStatus === ProofOfPersonhoodStatus.NoStatus && isTestnet && this._environmentId === "paseo-next-v2" && this.substrateAddress) {
|
|
1418
|
+
const aliasState = await this.classifyAliasAccountState(this.substrateAddress);
|
|
1419
|
+
const remediationMessage = formatPersonhoodRemediation(aliasState, this._environmentId);
|
|
1420
|
+
const currentName2 = popStatusName(userStatus);
|
|
1421
|
+
const requiredName2 = popStatusName(classification.status);
|
|
1422
|
+
return {
|
|
1423
|
+
label: validated,
|
|
1424
|
+
classification,
|
|
1425
|
+
userStatus,
|
|
1426
|
+
trailingDigits,
|
|
1427
|
+
baselength,
|
|
1428
|
+
isAvailable: true,
|
|
1429
|
+
existingOwner: null,
|
|
1430
|
+
isBaseNameReserved: isReserved,
|
|
1431
|
+
reservationOwner,
|
|
1432
|
+
isTestnet,
|
|
1433
|
+
canProceed: false,
|
|
1434
|
+
reason: `${validated}.dot requires ${requiredName2}, but this signer is ${currentName2}. ${remediationMessage}`,
|
|
1435
|
+
plannedAction: "abort",
|
|
1436
|
+
needsPopUpgrade: false,
|
|
1437
|
+
targetPopStatus,
|
|
1438
|
+
signerFreeBalance
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1345
1441
|
const currentName = popStatusName(userStatus);
|
|
1346
1442
|
const requiredName = popStatusName(classification.status);
|
|
1347
1443
|
return {
|
|
@@ -1471,6 +1567,57 @@ var DotNS = class {
|
|
|
1471
1567
|
return { label, owner: this.evmAddress };
|
|
1472
1568
|
});
|
|
1473
1569
|
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Reprove a stale DotNS alias binding.
|
|
1572
|
+
* Opens a People-chain client internally, builds the ring proof, and submits
|
|
1573
|
+
* reprove_alias_account on AH. Use when the alias exists but the ring root
|
|
1574
|
+
* has advanced past the stored revision.
|
|
1575
|
+
*
|
|
1576
|
+
* Requires a mnemonic — the DotNS instance must have been connected with one.
|
|
1577
|
+
*/
|
|
1578
|
+
async reprove(mnemonic) {
|
|
1579
|
+
this.ensureConnected();
|
|
1580
|
+
if (!this.substrateAddress || !this.signer) {
|
|
1581
|
+
throw new Error("reprove: DotNS must be connected with a signer");
|
|
1582
|
+
}
|
|
1583
|
+
const envId = this._environmentId ?? "paseo-next-v2";
|
|
1584
|
+
const { connectPeopleClient } = await import("./personhood/people-client.js");
|
|
1585
|
+
const { reproveAliasToAccount } = await import("./personhood/reprove.js");
|
|
1586
|
+
const { deriveMemberEntropy, deriveMemberKey } = await import("./personhood/member-key.js");
|
|
1587
|
+
const verifiable = await import("verifiablejs/nodejs");
|
|
1588
|
+
const memberEntropy = deriveMemberEntropy(mnemonic);
|
|
1589
|
+
const memberKey = deriveMemberKey(mnemonic);
|
|
1590
|
+
const peopleConn = await connectPeopleClient(envId);
|
|
1591
|
+
try {
|
|
1592
|
+
const result = await reproveAliasToAccount({
|
|
1593
|
+
peopleUnsafeApi: peopleConn.unsafeApi,
|
|
1594
|
+
ahUnsafeApi: this.clientWrapper.client,
|
|
1595
|
+
account: this.substrateAddress,
|
|
1596
|
+
memberKey,
|
|
1597
|
+
signCall: this.signer,
|
|
1598
|
+
buildRingProof: async ({ members, context, msg }) => {
|
|
1599
|
+
const r = verifiable.one_shot(memberEntropy, members, context, msg);
|
|
1600
|
+
return { proof: r.proof, alias: r.alias };
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
return result;
|
|
1604
|
+
} finally {
|
|
1605
|
+
peopleConn.disconnect();
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Run the personhood bootstrap flow for this DotNS signer.
|
|
1610
|
+
* Idempotent: each step is gated on chain state being "still needs doing".
|
|
1611
|
+
* Does NOT auto-run from preflight — call explicitly.
|
|
1612
|
+
*
|
|
1613
|
+
* Throws RecognizeRequiredError if the account hasn't been recognized by the
|
|
1614
|
+
* personhood faucet (https://sudo.personhood.dev/personhood-faucet).
|
|
1615
|
+
*/
|
|
1616
|
+
async bootstrap(mnemonic) {
|
|
1617
|
+
const { runBootstrap } = await import("./personhood/bootstrap.js");
|
|
1618
|
+
const envId = this._environmentId ?? "paseo-next-v2";
|
|
1619
|
+
return runBootstrap({ mnemonic, environmentId: envId });
|
|
1620
|
+
}
|
|
1474
1621
|
disconnect() {
|
|
1475
1622
|
if (this.client) {
|
|
1476
1623
|
this.client.destroy();
|
|
@@ -1503,6 +1650,7 @@ export {
|
|
|
1503
1650
|
verifyNonceAdvanced,
|
|
1504
1651
|
ProofOfPersonhoodStatus,
|
|
1505
1652
|
convertToHexString,
|
|
1653
|
+
__formatContractDryRunFailureForTest,
|
|
1506
1654
|
DOT_NODE,
|
|
1507
1655
|
convertWeiToNative,
|
|
1508
1656
|
computeDomainTokenId,
|
|
@@ -1517,6 +1665,7 @@ export {
|
|
|
1517
1665
|
parseDomainName,
|
|
1518
1666
|
parseProofOfPersonhoodStatus,
|
|
1519
1667
|
popStatusName,
|
|
1668
|
+
formatPersonhoodRemediation,
|
|
1520
1669
|
DotNS,
|
|
1521
1670
|
dotns
|
|
1522
1671
|
};
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildImplicationMessage,
|
|
3
|
+
buildV5GeneralExtrinsic,
|
|
4
|
+
bytesToHex,
|
|
5
|
+
encodeMembers,
|
|
6
|
+
hexToBytes,
|
|
7
|
+
readExtensionOrder,
|
|
8
|
+
toHex
|
|
9
|
+
} from "./chunk-ZYVGHDMU.js";
|
|
10
|
+
import {
|
|
11
|
+
PEOPLE_MEMBER_IDENTIFIER_HEX,
|
|
12
|
+
PGAS_ASSET_ID,
|
|
13
|
+
PROOF_BYTES
|
|
14
|
+
} from "./chunk-T7EEVWNU.js";
|
|
15
|
+
|
|
16
|
+
// src/personhood/claim-pgas.ts
|
|
17
|
+
import { Enum } from "polkadot-api";
|
|
18
|
+
var PgasClaimError = class extends Error {
|
|
19
|
+
kind;
|
|
20
|
+
dispatchError;
|
|
21
|
+
constructor(message, options = {}) {
|
|
22
|
+
super(message, options);
|
|
23
|
+
this.name = "PgasClaimError";
|
|
24
|
+
this.kind = options.kind ?? "Unknown";
|
|
25
|
+
this.dispatchError = options.dispatchError;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var SECS_PER_DAY = 86400n;
|
|
29
|
+
var IMPLICATION_EXCLUDE = /* @__PURE__ */ new Set([
|
|
30
|
+
"AuthorizeCall",
|
|
31
|
+
"AsPgas",
|
|
32
|
+
"StorageWeightReclaim"
|
|
33
|
+
]);
|
|
34
|
+
var PGAS_CONTEXT_PREFIX = new TextEncoder().encode("pop:gas:");
|
|
35
|
+
function buildGasContext(day, slotIndex) {
|
|
36
|
+
const out = new Uint8Array(32);
|
|
37
|
+
out.set(PGAS_CONTEXT_PREFIX, 0);
|
|
38
|
+
const dv = new DataView(out.buffer);
|
|
39
|
+
dv.setUint32(8, day, true);
|
|
40
|
+
dv.setUint32(12, slotIndex, true);
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function buildAsPgasClaimExtensionValue(proof, ringIndex, revision, day) {
|
|
44
|
+
return Enum("Claim", {
|
|
45
|
+
proof: bytesToHex(proof),
|
|
46
|
+
ring_index: ringIndex,
|
|
47
|
+
revision,
|
|
48
|
+
collection: Enum("People"),
|
|
49
|
+
day
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
var claimPgas = async ({
|
|
53
|
+
peopleUnsafeApi,
|
|
54
|
+
ahUnsafeApi,
|
|
55
|
+
ahClient,
|
|
56
|
+
target,
|
|
57
|
+
memberKey,
|
|
58
|
+
buildRingProof,
|
|
59
|
+
slotIndex = 0,
|
|
60
|
+
progress
|
|
61
|
+
}) => {
|
|
62
|
+
const people = peopleUnsafeApi;
|
|
63
|
+
const ah = ahUnsafeApi;
|
|
64
|
+
if (memberKey.length !== 32) {
|
|
65
|
+
throw new PgasClaimError("memberKey must be 32 bytes", { kind: "ClientError" });
|
|
66
|
+
}
|
|
67
|
+
const asset = await ah.query.Assets.Asset.getValue(PGAS_ASSET_ID, { at: "best" });
|
|
68
|
+
if (!asset) {
|
|
69
|
+
throw new PgasClaimError(
|
|
70
|
+
"PGAS asset (id 2_000_000_000) does not exist on AH",
|
|
71
|
+
{ kind: "PgasAssetNotCreated" }
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const position = await people.query.Members.Members.getValue(
|
|
75
|
+
PEOPLE_MEMBER_IDENTIFIER_HEX,
|
|
76
|
+
bytesToHex(memberKey),
|
|
77
|
+
{ at: "best" }
|
|
78
|
+
);
|
|
79
|
+
if (!position) {
|
|
80
|
+
throw new PgasClaimError(
|
|
81
|
+
"member key not present on People \u2014 recognize first",
|
|
82
|
+
{ kind: "NotARecognizedPerson" }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (position.type !== "Included") {
|
|
86
|
+
throw new PgasClaimError(
|
|
87
|
+
`member position is '${position.type}', expected 'Included'`,
|
|
88
|
+
{ kind: "NotARecognizedPerson" }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const ringIndex = position.value.ring_index;
|
|
92
|
+
const allEntries = await people.query.Members.RingKeys.getEntries({ at: "best" });
|
|
93
|
+
const pages = [];
|
|
94
|
+
const identHex = PEOPLE_MEMBER_IDENTIFIER_HEX.toLowerCase();
|
|
95
|
+
for (const entry of allEntries) {
|
|
96
|
+
if (entry.keyArgs[0].toLowerCase() !== identHex) continue;
|
|
97
|
+
if (Number(entry.keyArgs[1]) !== ringIndex) continue;
|
|
98
|
+
pages.push([Number(entry.keyArgs[2]), [...entry.value]]);
|
|
99
|
+
}
|
|
100
|
+
pages.sort((a, b) => a[0] - b[0]);
|
|
101
|
+
const ringKeys = pages.flatMap(([, ks]) => ks);
|
|
102
|
+
if (ringKeys.length === 0) {
|
|
103
|
+
throw new PgasClaimError("ring has no members on People", {
|
|
104
|
+
kind: "ClientError"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const membersBytes = encodeMembers(ringKeys.map((k) => hexToBytes(k)));
|
|
108
|
+
const collectionId = await ah.constants.AliasAccounts.PeopleCollectionIdentifier();
|
|
109
|
+
const ringExp = await ah.constants.AliasAccounts.PeopleRingExponent();
|
|
110
|
+
const ringExponent = ringExp.type === "R2e9" ? 9 : ringExp.type === "R2e10" ? 10 : 14;
|
|
111
|
+
const ringRoots = await ah.query.MembersSubscriber.RingRoots.getValue(
|
|
112
|
+
collectionId,
|
|
113
|
+
ringIndex,
|
|
114
|
+
{ at: "best" }
|
|
115
|
+
);
|
|
116
|
+
if (!ringRoots || ringRoots.length === 0) {
|
|
117
|
+
throw new PgasClaimError(
|
|
118
|
+
"AH has no RingRoots for this (collection, ring_index)",
|
|
119
|
+
{ kind: "RingRootNotFound" }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const latest = ringRoots[ringRoots.length - 1];
|
|
123
|
+
const revision = latest.revision;
|
|
124
|
+
const nowRaw = await ah.query.Timestamp.Now.getValue({ at: "best" });
|
|
125
|
+
const nowSec = nowRaw > 10000000000n ? nowRaw / 1000n : nowRaw;
|
|
126
|
+
const day = Number(nowSec / SECS_PER_DAY);
|
|
127
|
+
const contextBytes = buildGasContext(day, slotIndex);
|
|
128
|
+
const innerTx = ah.tx.Pgas.claim_pgas({ slot_index: slotIndex, target });
|
|
129
|
+
const callBytes = await innerTx.getEncodedData();
|
|
130
|
+
const passEmpty = await capturePass(innerTx, void 0);
|
|
131
|
+
const msg = buildImplicationMessage(
|
|
132
|
+
callBytes,
|
|
133
|
+
passEmpty.extensions,
|
|
134
|
+
IMPLICATION_EXCLUDE
|
|
135
|
+
);
|
|
136
|
+
const { proof, alias } = await buildRingProof({
|
|
137
|
+
ringExponent,
|
|
138
|
+
members: membersBytes,
|
|
139
|
+
context: contextBytes,
|
|
140
|
+
msg
|
|
141
|
+
});
|
|
142
|
+
if (proof.length !== PROOF_BYTES) {
|
|
143
|
+
throw new PgasClaimError(
|
|
144
|
+
`ring proof must be ${PROOF_BYTES} bytes, got ${proof.length}`,
|
|
145
|
+
{ kind: "ClientError" }
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const asPgasValue = Enum("Claim", {
|
|
149
|
+
proof: bytesToHex(proof),
|
|
150
|
+
ring_index: ringIndex,
|
|
151
|
+
revision,
|
|
152
|
+
collection: Enum("People"),
|
|
153
|
+
day
|
|
154
|
+
});
|
|
155
|
+
const passProof = await capturePass(innerTx, asPgasValue);
|
|
156
|
+
const extrinsicBytes = buildV5GeneralExtrinsic(callBytes, passProof.extensions);
|
|
157
|
+
const extrinsicHex = toHex(extrinsicBytes);
|
|
158
|
+
const blockHash = await submitExtrinsic(ahClient, extrinsicHex, progress);
|
|
159
|
+
const amount = await ah.constants.Pgas.PgasClaimAmount();
|
|
160
|
+
return { blockHash, amount, alias };
|
|
161
|
+
};
|
|
162
|
+
var capturePass = async (innerTx, asPgasValue) => {
|
|
163
|
+
let captured = null;
|
|
164
|
+
const sentinel = new Error("__pgas_capture_sentinel__");
|
|
165
|
+
const signer = {
|
|
166
|
+
publicKey: new Uint8Array(32),
|
|
167
|
+
signTx: async (callData, signedExtensions, metadata) => {
|
|
168
|
+
const order = await readExtensionOrder(metadata);
|
|
169
|
+
const byIdentifier = {};
|
|
170
|
+
for (const id of order) {
|
|
171
|
+
const ext = signedExtensions[id];
|
|
172
|
+
byIdentifier[id] = {
|
|
173
|
+
value: ext.value,
|
|
174
|
+
additionalSigned: ext.additionalSigned
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
captured = { callData, extensions: { order, byIdentifier } };
|
|
178
|
+
throw sentinel;
|
|
179
|
+
},
|
|
180
|
+
signBytes: async () => new Uint8Array(64)
|
|
181
|
+
};
|
|
182
|
+
try {
|
|
183
|
+
await innerTx.sign(signer, {
|
|
184
|
+
mortality: { mortal: false },
|
|
185
|
+
customSignedExtensions: {
|
|
186
|
+
AsPgas: {
|
|
187
|
+
value: asPgasValue,
|
|
188
|
+
additionalSigned: new Uint8Array()
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
if (err !== sentinel) throw err;
|
|
194
|
+
}
|
|
195
|
+
if (captured === null) {
|
|
196
|
+
throw new PgasClaimError("extension capture failed", { kind: "ClientError" });
|
|
197
|
+
}
|
|
198
|
+
return captured;
|
|
199
|
+
};
|
|
200
|
+
var narrowDispatchError = (dispatchError) => {
|
|
201
|
+
if (typeof dispatchError === "object" && dispatchError !== null && "type" in dispatchError) {
|
|
202
|
+
const d = dispatchError;
|
|
203
|
+
if (d.type === "Invalid" && typeof d.value === "object" && d.value !== null) {
|
|
204
|
+
const v = d.value;
|
|
205
|
+
if (v.type === "BadProof") return "BadProof";
|
|
206
|
+
if (v.type === "Custom") {
|
|
207
|
+
switch (v.value) {
|
|
208
|
+
case 230:
|
|
209
|
+
return "InvalidClaimSlot";
|
|
210
|
+
case 231:
|
|
211
|
+
return "InvalidClaimDay";
|
|
212
|
+
case 232:
|
|
213
|
+
return "AlreadyClaimedToday";
|
|
214
|
+
case 233:
|
|
215
|
+
return "PgasAssetNotCreated";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return "DispatchError";
|
|
221
|
+
};
|
|
222
|
+
var submitExtrinsic = (client, extrinsicHex, progress) => {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
let settled = false;
|
|
225
|
+
const fail = (err) => {
|
|
226
|
+
if (settled) return;
|
|
227
|
+
settled = true;
|
|
228
|
+
reject(err);
|
|
229
|
+
};
|
|
230
|
+
const succeed = (h) => {
|
|
231
|
+
if (settled) return;
|
|
232
|
+
settled = true;
|
|
233
|
+
resolve(h);
|
|
234
|
+
};
|
|
235
|
+
client.submitAndWatch(hexToBytes(extrinsicHex)).subscribe({
|
|
236
|
+
next: (event) => {
|
|
237
|
+
if (settled) return;
|
|
238
|
+
if (event.type === "broadcasted") {
|
|
239
|
+
progress?.onBroadcasted?.();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (event.type === "txBestBlocksState" && event.found) {
|
|
243
|
+
if (event.ok === false) {
|
|
244
|
+
fail(
|
|
245
|
+
new PgasClaimError("claim_pgas dispatched but failed in-block", {
|
|
246
|
+
kind: narrowDispatchError(event.dispatchError),
|
|
247
|
+
dispatchError: event.dispatchError
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
progress?.onBestBlock?.(event.block.hash);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (event.type === "finalized") {
|
|
256
|
+
if (event.ok === false) {
|
|
257
|
+
fail(
|
|
258
|
+
new PgasClaimError("claim_pgas failed at finalization", {
|
|
259
|
+
kind: narrowDispatchError(event.dispatchError),
|
|
260
|
+
dispatchError: event.dispatchError
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
succeed(event.block.hash);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
error: (err) => {
|
|
269
|
+
fail(
|
|
270
|
+
err instanceof PgasClaimError ? err : new PgasClaimError(
|
|
271
|
+
err instanceof Error ? `RPC rejected extrinsic: ${err.message}` : "RPC error during submitAndWatch",
|
|
272
|
+
{ cause: err, kind: "RpcError" }
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export {
|
|
281
|
+
PgasClaimError,
|
|
282
|
+
buildAsPgasClaimExtensionValue,
|
|
283
|
+
claimPgas
|
|
284
|
+
};
|