Summary
Create a Node.js script that runs after the contract E2E test (plotlink-contracts#27) and verifies the web app correctly indexes every transaction into Supabase.
This is the "contract test" layer — it catches ABI drift between plotlink-contracts and plotlink by verifying that real mainnet events are decoded and stored correctly.
Run command:
npx tsx scripts/e2e-verify.ts --from-file ../plotlink-contracts/e2e-results.json
Depends On
- plotlink-contracts#27 — the contract E2E script must run first and produce tx hashes + storyline IDs
Verification Scenarios
Group V1: Storyline Indexing
For each storyline created by the contract script:
| # |
Check |
How |
| V1.1 |
POST tx hash to /api/index/storyline |
Should return 200 |
| V1.2 |
Query Supabase storylines table by storyline_id |
Record must exist |
| V1.3 |
Verify writer_address matches deployer wallet |
Field accuracy |
| V1.4 |
Verify token_address is non-zero |
Token was created on MCV2 |
| V1.5 |
Verify title matches what was sent to contract |
String decoded correctly |
| V1.6 |
Verify has_deadline matches contract param |
Bool decoded correctly |
| V1.7 |
Verify plot_count = 1 (genesis only, before chainPlot indexing) |
Initial state correct |
| V1.8 |
Verify block_timestamp is a valid ISO date |
Timestamp conversion correct |
| V1.9 |
Verify tx_hash and log_index stored correctly |
Dedup keys present |
| V1.10 |
Verify writer_type = 0 (deployer is not a registered agent) |
ERC-8004 detection works |
Group V2: Plot Indexing
For each plot chained by the contract script:
| # |
Check |
How |
| V2.1 |
POST tx hash to /api/index/plot |
Should return 200 |
| V2.2 |
Query Supabase plots table by storyline_id + plot_index |
Record must exist |
| V2.3 |
Verify content_cid matches CID sent to contract |
CID decoded correctly |
| V2.4 |
Verify content_hash matches keccak256 of actual content |
Hash integrity |
| V2.5 |
Verify content field is non-empty (fetched from IPFS or fallback) |
Content retrieval works |
| V2.6 |
Verify plot_index is sequential (0 = genesis, 1, 2, 3...) |
Ordering correct |
| V2.7 |
After all plots indexed, verify parent storyline plot_count reconciled |
Reconciliation works |
| V2.8 |
Verify last_plot_time on storyline matches latest plot timestamp |
Reconciliation accurate |
Group V3: Trade Indexing
For each mint/burn from the contract script's trading scenarios:
| # |
Check |
How |
| V3.1 |
POST tx hash + token address to /api/index/trade |
Should return 200 |
| V3.2 |
Query Supabase trade_history by tx_hash |
Record(s) must exist |
| V3.3 |
Verify event_type is "mint" or "burn" correctly |
Event classification |
| V3.4 |
Verify price_per_token is > 0 and reasonable |
Price calculation correct |
| V3.5 |
Verify total_supply changes correctly after each trade |
Supply tracking |
| V3.6 |
Verify reserve_amount matches what the contract reported |
Amount accuracy |
| V3.7 |
Verify user_address matches the trader (deployer) |
User attribution |
| V3.8 |
Verify storyline_id resolved from token_address lookup |
Token → storyline mapping |
Group V4: Donation Indexing
For each donation from the contract script:
| # |
Check |
How |
| V4.1 |
POST tx hash to /api/index/donation |
Should return 200 |
| V4.2 |
Query Supabase donations by tx_hash |
Record must exist |
| V4.3 |
Verify donor_address matches deployer wallet |
Donor decoded correctly |
| V4.4 |
Verify amount matches donation amount (stored as wei string) |
Amount precision |
| V4.5 |
Verify storyline_id matches the donated-to storyline |
Routing correct |
Group V5: Price & TVL Reads
After all trades are indexed:
| # |
Check |
How |
| V5.1 |
Call getTokenPrice(tokenAddress) from lib/price.ts |
Returns non-null |
| V5.2 |
Verify pricePerToken is > 0 |
Price reads work |
| V5.3 |
Verify totalSupply matches expected after all buys/sells |
Supply accurate |
| V5.4 |
Call getTokenTVL(tokenAddress) |
Returns non-null |
| V5.5 |
Verify tvl is > 0 (reserve deposited from buys) |
TVL reads work |
Group V6: Content Hash Verification
| # |
Check |
How |
| V6.1 |
For each indexed plot, compute keccak256(content) locally |
Use lib/content.ts hashContent |
| V6.2 |
Compare local hash to stored content_hash |
Must match exactly |
| V6.3 |
Test with Unicode content (Korean + emoji) in at least one plot |
Unicode hashing consistent between Solidity and TS |
Group V7: Idempotency
| # |
Check |
How |
| V7.1 |
POST the same tx hash to storyline indexer twice |
Second call returns 200, no duplicate records |
| V7.2 |
POST the same tx hash to plot indexer twice |
No duplicate plots |
| V7.3 |
POST the same tx hash to trade indexer twice |
No duplicate trades |
| V7.4 |
POST the same tx hash to donation indexer twice |
No duplicate donations |
Group V8: Error Handling
| # |
Check |
How |
| V8.1 |
POST invalid tx hash (random hex) to storyline indexer |
Should return 4xx, not 500 |
| V8.2 |
POST valid tx hash from unrelated contract |
Should return 4xx (no matching event) |
| V8.3 |
POST empty body to each indexer route |
Should return 400 |
Data Flow Between Scripts
plotlink-contracts/ plotlink/
┌──────────────────┐ ┌──────────────────────┐
│ E2ETest.s.sol │ │ e2e-verify.ts │
│ │ │ │
│ Runs on mainnet │──writes──▶ │ Reads results JSON │
│ Real txs │ JSON file │ Calls indexer APIs │
│ Logs tx hashes │ │ Queries Supabase │
│ │ │ Verifies all fields │
└──────────────────┘ └──────────────────────┘
The contract script outputs a JSON file (e2e-results.json) with all tx hashes, storyline IDs, token addresses, and expected values. The verification script reads this and validates the full pipeline.
Output Format
=== V1: Storyline Indexing ===
[V1.1] POST /api/index/storyline (id=5) PASS 200 OK
[V1.2] Supabase record exists PASS
[V1.3] writer_address matches PASS 0x4d34...
[V1.4] token_address non-zero PASS 0xABC...
...
=== V7: Idempotency ===
[V7.1] Double-index storyline PASS no duplicates
...
=== ALL VERIFICATIONS PASSED ===
Acceptance Criteria
Branch
task/{issue-number}-e2e-indexer-verify
Summary
Create a Node.js script that runs after the contract E2E test (plotlink-contracts#27) and verifies the web app correctly indexes every transaction into Supabase.
This is the "contract test" layer — it catches ABI drift between
plotlink-contractsandplotlinkby verifying that real mainnet events are decoded and stored correctly.Run command:
Depends On
Verification Scenarios
Group V1: Storyline Indexing
For each storyline created by the contract script:
/api/index/storylinestorylinestable by storyline_idwriter_addressmatches deployer wallettoken_addressis non-zerotitlematches what was sent to contracthas_deadlinematches contract paramplot_count= 1 (genesis only, before chainPlot indexing)block_timestampis a valid ISO datetx_hashandlog_indexstored correctlywriter_type= 0 (deployer is not a registered agent)Group V2: Plot Indexing
For each plot chained by the contract script:
/api/index/plotplotstable by storyline_id + plot_indexcontent_cidmatches CID sent to contractcontent_hashmatches keccak256 of actual contentcontentfield is non-empty (fetched from IPFS or fallback)plot_indexis sequential (0 = genesis, 1, 2, 3...)plot_countreconciledlast_plot_timeon storyline matches latest plot timestampGroup V3: Trade Indexing
For each mint/burn from the contract script's trading scenarios:
/api/index/tradetrade_historyby tx_hashevent_typeis "mint" or "burn" correctlyprice_per_tokenis > 0 and reasonabletotal_supplychanges correctly after each tradereserve_amountmatches what the contract reporteduser_addressmatches the trader (deployer)storyline_idresolved from token_address lookupGroup V4: Donation Indexing
For each donation from the contract script:
/api/index/donationdonationsby tx_hashdonor_addressmatches deployer walletamountmatches donation amount (stored as wei string)storyline_idmatches the donated-to storylineGroup V5: Price & TVL Reads
After all trades are indexed:
getTokenPrice(tokenAddress)fromlib/price.tspricePerTokenis > 0totalSupplymatches expected after all buys/sellsgetTokenTVL(tokenAddress)tvlis > 0 (reserve deposited from buys)Group V6: Content Hash Verification
keccak256(content)locallylib/content.tshashContentcontent_hashGroup V7: Idempotency
Group V8: Error Handling
Data Flow Between Scripts
The contract script outputs a JSON file (
e2e-results.json) with all tx hashes, storyline IDs, token addresses, and expected values. The verification script reads this and validates the full pipeline.Output Format
Acceptance Criteria
Branch
task/{issue-number}-e2e-indexer-verify