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
1 change: 1 addition & 0 deletions .github/workflows/release-readiness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ jobs:
- name: Test deployment lanes
run: |
corepack yarn test:intent-guardian-fee-update
corepack yarn test:retire-dispute-risk-windows
corepack yarn test:v3-groups-deployment
corepack yarn test:dispute-lifecycle-deployment
corepack yarn test:method-scoped-deployment
Expand Down
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@
Recording checkpoints: commit live records before any artifact generation, pin lanes 39/40 after their first live
execution, propose the Base cutover at the live Safe nonce, and flip manifests/package only after execution. Lane 40
is immutable and pinned after its 2026-08-28 Base execution.
- `deploy/42_retire_dispute_risk_windows.ts` removes payment methods from dispute protection by zeroing their risk
windows on `DisputeProtectionPolicyMethodScopedStaked`: a zero window makes `onIntentSignaled` a no-op and
`isDisputeProtectionEnabled` false for that method, and PayPal/Venmo are untouched. Its target is
`RETIRED_DISPUTABLE_PAYMENT_METHODS` (`cashapp`, decided 2026-08-28), not `DISPUTABLE_PAYMENT_METHODS`, which
#311 already trimmed to PayPal/Venmo. Live networks run only under `DEPLOY_ACTIVE_TAG=42_retire_dispute_risk_windows`
(`yarn deploy:dispute-risk-windows:base_staging` executes the deployer-EOA write; `:base` queues one
`setRiskWindow(cashapp, 0)` Safe call that `deploy_summary` writes under `deployments/outputs/safe-batches/`);
local networks zero the window right after lane 39, and the lane skips wherever every retired window already reads
zero. Base executed the call at Safe nonce 80 on 2026-08-28 (safeTxHash `0x16a7…4650`, tx `0x4824…6f8a`, artifacts
`base_retire_cashapp_risk_window*`, whose Safe origin/description say "lane 41" — the retirement lane's number
before lane 41 was claimed by the UPI payment method), so Base evidence is already `0`. Base staging is still pending: the package
extractor's `LIVE_DISPUTABLE_PAYMENT_METHODS.base_staging` override, the staging evidence, and the spec's staging
window keep Cash App at 1209600 until the staging write executes, and that recording PR also pins lane 42.
- `deployments/predecessorDisputeStack.ts` keeps two pinned maps: `PREDECESSOR_DISPUTE_STACKS` describes the
predecessor of the currently selected stack and feeds the lane-30 wrapper, the package's recognized-predecessor
identities, and lane-34 tooling; `METHOD_SCOPED_PREDECESSOR_DISPUTE_STACKS` describes what lane 37 replaces (the
Expand Down
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,12 @@ Current numbered lanes include:
- `39` (immutable): method-scoped stack on a dedicated `StakeVaultMethodScoped`
(deploy-only on live networks; deploys and activates locally);
- `40` (immutable): dedicated-vault activation (tag-only; staging EOA steps,
one guarded Base cutover batch, deferred predecessor writer removal).
- `41`: generic UPI/INR payment method (tag-only, Base staging only).
one guarded Base cutover batch, deferred predecessor writer removal);
- `41`: generic UPI/INR payment method (tag-only, Base staging only);
- `42`: dispute risk-window retirement (tag-only on live networks; zeroes the
`RETIRED_DISPUTABLE_PAYMENT_METHODS` windows on the lane-39 policy; Base
executed at Safe nonce 80 on 2026-08-28, staging EOA write and its evidence
flip still pending; pin after that execution).

There is no `26` script. Numbered files are identities, not proof that every
script should execute. A numbered script is immutable after any production
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,17 @@ Dispute-evidence issuance in `attestation-service` remains a separate follow-up
by these contract lanes. Only PayPal and Venmo receive non-zero onchain risk windows; Cash App and every other
active payment method have a zero window and do not require stake.

