Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions PUBLICATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,66 @@ chain and wallet state. Do not reset an uncertain attempt simply because no
receipt appears immediately. If `.lock` remains after a crash, inspect its PID
and confirm that process is dead before removing **only the lock**.

**MetaMask terminal Smart Transaction cancellation:** a wallet detail that
explicitly says `cancelled` / `FAILED_TIMEOUT` is distinct from a pending or
unknown send. Preserve that wallet evidence and stop the signing server. Use
`review-cancelled` only after reviewing that exact terminal outcome:

```sh
npm run publication -- review-cancelled --config=publication/batch-N.json --directory=PROD_RUN --submission=ENTITY_ID --transaction=0xRECORDED_HASH --nonce=REVIEWED_UNUSED_NONCE --wallet-outcome=smart_transaction_cancelled_failed_timeout
```

The command checks two independent RPCs: the original hash must have neither a
transaction nor a receipt, and both latest and pending account nonces must equal
the reviewed unused nonce. It preserves the original journal and failed hash,
then prepares a retry pinned to that nonce. The signing server checks the nonce
again before accepting a new intent, and verifies it after inclusion. This does
not resend anything. A known transaction, consumed nonce, pending replacement,
RPC disagreement or unknown wallet outcome requires further reconciliation.

For a standard wallet attempt visibly marked **Failed**, with no detailed error,
use `review-failed` only if its original nonce is already pinned in the journal:

```sh
npm run publication -- review-failed --config=publication/batch-N.json --directory=PROD_RUN --submission=ENTITY_ID --transaction=0xRECORDED_HASH --nonce=ORIGINAL_PINNED_NONCE --wallet-outcome=wallet_failed_not_broadcast
```

This requires the same independent RPC checks, preserves the original attempt
in a `failed-HASH.json` archive, and retries at the exact original nonce. Do not
claim a Smart Transaction timeout when the wallet only shows Failed. A pending
attempt or a failure with an unknown original nonce remains blocked.

MetaMask Smart Transactions may keep a transaction outside the public mempool
before inclusion; RPC absence alone therefore never proves cancellation. For
the reviewed retry, the owner can use standard transaction submission by
turning Smart Transactions off under Settings > Transactions, then review and
sign from the publication page. This temporarily disables Smart Transactions'
relay protections; restore the setting after the publication if desired. See
[MetaMask's Smart Transactions guide](https://support.metamask.io/manage-crypto/transactions/smart-transactions/).

**Transport gas limits:** the signing page estimates execution gas, supplies an
explicit 10% reserve, and bounds the request by the Base per-transaction ceiling
of 16,777,216 gas. This does not change receipt bytes or sealed calldata.
MetaMask can otherwise apply its default 1.5 multiplier: an executable estimate
of 12,532,654 would become 18,798,981, above that ceiling. The actual gas limit
of a previously pending wallet attempt must be inspected before attributing its
failure to this risk. See [MetaMask's gas implementation](https://github.com/MetaMask/core/blob/main/packages/transaction-controller/src/utils/gas.ts)
and [EIP-7825](https://eips.ethereum.org/EIPS/eip-7825).

**Catalog failure:** the existing active generation remains available while a
new generation builds. A failed `building` generation is retained inactive and
must not be overwritten. Archive its `catalog-library.json` or
`catalog-proofs.json` checkpoint, investigate, and deliberately start a fresh
candidate. A concurrent pointer change stops activation; review the new active
state rather than automatically overwriting another publisher's work.

**Staging QA scope:** Library verification and publication always cover the
complete sealed proof cohort. If iPulse staging's active QA catalog excludes an
asset, pass the explicitly reviewed available entity IDs to `sync-ipulse` with
`--ipulse-entities=ID,ID`. This narrows only staging ledger sidecars and records
excluded IDs in its report; production refuses partial scope. Do not modify a
sealed plan or expand an F2 asset release to resolve a missing staging route.

For a reviewed rollback to a retained ready generation:

```sh
Expand Down
142 changes: 74 additions & 68 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"firebase": "^12.17.1",
"json-canonicalize": "^2.0.0",
"nanoid": "3.3.18",
"next": "16.3.4",
"next": "16.3.8",
"react": "19.2.8",
"react-dom": "19.2.8",
"server-only": "^0.0.1",
Expand All @@ -68,5 +68,8 @@
"postcss": "^8.5.6",
"typescript": "~5.9.3",
"vitest": "4.1.11"
},
"overrides": {
"@grpc/grpc-js": "1.14.5"
}
}
18 changes: 18 additions & 0 deletions scripts/__tests__/publication-gas.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {it,expect} from 'vitest';
import {publicationGasLimit,BASE_TRANSACTION_GAS_CAP} from '../lib/publication-gas.mjs';

it('keeps the complete Silver cohort below the cap despite a wallet default 50% buffer',()=>{
const estimate=12_532_654n;
expect(estimate*150n/100n).toBeGreaterThan(BASE_TRANSACTION_GAS_CAP);
expect(publicationGasLimit(estimate)).toBe(13_785_920n);
expect(publicationGasLimit(estimate)).toBeGreaterThan(estimate);
});
it('never lowers the limit below an executable estimate near the cap',()=>{
expect(publicationGasLimit(16_000_000n)).toBe(BASE_TRANSACTION_GAS_CAP);
expect(publicationGasLimit(BASE_TRANSACTION_GAS_CAP)).toBe(BASE_TRANSACTION_GAS_CAP);
});
it.each([0n,-1n,BASE_TRANSACTION_GAS_CAP+1n])('rejects an invalid or oversized estimate (%s)',estimate=>expect(()=>publicationGasLimit(estimate)).toThrow());
it('rounds reserves upward and accepts the wallet RPC hex estimate',()=>{
expect(publicationGasLimit('0xb')).toBe(13n);
expect(publicationGasLimit('0xbf3c9e')).toBeGreaterThan(BigInt('0xbf3c9e'));
});
12 changes: 12 additions & 0 deletions scripts/__tests__/publication-scope.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {describe,it,expect} from 'vitest';
import {selectIpulseProofCohort} from '../lib/publication-scope.mjs';
const transactions=[{entityId:'silver'},{entityId:'btc'}];
describe('explicit staging proof sidecar scope',()=>{
it('retains the full cohort by default',()=>expect(selectIpulseProofCohort(transactions,'ipulse-401013')).toBe(transactions));
it('permits an explicit known staging subset without changing the sealed plan',()=>{
expect(selectIpulseProofCohort(transactions,'pulse-staging-e1394','btc')).toEqual([{entityId:'btc'}]);
expect(transactions).toHaveLength(2);
});
it.each(['','btc,btc','unknown','btc,'])('rejects invalid staging scope %s',ids=>expect(()=>selectIpulseProofCohort(transactions,'pulse-staging-e1394',ids)).toThrow());
it('never narrows production proof publication',()=>expect(()=>selectIpulseProofCohort(transactions,'ipulse-401013','btc')).toThrow('only for staging QA'));
});
Loading
Loading