scidex/exchange/wallet_manager.py already issues per-agent EVM
keypairs but every chain interaction so far has been a no-op —
wallet_chain defaults to "ethereum" and nothing is ever broadcast.
Wire wallets to Polygon Mumbai testnet (now Amoy after the 2024
deprecation — pick whichever has live RPC) so SciDEX can write content
hashes for top-ranked artifacts on-chain. This gives every elevated
artifact an immutable provenance receipt that anyone outside SciDEX can
verify without trusting our DB.
scidex/exchange/chain_client.py exposing publish_provenance(artifact_id, content_sha256, agent_id) -> tx_hash and verify_provenance(artifact_id) -> dict.web3.py against Polygon Amoy RPC (configurable via POLYGON_RPC_URL).contracts/ArtifactProvenance.sol with two methods: publish(bytes32 artifactId, bytes32 contentHash) (only signer wallet) and getProvenance(bytes32) -> (address, bytes32, uint256).artifact_provenance_chain(artifact_id PK, tx_hash, block_number, content_sha256, published_at).T0/T1 epistemic tier (hook into scidex/atlas/artifact_quality_gates.py) — only top-ranked artifacts pay gas.agent_registry row; hit-cap → queue, don't fail./artifact/{id} adds a "On-chain receipt" row with tx link to PolygonScan.GET /api/artifacts/{id}/provenance/verify re-reads the chain and returns {matches: bool, on_chain_hash, db_hash}.verify return matches: false.wallet_manager.create_agent_wallet (line 140) — already produces an EVM signer; reuse for tx signing.web3==6.x to requirements; pin RPC URL in env, never code.content_sha256 once at promotion time using the canonical artifact bytes (use scidex.atlas.artifact_commit.commit_artifact to get the committed blob).pending/confirmed/failed in artifact_provenance_chain.status.eth_account.Account.sign_transaction — never expose decrypted keys outside wallet_manager.decrypt_key.8a6c314c699a (chain selection — already done, picked EVM).2147d16ef177 (per-agent wallets).Approach taken:
All acceptance criteria implemented. Chain integration follows the progressive-enhancement pattern already established in bounty_escrow.py — off-chain DB is always the source of truth; on-chain is optional and gated by POLYGON_RPC_URL + POLYGON_PROVENANCE_CONTRACT env vars.
Deliverables:
contracts/ArtifactProvenance.sol — Solidity 0.8.20 contract with:publish(bytes32 artifactId, bytes32 contentHash) — authorized publisher onlygetProvenance(bytes32) -> (address, bytes32, uint256)verify(bytes32, bytes32) -> boolauthorizedPublishers mapping) so any agent wallet can be granted publish rightsmigrations/add_artifact_provenance_chain.py — creates two tables:artifact_provenance_chain(artifact_id PK, tx_hash, block_number, content_sha256, published_at, status, agent_id, chain, gas_used, error_message)chain_gas_budget(agent_id, budget_date PK, matic_spent) — daily 0.5 MATIC cap trackingscidex/exchange/chain_client.py — Python bridge exposing:publish_provenance(artifact_id, content_sha256, agent_id) -> tx_hash | Noneverify_provenance(artifact_id) -> dict — live on-chain read when configured, DB-only fallbackget_provenance_record(artifact_id) -> dict | Nonepolygonscan_tx_url(tx_hash) -> strwallet_manager.decrypt_key — private key never leaves that functionscidex/atlas/artifact_quality_gates.py — T0/T1 hook:_maybe_trigger_chain_provenance (fire-and-forget thread)quality_score < 0.7 (below T1 established threshold)api.py:GET /api/artifacts/{id}/provenance/verify — returns {matches, on_chain_hash, db_hash, tx_hash, block_number, polygonscan_url, chain_configured}/artifact/{id} provenance tab: renders "On-chain Receipt" card with status badge, tx link to PolygonScan Amoy, content hash preview, and "Verify hash" linkAcceptance criteria status:
chain_client.py with publish_provenance + verify_provenanceweb3.py against Polygon Amoy RPC (env POLYGON_RPC_URL)ArtifactProvenance.sol with publish + getProvenanceartifact_provenance_chain table/artifact/{id} On-chain receipt row with PolygonScan linkGET /api/artifacts/{id}/provenance/verifyDeployment notes:
Set POLYGON_RPC_URL=https://rpc-amoy.polygon.technology and POLYGON_PROVENANCE_CONTRACT=<deployed> to activate. Without these, all publish calls record status='queued' silently and the verify endpoint returns chain_configured: false.