`deploy/42_retire_dispute_risk_windows.ts` is the governed write behind that statement (decided 2026-08-28). It zeroes
the `RETIRED_DISPUTABLE_PAYMENT_METHODS` risk windows on `DisputeProtectionPolicyMethodScopedStaked`, which makes
`onIntentSignaled` a no-op and `isDisputeProtectionEnabled` false for the method while PayPal and Venmo keep their
14-day windows. Base executed it at Safe nonce 80 on 2026-08-28 (safeTxHash `0x16a7…4650`, tx
`0x4824a78931e3785cd69e4aa1c669c2f86cf0ccbaf22066d54f7f7ace55156f8a`; batch and chunk plan under
`deployments/outputs/safe-batches/base_retire_cashapp_risk_window*`; their "lane 41" label predates the UPI
payment method claiming that number), so the lane's tagged Base run now skips. Base
staging still carries the 14-day Cash App window until `yarn deploy:dispute-risk-windows:base_staging` executes the
deployer-EOA write; after that, the recording PR drops Cash App from the package extractor's staging
`LIVE_DISPUTABLE_PAYMENT_METHODS` override, flips the staging evidence and spec windows, and pins lane 42.

### Whitelist Bootstrap

`yarn whitelist:bootstrap` discovers active deposits from a configurable raw GraphQL endpoint and
Expand Down
157 changes: 157 additions & 0 deletions deploy/42_retire_dispute_risk_windows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Lane 42 retires dispute protection for governance-selected payment methods by zeroing their risk windows.
// Local and EOA-owned policies execute directly; Safe-owned policies emit queued governance calls.
// The lane skips once every retired window reads zero; per-network evidence flips in recording PRs after each execution.

import { ethers } from "ethers";
import type { BigNumber, Contract } from "ethers";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";

import {
MULTI_SIG,
RETIRED_DISPUTABLE_PAYMENT_METHODS,
getActivePaymentMethods,
} from "../deployments/parameters";
import {
getDeployedContractAddress,
waitForDeploymentDelay,
} from "../deployments/helpers";
import { safeBatchCollector } from "../deployments/safeBatchCollector";

export const TAG = "42_retire_dispute_risk_windows";
export const POLICY_DEPLOYMENT_NAME =
"DisputeProtectionPolicyMethodScopedStaked";

const SUPPORTED_NETWORKS = new Set([
"localhost",
"hardhat",
"base_staging",
"base",
]);

function isLiveNetwork(network: string): boolean {
return network === "base" || network === "base_staging";
}

function sameAddress(left: string, right: string): boolean {
return left.toLowerCase() === right.toLowerCase();
}

export function paymentMethodHash(method: string): string {
return ethers.utils.keccak256(ethers.utils.toUtf8Bytes(method));
}

export function assertRetiredMethodsActive(
network: string,
retired: readonly string[] = RETIRED_DISPUTABLE_PAYMENT_METHODS
): void {
if (retired.length === 0) {
throw new Error("Retired disputable payment methods must not be empty");
}
const activeMethods = getActivePaymentMethods(network);
for (const method of retired) {
if (!activeMethods.includes(method)) {
throw new Error(
`Retired disputable payment method is not active on ${network}: ${method}`
);
}
}
}

export async function readRetiredRiskWindows(
policy: Contract,
network: string
): Promise<Array<{ method: string; hash: string; window: BigNumber }>> {
assertRetiredMethodsActive(network);
return Promise.all(
RETIRED_DISPUTABLE_PAYMENT_METHODS.map(async (method) => {
const hash = paymentMethodHash(method);
return {
method,
hash,
window: await policy.getRiskWindow(hash),
};
})
);
}

export async function retireDisputeRiskWindows(
hre: HardhatRuntimeEnvironment,
policy: Contract
): Promise<void> {
const network = hre.deployments.getNetworkName();
const windows = await readRetiredRiskWindows(policy, network);
const drifted = windows.filter(({ window }) => !window.isZero());
if (drifted.length === 0) return;

const accounts = await hre.getUnnamedAccounts();
const deployer = accounts[0];
const expectedOwner = MULTI_SIG[network] || deployer;
const owner = await policy.owner();
if (!sameAddress(owner, expectedOwner)) {
throw new Error(
`${POLICY_DEPLOYMENT_NAME} owner mismatch: expected ${expectedOwner}, found ${owner}`
);
}

if (accounts.some((account) => sameAddress(account, owner))) {
const signer = await hre.ethers.getSigner(owner);
for (const { hash } of drifted) {
await (await policy.connect(signer).setRiskWindow(hash, 0)).wait();
await waitForDeploymentDelay(hre);
}
for (const { method, hash } of drifted) {
if (!(await policy.getRiskWindow(hash)).isZero()) {
throw new Error(
`${POLICY_DEPLOYMENT_NAME} risk window still set for ${method}`
);
}
}
return;
}

for (const { method, hash } of drifted) {
safeBatchCollector.add(
policy.address,
policy.interface.encodeFunctionData("setRiskWindow", [hash, 0]),
`${POLICY_DEPLOYMENT_NAME}.setRiskWindow(${method}, 0)`
);
}
}

