{"workflow":null,"planning":null,"id":"49c37b81-ae2b-49af-a9ba-d98f188ae617","state":"completed","template":"shape:chain","objective":"Build X402Settler, a contract that settles x402-style 'exact' payments in any standard ERC-20 through a Permit2 witness transfer, with Foundry tests, an independent review and a fix pass. Local only: do not deploy, and nothing here touches the swarm's own payment path.\n\nContract (src/X402Settler.sol, Solidity 0.8.26):\n- constructor(address permit2) stores it immutable (zero reverts). No owner, admin, fee or ETH handling (no payable function, no receive).\n- struct X402Payment { bytes32 paymentId; address to; uint256 validAfter; }. X402_PAYMENT_TYPEHASH = keccak256(\"X402Payment(bytes32 paymentId,address to,uint256 validAfter)\"); witness = keccak256(abi.encode(X402_PAYMENT_TYPEHASH, paymentId, to, validAfter)); WITNESS_TYPE_STRING = \"X402Payment witness)TokenPermissions(address token,uint256 amount)X402Payment(bytes32 paymentId,address to,uint256 validAfter)\" (dependent types in alphabetical order, as EIP-712 requires).\n- settle(ISignatureTransfer.PermitTransferFrom permit, address owner, X402Payment payment, bytes signature): anyone may call (the facilitator). Reverts ZeroRecipient if payment.to is zero, SelfPayment if payment.to == owner, NotYetValid if block.timestamp < payment.validAfter (validAfter itself is valid), AlreadySettled if receipts[owner][paymentId] exists. It then writes the receipt (before the external call), calls PERMIT2.permitWitnessTransferFrom(permit, SignatureTransferDetails(payment.to, permit.permitted.amount), owner, witness, WITNESS_TYPE_STRING, signature) — the exact scheme always moves the full permitted amount — and requires payment.to's token balance to have risen by exactly that amount (UnsupportedToken otherwise, which rejects fee-on-transfer tokens). The Permit2 deadline is the payment's valid-before time and Permit2's nonce stops replays of the same signature.\n- Receipt { address token; uint256 amount; address to; address facilitator; uint64 settledAt; } in receipts[owner][paymentId]; views receipt(owner, paymentId) and isSettled(owner, paymentId); event Settled(bytes32 indexed paymentId, address indexed owner, address indexed to, address token, uint256 amount, address facilitator). Receipts are keyed by (owner, paymentId) so nobody but the payer can occupy a payer's paymentId: only the owner can sign a permit that moves the owner's tokens. A server proves settlement by reading receipt(owner, paymentId) and comparing token, amount and to with what it asked for.\n\nTests: Permit2 is not vendored and builds have no network, so add test/mocks/MockPermit2.sol implementing only permitWitnessTransferFrom with canonical Permit2 hashing (EIP-712 domain name \"Permit2\", chainId, verifyingContract, no version; spender = msg.sender in the struct; the PermitWitnessTransferFrom stub followed by witnessTypeString; unordered bitmap nonces; deadline check; 65-byte ECDSA owners). Pin its digest for one fixed input to a value computed independently in the test from the EIP-712 formula. Cover: happy path with vm.sign; validAfter - 1, validAfter, deadline and deadline + 1; the same permit replayed; a fresh permit reusing a paymentId for the same owner (AlreadySettled); one paymentId used by two different owners (both settle, separate receipts); a signature whose witness names another to or paymentId; a signature made for another spender contract; a fee-on-transfer token (UnsupportedToken); a token whose transferFrom re-enters settle cannot settle twice. Scope: the contract stays under ~150 lines.","blockedReason":null,"createdAt":"2026-09-27T05:30:08.107Z","updatedAt":"2026-09-27T05:58:49.034Z","paidBy":"0x8a3b860f6dbfbbbb07f21905ed8cb0234538bdc8","via":"api.imd.fun","parentJobId":null,"project":{"id":"49c37b81-ae2b-49af-a9ba-d98f188ae617","head":"49c37b81-ae2b-49af-a9ba-d98f188ae617","running":null,"versions":[{"jobId":"49c37b81-ae2b-49af-a9ba-d98f188ae617","workflowId":null,"objective":"Build X402Settler, a contract that settles x402-style 'exact' payments in any standard ERC-20 through a Permit2 witness transfer, with Foundry tests, an independent review and a fix pass. Local only: do not deploy, and nothing here touches the swarm's own payment path.\n\nContract (src/X402Settler.sol, Solidity 0.8.26):\n- constructor(address permit2) stores it immutable (zero reverts). No owner, admin, fee or ETH handling (no payable function, no receive).\n- struct X402Payment { bytes32 paymentId; address to; uint256 validAfter; }. X402_PAYMENT_TYPEHASH = keccak256(\"X402Payment(bytes32 paymentId,address to,uint256 validAfter)\"); witness = keccak256(abi.encode(X402_PAYMENT_TYPEHASH, paymentId, to, validAfter)); WITNESS_TYPE_STRING = \"X402Payment witness)TokenPermissions(address token,uint256 amount)X402Payment(bytes32 paymentId,address to,uint256 validAfter)\" (dependent types in alphabetical order, as EIP-712 requires).\n- settle(ISignatureTransfer.PermitTransferFrom permit, address owner, X402Payment payment, bytes signature): anyone may call (the facilitator). Reverts ZeroRecipient if payment.to is zero, SelfPayment if payment.to == owner, NotYetValid if block.timestamp < payment.validAfter (validAfter itself is valid), AlreadySettled if receipts[owner][paymentId] exists. It then writes the receipt (before the external call), calls PERMIT2.permitWitnessTransferFrom(permit, SignatureTransferDetails(payment.to, permit.permitted.amount), owner, witness, WITNESS_TYPE_STRING, signature) — the exact scheme always moves the full permitted amount — and requires payment.to's token balance to have risen by exactly that amount (UnsupportedToken otherwise, which rejects fee-on-transfer tokens). The Permit2 deadline is the payment's valid-before time and Permit2's nonce stops replays of the same signature.\n- Receipt { address token; uint256 amount; address to; address facilitator; uint64 settledAt; } in receipts[owner][paymentId]; views receipt(owner, paymentId) and isSettled(owner, paymentId); event Settled(bytes32 indexed paymentId, address indexed owner, address indexed to, address token, uint256 amount, address facilitator). Receipts are keyed by (owner, paymentId) so nobody but the payer can occupy a payer's paymentId: only the owner can sign a permit that moves the owner's tokens. A server proves settlement by reading receipt(owner, paymentId) and comparing token, amount and to with what it asked for.\n\nTests: Permit2 is not vendored and builds have no network, so add test/mocks/MockPermit2.sol implementing only permitWitnessTransferFrom with canonical Permit2 hashing (EIP-712 domain name \"Permit2\", chainId, verifyingContract, no version; spender = msg.sender in the struct; the PermitWitnessTransferFrom stub followed by witnessTypeString; unordered bitmap nonces; deadline check; 65-byte ECDSA owners). Pin its digest for one fixed input to a value computed independently in the test from the EIP-712 formula. Cover: happy path with vm.sign; validAfter - 1, validAfter, deadline and deadline + 1; the same permit replayed; a fresh permit reusing a paymentId for the same owner (AlreadySettled); one paymentId used by two different owners (both settle, separate receipts); a signature whose witness names another to or paymentId; a signature made for another spender contract; a fee-on-transfer token (UnsupportedToken); a token whose transferFrom re-enters settle cannot settle twice. Scope: the contract stays under ~150 lines.","baseCommit":"0243d7da4a4337ae8b16bcdf15bb4ead736fd68f","state":"completed","createdAt":"2026-09-27T05:30:08.107Z"}]},"deliver":true,"host":false,"site":null,"launch":{"requested":false,"kind":null,"id":null,"status":null,"chainId":null},"oracleRequestId":null,"delivery":{"failure":"Command failed: git -c http.postBuffer=524288000 -c http.version=HTTP/1.1 -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=300 -c user.name=IdentityMD -c user.email=network@identitymd.invalid merge --no-edit -m identitymd: combine accepted artifacts 7d976bf842ca41118437391127ed7b75085278ec\n"},"media":null,"nodes":[{"key":"adversarial_review","role":"review","state":"accepted","attempt":1,"revisions":0,"judgeRevisions":0,"dependsOn":["build_contract_project"],"allowedPaths":[],"failureReason":null,"dispatchNote":null,"dispatchNoteAt":null,"updatedAt":"2026-09-27T05:47:39.808Z","verdict":null,"seat":{"tokenId":"1846","agentId":"51297"},"live":null},{"key":"build_contract_project","role":"implement","state":"accepted","attempt":1,"revisions":0,"judgeRevisions":0,"dependsOn":[],"allowedPaths":[],"failureReason":null,"dispatchNote":null,"dispatchNoteAt":null,"updatedAt":"2026-09-27T05:44:53.173Z","verdict":{"status":"accepted","profile":"foundry","evaluation":"checks","rejectionCode":null,"detail":"all checks passed","verifierVersion":"0.1.0+ff982c0f","verifiedTreeHash":"5b3c1d74433ab3f6eaadab1bfb52642e62b46c80","at":"2026-09-27T05:44:53.176Z","failedChecks":[]},"seat":{"tokenId":"1129","agentId":"51017"},"live":null},{"key":"fix_findings","role":"implement","state":"accepted","attempt":2,"revisions":0,"judgeRevisions":0,"dependsOn":["adversarial_review"],"allowedPaths":[],"failureReason":null,"dispatchNote":null,"dispatchNoteAt":null,"updatedAt":"2026-09-27T05:58:49.034Z","verdict":{"status":"accepted","profile":"foundry","evaluation":"checks","rejectionCode":null,"detail":"all checks passed","verifierVersion":"0.1.0+ff982c0f","verifiedTreeHash":"e45afb4f2103a64044cdc29f9bcd38bc77802939","at":"2026-09-27T05:58:49.036Z","failedChecks":[]},"seat":{"tokenId":"579","agentId":"50950"},"live":null}],"reviews":[{"status":"sent","chainId":1,"txHash":"0x4f6967822ac60ce2990d9bbd76e2159beebe588994967a085959bfbc405bb6fc","blockNumber":26114145,"sentAt":"2026-10-03T20:16:49.488Z","entries":[{"nodeKey":"adversarial_review","agentId":"51297","value":1,"role":"review:submission"},{"nodeKey":"build_contract_project","agentId":"51017","value":1,"role":"verification:checks"},{"nodeKey":"fix_findings","agentId":"51105","value":0,"role":"verification:checks"},{"nodeKey":"fix_findings","agentId":"50950","value":1,"role":"verification:checks"}]}]}