export async function retiredRiskWindowsCleared(
hre: HardhatRuntimeEnvironment
): Promise<boolean> {
const network = hre.deployments.getNetworkName();
const policy = await hre.ethers.getContractAt(
"DisputeProtectionPolicy",
getDeployedContractAddress(network, POLICY_DEPLOYMENT_NAME)
);
const windows = await readRetiredRiskWindows(policy, network);
return windows.every(({ window }) => window.isZero());
}

const func: DeployFunction = async function (
hre: HardhatRuntimeEnvironment
): Promise<void> {
const network = hre.deployments.getNetworkName();
const policy = await hre.ethers.getContractAt(
"DisputeProtectionPolicy",
getDeployedContractAddress(network, POLICY_DEPLOYMENT_NAME)
);
await retireDisputeRiskWindows(hre, policy);
};

func.skip = async (hre: HardhatRuntimeEnvironment): Promise<boolean> => {
const network = hre.deployments.getNetworkName();
if (!SUPPORTED_NETWORKS.has(network)) return true;
if (isLiveNetwork(network) && process.env.DEPLOY_ACTIVE_TAG !== TAG) {
return true;
}
return retiredRiskWindowsCleared(hre);
};

func.tags = [TAG, "DisputeRiskWindowRetirement"];
func.dependencies = ["39_deploy_method_scoped_vault_stack"];

export default func;
2 changes: 2 additions & 0 deletions deploy/deploy_summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ func.tags = [
"V3DisputeMethodScopedStack",
"38_activate_method_scoped_dispute_lifecycle_stack",
"V3DisputeMethodScopedActivation",
"42_retire_dispute_risk_windows",
"DisputeRiskWindowRetirement",
];

export default func;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "1.0",
"chainId": "8453",
"createdAt": 1787912115441,
"meta": {
"name": "Base Cash App dispute risk-window retirement",
"description": "DisputeProtectionPolicyMethodScopedStaked.setRiskWindow(keccak256(\"cashapp\"), 0) — governance decision 2026-08-28: Cash App leaves the dispute-protected set (zero window = onIntentSignaled no-op, isDisputeProtectionEnabled false); PayPal and Venmo keep 1209600. Lane 41 (PR #310) encoding; simulated from the Safe at Base block 50561373.",
"txBuilderVersion": "1.16.5",
"createdFromSafeAddress": "0x0bC26FF515411396DD588Abd6Ef6846E04470227",
"createdFromOwnerAddress": ""
},
"transactions": [
{
"to": "0xbF4B769dB70DBEc89b6b2c44988304a7aD2de4Fc",
"value": "0",
"data": "0x0aaaa5b410940ee67cfb3c6c064569ec92c0ee934cd7afa18dd2ca2d6a2254fcb009c17d0000000000000000000000000000000000000000000000000000000000000000",
"contractMethod": null,
"contractInputsValues": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"version": 1,
"chainId": 8453,
"safe": "0x0bC26FF515411396DD588Abd6Ef6846E04470227",
"sourceFile": "/Users/kartikeychauhan/Documents/zkp2p/zkp2p-contracts/deployments/outputs/safe-batches/base_retire_cashapp_risk_window.json",
"maxGas": "30000000",
"callOverhead": "60000",
"chunkOverhead": "100000",
"chunks": [
{
"safeTx": {
"to": "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D",
"value": "0",
"data": "0x8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009900bf4b769db70dbec89b6b2c44988304a7ad2de4fc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000440aaaa5b410940ee67cfb3c6c064569ec92c0ee934cd7afa18dd2ca2d6a2254fcb009c17d000000000000000000000000000000000000000000000000000000000000000000000000000000",
"operation": 1,
"safeTxGas": "0",
"baseGas": "0",
"gasPrice": "0",
"gasToken": "0x0000000000000000000000000000000000000000",
"refundReceiver": "0x0000000000000000000000000000000000000000",
"nonce": 80
},
"safeTxHash": "0x16a751458a0f011fecf113c6c8dbbf26eecd930a91c594c9f3ba66266e7a4650",
"estimatedGas": "190904",
"callCount": 1,
"calls": [
{
"target": "0xbf4b769db70dbec89b6b2c44988304a7ad2de4fc",
"selector": "0x0aaaa5b4",
"estimatedGas": "30904"
}
],
"firstCall": "0x0aaaa5b4@0xbf4b769db70dbec89b6b2c44988304a7ad2de4fc",
"lastCall": "0x0aaaa5b4@0xbf4b769db70dbec89b6b2c44988304a7ad2de4fc"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"file": "base_retire_cashapp_risk_window.json",
"sha256": "c72ccdc3442511e06554dae42f65f808f55dc0f505ddc1649abc1b1df333524e"
}
7 changes: 7 additions & 0 deletions deployments/parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ export const DISPUTABLE_PAYMENT_METHODS: string[] = [
"venmo",
];

// Payment methods governance removed from dispute protection after lane 39 configured them. Lane 42 zeroes
// their risk windows wherever they are still set (Base executed at Safe nonce 80 on 2026-08-28; Base staging
// pending). DISPUTABLE_PAYMENT_METHODS already excludes them; staging evidence flips after the staging write.
export const RETIRED_DISPUTABLE_PAYMENT_METHODS: string[] = [
"cashapp",
];

export const ORCHESTRATOR_V3_PROTOCOL_FEE: any = {
"localhost": ether(.001),
"hardhat": ether(.001),
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"deploy:dispute-method-scoped-vault:base": "ts-node --transpile-only scripts/deployActive.ts base 39_deploy_method_scoped_vault_stack",
"deploy:dispute-method-scoped-vault-activation:base_staging": "ts-node --transpile-only scripts/deployActive.ts base_staging 40_activate_method_scoped_vault_stack",
"deploy:dispute-method-scoped-vault-activation:base": "ts-node --transpile-only scripts/deployActive.ts base 40_activate_method_scoped_vault_stack",
"deploy:dispute-risk-windows:base_staging": "ts-node --transpile-only scripts/deployActive.ts base_staging 42_retire_dispute_risk_windows",
"deploy:dispute-risk-windows:base": "ts-node --transpile-only scripts/deployActive.ts base 42_retire_dispute_risk_windows",
"whitelist:bootstrap": "ts-node --transpile-only scripts/bootstrapWhitelistPolicy.ts",
"safe:propose-chunks": "ts-node --transpile-only scripts/proposeSafeBatchChunks.ts",
"etherscan:base": "yarn hardhat --network base etherscan-verify-with-delay --delay 600",
Expand All @@ -59,6 +61,7 @@
"test:method-scoped-deployment": "node scripts/test-method-scoped-deployment.cjs",
"test:method-scoped-vault-deployment": "node scripts/test-method-scoped-vault-deployment.cjs",
"test:method-scoped-activation": "node scripts/test-method-scoped-activation.cjs && node scripts/test-method-scoped-activation-rehearsal.cjs && node scripts/test-method-scoped-vault-activation.cjs && node scripts/test-method-scoped-vault-activation-rehearsal.cjs && node scripts/test-method-scoped-runner.cjs",
"test:retire-dispute-risk-windows": "node --test scripts/retire-dispute-risk-windows.spec.cjs",
"test:propose-safe-batch-chunks": "node --test scripts/test-propose-safe-batch-chunks.cjs",
"test:v3-groups-deployment": "node scripts/test-v3-groups-base-deployment.cjs prepare-resume && node scripts/test-v3-groups-base-deployment.cjs reject-mismatch && node scripts/test-v3-groups-base-deployment.cjs managed-hook-guard && node scripts/test-v3-groups-base-deployment.cjs managed-hook-no-rollback && node scripts/test-v3-groups-base-deployment.cjs managed-hook-successor-policy && node scripts/test-v3-groups-base-deployment.cjs managed-hook-bytecode-mismatch && node scripts/test-v3-groups-base-deployment.cjs managed-hook-historical-successor",
"test:release-policy": "node --test scripts/verify-github-environment.spec.mjs scripts/npm-release.spec.mjs",
Expand Down
Loading
Loading