diff --git a/.github/workflows/rhc-canonical-chain-history.yml b/.github/workflows/rhc-canonical-chain-history.yml new file mode 100644 index 0000000..3de499e --- /dev/null +++ b/.github/workflows/rhc-canonical-chain-history.yml @@ -0,0 +1,199 @@ +name: RHC canonical SeaDrop and Seaport history + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-canonical-chain-history.yml' + - 'chain-history/**' + - 'chain-history-trigger.txt' + +permissions: + actions: read + contents: write + +concurrency: + group: rhc-canonical-chain-history-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + head: + runs-on: ubuntu-latest + outputs: + head: ${{ steps.capture.outputs.head }} + steps: + - id: capture + shell: bash + run: | + set -euo pipefail + python - <<'PY' >> "$GITHUB_OUTPUT" + import json, urllib.request + payload=json.dumps({'jsonrpc':'2.0','id':1,'method':'eth_blockNumber','params':[]}).encode() + request=urllib.request.Request('https://rpc.mainnet.chain.robinhood.com',data=payload,headers={'Content-Type':'application/json','User-Agent':'rhc-head-capture/1.0'}) + with urllib.request.urlopen(request,timeout=60) as response: + data=json.loads(response.read()) + current=int(data['result'],16) + finalized=max(0,current-1000) + print(f'head={finalized}') + PY + + collect: + needs: head + strategy: + fail-fast: false + max-parallel: 8 + matrix: + event: [seadrop, seaport] + shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63] + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Collect canonical event shard + shell: bash + run: | + set -euo pipefail + python chain-history/collect_logs.py \ + --event '${{ matrix.event }}' \ + --shard '${{ matrix.shard }}' \ + --shards 64 \ + --head '${{ needs.head.outputs.head }}' \ + --out 'out-${{ matrix.event }}-${{ matrix.shard }}' + - uses: actions/upload-artifact@v4 + if: always() + with: + name: rhc-chain-${{ matrix.event }}-${{ matrix.shard }} + path: out-${{ matrix.event }}-${{ matrix.shard }}/ + retention-days: 3 + if-no-files-found: error + compression-level: 9 + + aggregate: + needs: [head, collect] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + with: + ref: chatgpt/rhc-wallet-verification-p0p1-20260828 + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - uses: actions/download-artifact@v4 + with: + pattern: rhc-chain-* + path: downloaded-chain-history + - name: Merge shards and enforce exact coverage + env: + FIXED_HEAD: ${{ needs.head.outputs.head }} + COLLECT_RESULT: ${{ needs.collect.result }} + shell: bash + run: | + set -euo pipefail + python - <<'PY' + import gzip, hashlib, json, os, pathlib, shutil + + head=int(os.environ['FIXED_HEAD']) + collect_result=os.environ['COLLECT_RESULT'] + source=pathlib.Path('downloaded-chain-history') + target=pathlib.Path('canonical-chain-history') + if target.exists(): + shutil.rmtree(target) + target.mkdir() + + overall={'fixed_head':head,'matrix_result':collect_result,'events':{},'production_approved_wallets':0} + global_failures=[] + for event in ('seadrop','seaport'): + event_dir=target/event + event_dir.mkdir() + logs={} + ranges=[] + shard_validations=[] + missing=[] + for shard in range(64): + d=source/f'rhc-chain-{event}-{shard}' + if not d.exists(): + missing.append(shard) + continue + validation_path=d/'validation.json' + coverage_path=d/'coverage.json' + log_path=d/'logs.jsonl.gz' + if not validation_path.exists() or not coverage_path.exists() or not log_path.exists(): + missing.append(shard) + continue + validation=json.loads(validation_path.read_text(encoding='utf-8')) + coverage=json.loads(coverage_path.read_text(encoding='utf-8')) + shard_validations.append(validation) + ranges.append((coverage['start'],coverage['end'],shard)) + with gzip.open(log_path,'rt',encoding='utf-8') as handle: + for line in handle: + row=json.loads(line) + key=(row.get('blockHash'),row.get('transactionHash'),row.get('logIndex')) + logs[key]=row + failures=[] + if missing: + failures.append({'code':'MISSING_SHARDS','shards':missing}) + if any(row.get('status')!='PASS' for row in shard_validations): + failures.append({'code':'SHARD_VALIDATION_FAILED','count':sum(row.get('status')!='PASS' for row in shard_validations)}) + ranges.sort() + expected_start=0 + for start,end,shard in ranges: + if start!=expected_start: + failures.append({'code':'COVERAGE_GAP_OR_OVERLAP','expected_start':expected_start,'actual_start':start,'shard':shard}) + expected_start=end+1 + if expected_start!=head+1: + failures.append({'code':'HEAD_NOT_COVERED','expected_end':head,'actual_end':expected_start-1}) + + ordered=sorted(logs.values(),key=lambda row:(int(row['blockNumber'],16),int(row.get('transactionIndex') or '0x0',16),int(row['logIndex'],16))) + out=event_dir/f'{event}_logs.jsonl.gz' + with gzip.open(out,'wt',encoding='utf-8',newline='\n') as handle: + for row in ordered: + handle.write(json.dumps(row,ensure_ascii=False,sort_keys=True)+'\n') + validation={ + 'status':'PASS' if not failures else 'FAIL', + 'event':event, + 'fixed_head':head, + 'shards_expected':64, + 'shards_found':len(ranges), + 'unique_logs':len(ordered), + 'failures':failures, + 'matrix_result':collect_result, + } + (event_dir/'VALIDATION.json').write_text(json.dumps(validation,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + overall['events'][event]=validation + global_failures.extend({'event':event,**failure} for failure in failures) + + overall['status']='PASS' if not global_failures and collect_result=='success' else 'FAIL' + overall['failures']=global_failures + (target/'VALIDATION.json').write_text(json.dumps(overall,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + + manifest=[] + for p in sorted(target.rglob('*')): + if p.is_file(): + manifest.append({'path':str(p.relative_to(target)),'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()}) + (target/'MANIFEST.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + print(json.dumps(overall,sort_keys=True)) + PY + - name: Commit raw canonical history and validation + shell: bash + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add canonical-chain-history + if ! git diff --cached --quiet; then + git commit -m 'data: persist canonical SeaDrop and Seaport histories' + git push origin HEAD:chatgpt/rhc-wallet-verification-p0p1-20260828 + fi + python - <<'PY' + import json + data=json.load(open('canonical-chain-history/VALIDATION.json')) + if data['status']!='PASS': + raise SystemExit(1) + PY diff --git a/.github/workflows/rhc-finalize-p1.yml b/.github/workflows/rhc-finalize-p1.yml new file mode 100644 index 0000000..fbb35fe --- /dev/null +++ b/.github/workflows/rhc-finalize-p1.yml @@ -0,0 +1,148 @@ +name: Finalize RHC P1 verification checkpoint + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-finalize-p1.yml' + - 'recovered-p1-partial-checkpoint/RECOVERY_REPORT.json' + +permissions: + actions: read + contents: write + +concurrency: + group: rhc-finalize-p1-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + finalize: + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + GH_TOKEN: ${{ github.token }} + TARGET_WORKFLOW_NAME: 'Re-run only missing RHC P1 shards' + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 1 + - name: Determine whether finalization is ready + id: plan + shell: bash + run: | + set -euo pipefail + python - <<'PY' >> "$GITHUB_OUTPUT" + import json + from pathlib import Path + p=Path('recovered-p1-partial-checkpoint/RECOVERY_REPORT.json') + if not p.exists(): + print('ready=false') + print('has_missing=false') + print('missing_json=[]') + else: + d=json.loads(p.read_text()) + missing=[k for k,v in d.get('shards',{}).items() if v.get('status')!='RECOVERED_NONEMPTY'] + print('ready=true') + print('has_missing='+('true' if missing else 'false')) + print('missing_json='+json.dumps(missing,separators=(',',':'))) + PY + - name: Stop cleanly until recovery report exists + if: steps.plan.outputs.ready != 'true' + run: echo 'Recovery report is not present yet; no data collection or re-run was started.' + - name: Reconstruct recovered partial package + if: steps.plan.outputs.ready == 'true' + shell: bash + run: | + set -euo pipefail + cat recovered-p1-partial-checkpoint/rhc-p1-partial-recovered.tar.gz.part-* > rhc-p1-partial-recovered.tar.gz + expected=$(awk '{print $1}' recovered-p1-partial-checkpoint/SHA256SUMS.txt) + actual=$(sha256sum rhc-p1-partial-recovered.tar.gz | awk '{print $1}') + test "$expected" = "$actual" + rm -rf final-p1-work + mkdir final-p1-work + tar -xzf rhc-p1-partial-recovered.tar.gz -C final-p1-work + - name: Wait for and download targeted missing-shard artifacts + if: steps.plan.outputs.ready == 'true' && steps.plan.outputs.has_missing == 'true' + shell: bash + run: | + set -euo pipefail + branch='${{ github.event.pull_request.head.ref }}' + run_id='' + for i in $(seq 1 180); do + run_id=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs?branch=${branch}&per_page=100" --jq ".workflow_runs | map(select(.name == env.TARGET_WORKFLOW_NAME)) | sort_by(.id) | last | .id // empty") + if [ -n "$run_id" ]; then + status=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}" --jq '.status') + conclusion=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}" --jq '.conclusion // ""') + echo "targeted run ${run_id}: ${status} ${conclusion}" + if [ "$status" = completed ]; then + test "$conclusion" = success + break + fi + fi + sleep 15 + done + test -n "$run_id" + mkdir -p final-p1-work/targeted-reruns + gh run download "$run_id" -R "$GITHUB_REPOSITORY" -D final-p1-work/targeted-reruns + echo "$run_id" > final-p1-work/TARGETED_RERUN_ID.txt + - name: Build final four-shard evidence checkpoint + if: steps.plan.outputs.ready == 'true' + shell: bash + run: | + set -euo pipefail + python - <<'PY' + from pathlib import Path + import csv,json,hashlib,shutil + root=Path('final-p1-work') + candidates=[p for p in root.rglob('*') if p.is_file()] + report={'status':'PASS','shards':{}} + for shard in ('p1a','p1b','p1c','p1d'): + paths=[p for p in candidates if shard in str(p).lower()] + counts={};substantive=0 + for p in paths: + if p.suffix.lower()=='.csv': + try: + with p.open(newline='',encoding='utf-8-sig') as f:n=sum(1 for _ in csv.DictReader(f)) + counts[str(p)]=n + if n>0 and any(k in p.name.lower() for k in ('transaction','transfer','receipt','event','evidence','summary','wallet')):substantive+=n + except Exception as exc:counts[str(p)]={'error':repr(exc)} + elif p.suffix.lower()=='.json': + try: + obj=json.loads(p.read_text(encoding='utf-8-sig'));counts[str(p)]={'json_type':type(obj).__name__} + except Exception as exc:counts[str(p)]={'error':repr(exc)} + status='PASS_NONEMPTY' if paths and substantive>0 else 'FAIL_EMPTY_OR_MISSING' + if status!='PASS_NONEMPTY':report['status']='FAIL' + report['shards'][shard]={'status':status,'file_count':len(paths),'substantive_rows':substantive,'row_counts':counts} + report['production_approved_wallets']=0 + report['decision_use']='RESEARCH_EVIDENCE_ONLY_NOT_STRONG_WALLET_APPROVAL' + Path('final-p1-work/P1_FINAL_VALIDATION.json').write_text(json.dumps(report,indent=2,sort_keys=True),encoding='utf-8') + print(json.dumps(report,indent=2,sort_keys=True)) + if report['status']!='PASS':raise SystemExit(1) + # immutable manifest over final work before packaging + rows=[] + for p in sorted(root.rglob('*')): + if p.is_file():rows.append({'path':str(p.relative_to(root)),'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()}) + Path('final-p1-work/MANIFEST.json').write_text(json.dumps(rows,indent=2,sort_keys=True),encoding='utf-8') + PY + tar -czf rhc-p1-final.tar.gz final-p1-work + rm -rf recovered-p1-final-checkpoint + mkdir recovered-p1-final-checkpoint + split -b 40m -d -a 3 rhc-p1-final.tar.gz recovered-p1-final-checkpoint/rhc-p1-final.tar.gz.part- + sha256sum rhc-p1-final.tar.gz > recovered-p1-final-checkpoint/SHA256SUMS.txt + cp final-p1-work/P1_FINAL_VALIDATION.json final-p1-work/MANIFEST.json recovered-p1-final-checkpoint/ + - name: Commit final P1 checkpoint to temporary branch + if: steps.plan.outputs.ready == 'true' + shell: bash + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add recovered-p1-final-checkpoint + if git diff --cached --quiet; then + echo 'No final P1 checkpoint changes.' + else + git commit -m 'chore: finalize complete P1 verification checkpoint' + git push origin "HEAD:${{ github.event.pull_request.head.ref }}" + fi diff --git a/.github/workflows/rhc-nft-population-history.yml b/.github/workflows/rhc-nft-population-history.yml new file mode 100644 index 0000000..19a9e45 --- /dev/null +++ b/.github/workflows/rhc-nft-population-history.yml @@ -0,0 +1,226 @@ +name: RHC NFT population and complete transfer history + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-nft-population-history.yml' + - 'nft-population/**' + - 'nft-population-trigger.txt' + +permissions: + actions: read + contents: write + +concurrency: + group: rhc-nft-population-history-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + head: + runs-on: ubuntu-latest + outputs: + head: ${{ steps.capture.outputs.head }} + steps: + - id: capture + shell: bash + run: | + set -euo pipefail + python - <<'PY' >> "$GITHUB_OUTPUT" + import json, urllib.request + body=json.dumps({'jsonrpc':'2.0','id':1,'method':'eth_blockNumber','params':[]}).encode() + req=urllib.request.Request('https://rpc.mainnet.chain.robinhood.com',data=body,headers={'Content-Type':'application/json','User-Agent':'rhc-nft-head/1.0'}) + with urllib.request.urlopen(req,timeout=60) as response: + result=json.loads(response.read())['result'] + print(f'head={max(0,int(result,16)-1000)}') + PY + + population: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Fetch complete ERC-721 and ERC-1155 contract population + run: python nft-population/fetch_population.py --out out-population + - uses: actions/upload-artifact@v4 + if: always() + with: + name: rhc-nft-contract-population + path: out-population/ + retention-days: 3 + if-no-files-found: error + compression-level: 9 + + transfers: + needs: [head, population] + strategy: + fail-fast: false + max-parallel: 6 + matrix: + shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31] + runs-on: ubuntu-latest + timeout-minutes: 150 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install deterministic Keccak implementation + run: python -m pip install --disable-pip-version-check --no-input pycryptodome==3.23.0 + - uses: actions/download-artifact@v4 + with: + name: rhc-nft-contract-population + path: population-input + - name: Collect full transfer history for contract shard + shell: bash + run: | + set -euo pipefail + python nft-population/collect_transfers.py \ + --contracts population-input/contracts.jsonl \ + --shard '${{ matrix.shard }}' \ + --shards 32 \ + --head '${{ needs.head.outputs.head }}' \ + --out 'out-transfers-${{ matrix.shard }}' + - uses: actions/upload-artifact@v4 + if: always() + with: + name: rhc-nft-transfers-${{ matrix.shard }} + path: out-transfers-${{ matrix.shard }}/ + retention-days: 3 + if-no-files-found: error + compression-level: 9 + + aggregate: + needs: [head, population, transfers] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + with: + ref: chatgpt/rhc-wallet-verification-p0p1-20260828 + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - uses: actions/download-artifact@v4 + with: + name: rhc-nft-contract-population + path: downloaded-population + - uses: actions/download-artifact@v4 + with: + pattern: rhc-nft-transfers-* + path: downloaded-transfers + - name: Merge population, transfer lifecycle, and mint transaction evidence + env: + FIXED_HEAD: ${{ needs.head.outputs.head }} + POPULATION_RESULT: ${{ needs.population.result }} + TRANSFER_RESULT: ${{ needs.transfers.result }} + shell: bash + run: | + set -euo pipefail + python - <<'PY' + import gzip, hashlib, json, os, pathlib, shutil + + population=pathlib.Path('downloaded-population') + shards=pathlib.Path('downloaded-transfers') + target=pathlib.Path('nft-population-results') + if target.exists(): + shutil.rmtree(target) + target.mkdir() + shutil.copytree(population,target/'population') + + contracts=[json.loads(line) for line in (population/'contracts.jsonl').read_text(encoding='utf-8').splitlines() if line.strip()] + expected_addresses={row['contract_address'].lower() for row in contracts} + seen_addresses=set() + transfer_rows={} + mint_rows={} + validations=[] + missing=[] + for shard in range(32): + d=shards/f'rhc-nft-transfers-{shard}' + if not d.exists(): + missing.append(shard) + continue + validation_path=d/'VALIDATION.json' + if not validation_path.exists(): + missing.append(shard) + continue + validation=json.loads(validation_path.read_text(encoding='utf-8')) + validations.append(validation) + selected_path=d/'selected_contracts.json' + if selected_path.exists(): + selected=json.loads(selected_path.read_text(encoding='utf-8')) + seen_addresses.update(row['contract_address'].lower() for row in selected) + transfer_path=d/'transfers.jsonl.gz' + if transfer_path.exists(): + with gzip.open(transfer_path,'rt',encoding='utf-8') as handle: + for line in handle: + row=json.loads(line) + key=(row['block_hash'],row['transaction_hash'],row['log_index'],row.get('batch_item_index',0)) + transfer_rows[key]=row + mint_path=d/'mint_transactions.jsonl' + if mint_path.exists(): + for line in mint_path.read_text(encoding='utf-8').splitlines(): + if line.strip(): + row=json.loads(line) + key=(row['transaction_hash'],row['contract_address']) + mint_rows[key]=row + + failures=[] + if missing: + failures.append({'code':'MISSING_TRANSFER_SHARDS','shards':missing}) + failed=[row for row in validations if row.get('status')!='PASS'] + if failed: + failures.append({'code':'TRANSFER_SHARD_FAILURE','count':len(failed)}) + if seen_addresses!=expected_addresses: + failures.append({'code':'CONTRACT_COVERAGE_MISMATCH','missing':sorted(expected_addresses-seen_addresses),'unexpected':sorted(seen_addresses-expected_addresses)}) + + with gzip.open(target/'all_transfers.jsonl.gz','wt',encoding='utf-8',newline='\n') as handle: + for row in sorted(transfer_rows.values(),key=lambda value:(value['block_number'],value['transaction_index'],value['log_index'],value.get('batch_item_index',0))): + handle.write(json.dumps(row,ensure_ascii=False,sort_keys=True)+'\n') + with (target/'all_mint_transactions.jsonl').open('w',encoding='utf-8',newline='\n') as handle: + for row in sorted(mint_rows.values(),key=lambda value:(value['block_number'],value['transaction_hash'],value['contract_address'])): + handle.write(json.dumps(row,ensure_ascii=False,sort_keys=True)+'\n') + + report={ + 'status':'PASS' if not failures and os.environ['POPULATION_RESULT']=='success' and os.environ['TRANSFER_RESULT']=='success' else 'FAIL', + 'fixed_head':int(os.environ['FIXED_HEAD']), + 'population_job_result':os.environ['POPULATION_RESULT'], + 'transfer_matrix_result':os.environ['TRANSFER_RESULT'], + 'contract_count':len(expected_addresses), + 'contracts_covered':len(seen_addresses), + 'decoded_transfer_rows':len(transfer_rows), + 'mint_transaction_rows':len(mint_rows), + 'shards_found':len(validations), + 'failures':failures, + 'production_approved_wallets':0, + } + (target/'VALIDATION.json').write_text(json.dumps(report,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + manifest=[] + for p in sorted(target.rglob('*')): + if p.is_file(): + manifest.append({'path':str(p.relative_to(target)),'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()}) + (target/'MANIFEST.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + print(json.dumps(report,sort_keys=True)) + PY + - name: Commit complete NFT population and lifecycle evidence + shell: bash + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add nft-population-results + if ! git diff --cached --quiet; then + git commit -m 'data: persist complete NFT population and transfer histories' + git push origin HEAD:chatgpt/rhc-wallet-verification-p0p1-20260828 + fi + python - <<'PY' + import json + report=json.load(open('nft-population-results/VALIDATION.json')) + if report['status']!='PASS': + raise SystemExit(1) + PY diff --git a/.github/workflows/rhc-recover-p1-artifacts.yml b/.github/workflows/rhc-recover-p1-artifacts.yml new file mode 100644 index 0000000..399a68f --- /dev/null +++ b/.github/workflows/rhc-recover-p1-artifacts.yml @@ -0,0 +1,136 @@ +name: Recover completed RHC P1 artifacts + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-recover-p1-artifacts.yml' + +permissions: + actions: read + contents: write + +concurrency: + group: rhc-recover-p1-artifacts-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + recover: + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + SOURCE_RUN_ID: '33167365485' + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 1 + - name: Wait for existing P1 run only + shell: bash + run: | + set -euo pipefail + for i in $(seq 1 120); do + status=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}" --jq '.status') + conclusion=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}" --jq '.conclusion // ""') + echo "source run: status=${status} conclusion=${conclusion}" + if [ "$status" = completed ]; then + test "$conclusion" = success + exit 0 + fi + sleep 15 + done + echo 'Timed out waiting for existing run; no collection was re-run.' >&2 + exit 1 + - name: Download existing artifacts + shell: bash + run: | + set -euo pipefail + rm -rf recovered-p1-work + mkdir -p recovered-p1-work/download + gh run download "$SOURCE_RUN_ID" -R "$GITHUB_REPOSITORY" -D recovered-p1-work/download + find recovered-p1-work/download -maxdepth 3 -type f -print | sort + - name: Validate P1 shards fail-closed + shell: bash + run: | + set -euo pipefail + python - <<'PY' + from pathlib import Path + import csv, json, hashlib + + root = Path('recovered-p1-work/download') + report = {'source_run_id': 33167365485, 'status': 'PASS', 'shards': {}} + for shard in ('p1a','p1b','p1c','p1d'): + paths = [p for p in root.rglob('*') if p.is_file() and shard in str(p).lower()] + row_counts = {} + substantive = 0 + for p in paths: + if p.suffix.lower() == '.csv': + try: + with p.open(newline='', encoding='utf-8-sig') as f: + n = sum(1 for _ in csv.DictReader(f)) + row_counts[str(p)] = n + if n > 0 and any(k in p.name.lower() for k in ('transaction','transfer','receipt','event','evidence','summary','wallet')): + substantive += n + except Exception as exc: + row_counts[str(p)] = {'error': repr(exc)} + elif p.suffix.lower() == '.json': + try: + obj = json.loads(p.read_text(encoding='utf-8-sig')) + row_counts[str(p)] = {'json_type': type(obj).__name__} + except Exception as exc: + row_counts[str(p)] = {'error': repr(exc)} + shard_status = 'PASS' if paths and substantive > 0 else 'FAIL_EMPTY_OR_MISSING' + if shard_status != 'PASS': + report['status'] = 'FAIL' + report['shards'][shard] = { + 'status': shard_status, + 'file_count': len(paths), + 'substantive_rows': substantive, + 'row_counts': row_counts, + } + Path('recovered-p1-work/VALIDATION.json').write_text(json.dumps(report, indent=2, sort_keys=True), encoding='utf-8') + print(json.dumps(report, indent=2, sort_keys=True)) + if report['status'] != 'PASS': + raise SystemExit(1) + PY + - name: Package immutable recovery checkpoint + shell: bash + run: | + set -euo pipefail + rm -rf recovered-p1 + mkdir -p recovered-p1 + cp -a recovered-p1-work/download recovered-p1/source-artifacts + cp recovered-p1-work/VALIDATION.json recovered-p1/ + python - <<'PY' + from pathlib import Path + import hashlib, json + root=Path('recovered-p1') + rows=[] + for p in sorted(root.rglob('*')): + if p.is_file(): + rows.append({'path':str(p.relative_to(root)),'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()}) + (root/'MANIFEST.json').write_text(json.dumps(rows,indent=2,sort_keys=True),encoding='utf-8') + PY + tar -czf rhc-p1-recovered.tar.gz recovered-p1 + rm -rf recovered-p1-commit + mkdir recovered-p1-commit + split -b 40m -d -a 3 rhc-p1-recovered.tar.gz recovered-p1-commit/rhc-p1-recovered.tar.gz.part- + sha256sum rhc-p1-recovered.tar.gz > recovered-p1-commit/SHA256SUMS.txt + cp recovered-p1/VALIDATION.json recovered-p1/ MANIFEST.json recovered-p1-commit/ 2>/dev/null || true + - name: Commit recovery chunks to temporary branch + shell: bash + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + rm -rf recovered-p1-checkpoint + mv recovered-p1-commit recovered-p1-checkpoint + git add recovered-p1-checkpoint + if git diff --cached --quiet; then + echo 'No recovery changes.' + else + git commit -m 'chore: checkpoint recovered P1 verification artifacts' + git push origin "HEAD:${{ github.event.pull_request.head.ref }}" + fi diff --git a/.github/workflows/rhc-recover-p1-partial.yml b/.github/workflows/rhc-recover-p1-partial.yml new file mode 100644 index 0000000..81498a3 --- /dev/null +++ b/.github/workflows/rhc-recover-p1-partial.yml @@ -0,0 +1,125 @@ +name: Recover available RHC P1 artifacts fail-closed + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-recover-p1-partial.yml' + +permissions: + actions: read + contents: write + +concurrency: + group: rhc-recover-p1-partial-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + recover: + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + SOURCE_RUN_ID: '33167365485' + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 1 + - name: Wait until source run is no longer active + shell: bash + run: | + set -euo pipefail + for i in $(seq 1 120); do + status=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}" --jq '.status') + conclusion=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}" --jq '.conclusion // ""') + echo "source run: status=${status} conclusion=${conclusion}" + if [ "$status" = completed ]; then exit 0; fi + sleep 15 + done + echo 'Timed out waiting for source run; no collection was re-run.' >&2 + exit 1 + - name: Record source jobs and download every available artifact + shell: bash + run: | + set -euo pipefail + rm -rf recovered-p1-partial-work + mkdir -p recovered-p1-partial-work/download + gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}/jobs?per_page=100" > recovered-p1-partial-work/source_jobs.json + gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}/artifacts?per_page=100" > recovered-p1-partial-work/source_artifacts.json + gh run download "$SOURCE_RUN_ID" -R "$GITHUB_REPOSITORY" -D recovered-p1-partial-work/download || true + find recovered-p1-partial-work/download -maxdepth 4 -type f -print | sort + - name: Classify recovered and missing shards + shell: bash + run: | + set -euo pipefail + python - <<'PY' + from pathlib import Path + import csv, json, hashlib + root=Path('recovered-p1-partial-work/download') + jobs=json.loads(Path('recovered-p1-partial-work/source_jobs.json').read_text()) + artifacts=json.loads(Path('recovered-p1-partial-work/source_artifacts.json').read_text()) + report={ + 'source_run_id':33167365485, + 'source_jobs':[{'id':j.get('id'),'name':j.get('name'),'status':j.get('status'),'conclusion':j.get('conclusion')} for j in jobs.get('jobs',[])], + 'source_artifacts':[{'id':a.get('id'),'name':a.get('name'),'size_in_bytes':a.get('size_in_bytes'),'expired':a.get('expired')} for a in artifacts.get('artifacts',[])], + 'shards':{}, + } + recovered=0 + for shard in ('p1a','p1b','p1c','p1d'): + paths=[p for p in root.rglob('*') if p.is_file() and shard in str(p).lower()] + counts={}; substantive=0 + for p in paths: + if p.suffix.lower()=='.csv': + try: + with p.open(newline='',encoding='utf-8-sig') as f:n=sum(1 for _ in csv.DictReader(f)) + counts[str(p)]=n + if n>0 and any(k in p.name.lower() for k in ('transaction','transfer','receipt','event','evidence','summary','wallet')):substantive+=n + except Exception as exc:counts[str(p)]={'error':repr(exc)} + elif p.suffix.lower()=='.json': + try: + obj=json.loads(p.read_text(encoding='utf-8-sig')) + counts[str(p)]={'json_type':type(obj).__name__} + except Exception as exc:counts[str(p)]={'error':repr(exc)} + status='RECOVERED_NONEMPTY' if paths and substantive>0 else 'MISSING_OR_EMPTY' + if status=='RECOVERED_NONEMPTY':recovered+=1 + report['shards'][shard]={'status':status,'file_count':len(paths),'substantive_rows':substantive,'row_counts':counts} + report['recovered_shards']=recovered + report['missing_shards']=4-recovered + report['status']='PASS_ALL' if recovered==4 else ('PARTIAL' if recovered>0 else 'FAIL_NONE') + Path('recovered-p1-partial-work/RECOVERY_REPORT.json').write_text(json.dumps(report,indent=2,sort_keys=True),encoding='utf-8') + print(json.dumps(report,indent=2,sort_keys=True)) + PY + - name: Package and commit recovery checkpoint + shell: bash + run: | + set -euo pipefail + rm -rf recovered-p1-partial recovered-p1-partial-commit + mkdir -p recovered-p1-partial recovered-p1-partial-commit + cp -a recovered-p1-partial-work/download recovered-p1-partial/source-artifacts + cp recovered-p1-partial-work/source_jobs.json recovered-p1-partial-work/source_artifacts.json recovered-p1-partial-work/RECOVERY_REPORT.json recovered-p1-partial/ + python - <<'PY' + from pathlib import Path + import hashlib,json + root=Path('recovered-p1-partial') + rows=[] + for p in sorted(root.rglob('*')): + if p.is_file():rows.append({'path':str(p.relative_to(root)),'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()}) + (root/'MANIFEST.json').write_text(json.dumps(rows,indent=2,sort_keys=True),encoding='utf-8') + PY + tar -czf rhc-p1-partial-recovered.tar.gz recovered-p1-partial + split -b 40m -d -a 3 rhc-p1-partial-recovered.tar.gz recovered-p1-partial-commit/rhc-p1-partial-recovered.tar.gz.part- + sha256sum rhc-p1-partial-recovered.tar.gz > recovered-p1-partial-commit/SHA256SUMS.txt + cp recovered-p1-partial/RECOVERY_REPORT.json recovered-p1-partial/MANIFEST.json recovered-p1-partial-commit/ + rm -rf recovered-p1-partial-checkpoint + mv recovered-p1-partial-commit recovered-p1-partial-checkpoint + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add recovered-p1-partial-checkpoint + if git diff --cached --quiet; then + echo 'No recovery changes.' + else + git commit -m 'chore: checkpoint all available P1 artifacts' + git push origin "HEAD:${{ github.event.pull_request.head.ref }}" + fi diff --git a/.github/workflows/rhc-rerun-missing-p1.yml b/.github/workflows/rhc-rerun-missing-p1.yml new file mode 100644 index 0000000..b33f783 --- /dev/null +++ b/.github/workflows/rhc-rerun-missing-p1.yml @@ -0,0 +1,112 @@ +name: Re-run only missing RHC P1 shards + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-rerun-missing-p1.yml' + - 'recovered-p1-partial-checkpoint/RECOVERY_REPORT.json' + +permissions: + contents: read + +concurrency: + group: rhc-rerun-missing-p1-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + plan: + runs-on: ubuntu-latest + outputs: + missing_json: ${{ steps.plan.outputs.missing_json }} + has_missing: ${{ steps.plan.outputs.has_missing }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + - id: plan + shell: bash + run: | + set -euo pipefail + python - <<'PY' >> "$GITHUB_OUTPUT" + import json + from pathlib import Path + p=Path('recovered-p1-partial-checkpoint/RECOVERY_REPORT.json') + if not p.exists(): + print('missing_json=[]') + print('has_missing=false') + else: + d=json.loads(p.read_text()) + missing=[k for k,v in d.get('shards',{}).items() if v.get('status')!='RECOVERED_NONEMPTY'] + print('missing_json='+json.dumps(missing,separators=(',',':'))) + print('has_missing='+('true' if missing else 'false')) + PY + + rerun: + needs: plan + if: needs.plan.outputs.has_missing == 'true' + strategy: + fail-fast: false + matrix: + shard: ${{ fromJSON(needs.plan.outputs.missing_json) }} + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Materialize collector and exact missing shard + shell: bash + env: + SHARD: ${{ matrix.shard }} + run: | + set -euo pipefail + mkdir -p runtime + base64 -d wallet-verification/collector.py.gz.b64 | gzip -d > runtime/collector.py + base64 -d wallet-verification/wallets.json.gz.b64 | gzip -d > runtime/all_wallets.json + python -m py_compile runtime/collector.py + python - <<'PY' + import json, os + rows=json.load(open('runtime/all_wallets.json')) + p1=[r for r in rows if r.get('priority')=='P1'] + groups={'p1a':p1[0:5],'p1b':p1[5:10],'p1c':p1[10:15],'p1d':p1[15:19]} + shard=os.environ['SHARD'] + selected=groups[shard] + assert selected and all(r.get('priority')=='P1' for r in selected) + json.dump(selected,open('runtime/wallets.json','w'),indent=2) + print({'shard':shard,'wallets':len(selected)}) + PY + - name: Collect missing shard once + shell: bash + run: python runtime/collector.py --wallets runtime/wallets.json --out "out-${{ matrix.shard }}" + - name: Fail closed on empty evidence + shell: bash + run: | + set -euo pipefail + python - <<'PY' + from pathlib import Path + import csv,json + root=Path('out-${{ matrix.shard }}') + files=list(root.rglob('*')) + substantive=0 + for p in files: + if p.is_file() and p.suffix.lower()=='.csv': + with p.open(newline='',encoding='utf-8-sig') as f:n=sum(1 for _ in csv.DictReader(f)) + if n>0 and any(k in p.name.lower() for k in ('transaction','transfer','receipt','event','evidence','summary','wallet')):substantive+=n + report={'shard':'${{ matrix.shard }}','file_count':sum(p.is_file() for p in files),'substantive_rows':substantive,'status':'PASS' if substantive>0 else 'FAIL_EMPTY'} + (root/'RERUN_VALIDATION.json').write_text(json.dumps(report,indent=2),encoding='utf-8') + print(report) + if substantive<=0:raise SystemExit(1) + PY + - uses: actions/upload-artifact@v4 + if: always() + with: + name: rhc-wallet-verification-${{ matrix.shard }}-targeted-rerun + path: out-${{ matrix.shard }}/ + retention-days: 3 + if-no-files-found: error + compression-level: 9 diff --git a/.github/workflows/rhc-wallet-intelligence-build.yml b/.github/workflows/rhc-wallet-intelligence-build.yml new file mode 100644 index 0000000..553e4d6 --- /dev/null +++ b/.github/workflows/rhc-wallet-intelligence-build.yml @@ -0,0 +1,70 @@ +name: Build RHC wallet intelligence datasets + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-wallet-intelligence-build.yml' + - 'wallet-intelligence/**' + - 'canonical-chain-history/**' + - 'nft-population-results/**' + +permissions: + contents: write + +concurrency: + group: rhc-wallet-intelligence-build-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 180 + steps: + - uses: actions/checkout@v4 + with: + ref: chatgpt/rhc-wallet-verification-p0p1-20260828 + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Verify upstream evidence is present + shell: bash + run: | + set -euo pipefail + test -f canonical-chain-history/VALIDATION.json + test -f nft-population-results/VALIDATION.json + python - <<'PY' + import json + for path in ('canonical-chain-history/VALIDATION.json','nft-population-results/VALIDATION.json'): + data=json.load(open(path)) + if data.get('status')!='PASS': + raise SystemExit(f'{path} not PASS: {data}') + PY + - name: Build project universe, outcomes, Selection Alpha, Execution Alpha, and Copy proxy + shell: bash + run: | + set -euo pipefail + rm -rf wallet-intelligence-results + python wallet-intelligence/build_dataset.py \ + --chain-history canonical-chain-history \ + --nft-population nft-population-results \ + --out wallet-intelligence-results + - name: Commit immutable normalized data release + shell: bash + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add wallet-intelligence-results + if ! git diff --cached --quiet; then + git commit -m 'data: build deterministic Robinhood wallet intelligence datasets' + git push origin HEAD:chatgpt/rhc-wallet-verification-p0p1-20260828 + fi + python - <<'PY' + import json + data=json.load(open('wallet-intelligence-results/VALIDATION.json')) + if data['status']!='PASS': + raise SystemExit(1) + PY diff --git a/.github/workflows/rhc-wallet-verification-consolidate.yml b/.github/workflows/rhc-wallet-verification-consolidate.yml new file mode 100644 index 0000000..29dd2ef --- /dev/null +++ b/.github/workflows/rhc-wallet-verification-consolidate.yml @@ -0,0 +1,73 @@ +name: Consolidate RHC wallet verification P0-P1 artifacts + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-wallet-verification-consolidate.yml' + - 'wallet-verification/consolidate_previous_run.py' + +permissions: + actions: read + contents: write + +concurrency: + group: rhc-wallet-verification-consolidate-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + consolidate: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Download and audit the previous P0-P1 shard artifacts + id: consolidate + continue-on-error: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SOURCE_RUN_ID: '33167365485' + run: | + python wallet-verification/consolidate_previous_run.py + echo "exit_code=0" >> "$GITHUB_OUTPUT" + - name: Preserve machine-readable checkpoint on the research branch + if: always() + shell: bash + run: | + set -euo pipefail + test -f out-p0p1-consolidated/consolidated_status.json + cp out-p0p1-consolidated/consolidated_status.json wallet-verification/consolidated_status.json + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add wallet-verification/consolidated_status.json + if ! git diff --cached --quiet; then + git commit -m 'chore: record P0-P1 evidence consolidation checkpoint' + git push origin HEAD:${{ github.event.pull_request.head.ref }} + fi + - uses: actions/upload-artifact@v4 + if: always() + with: + name: rhc-wallet-verification-p0p1-consolidated + path: | + out-p0p1-consolidated/ + rhc-wallet-verification-p0p1-consolidated.zip + retention-days: 7 + if-no-files-found: error + compression-level: 9 + - name: Enforce complete evidence set + if: always() + shell: bash + run: | + python - <<'PY' + import json + report=json.load(open('out-p0p1-consolidated/consolidated_status.json',encoding='utf-8')) + print(json.dumps(report, indent=2, sort_keys=True)) + if report.get('status') != 'PASS': + raise SystemExit(1) + PY diff --git a/.github/workflows/rhc-wallet-verification-p0p1.yml b/.github/workflows/rhc-wallet-verification-p0p1.yml new file mode 100644 index 0000000..08a1d84 --- /dev/null +++ b/.github/workflows/rhc-wallet-verification-p0p1.yml @@ -0,0 +1,81 @@ +name: RHC wallet verification P0-P1 + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-wallet-verification-p0p1.yml' + - 'wallet-verification/**' + +permissions: + contents: read + +concurrency: + group: rhc-wallet-verification-p0p1-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + verify: + strategy: + fail-fast: false + matrix: + shard: [p0, p1a, p1b, p1c, p1d] + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Materialize audited collector and fixed shard input + shell: bash + env: + SHARD: ${{ matrix.shard }} + run: | + set -euo pipefail + mkdir -p runtime + base64 -d wallet-verification/collector.py.gz.b64 | gzip -d > runtime/collector.py + base64 -d wallet-verification/wallets.json.gz.b64 | gzip -d > runtime/wallets-all.json + python -m py_compile runtime/collector.py + python - <<'PY' + import json, os + rows=json.load(open('runtime/wallets-all.json')) + p0=[row for row in rows if row['priority']=='P0'] + p1=[row for row in rows if row['priority']=='P1'] + shard=os.environ['SHARD'] + slices={ + 'p0': (0,0), + 'p1a': (0,5), + 'p1b': (5,10), + 'p1c': (10,15), + 'p1d': (15,19), + } + start,end=slices[shard] + selected=p0 if shard=='p0' else p0+p1[start:end] + json.dump(selected,open('runtime/wallets.json','w'),indent=2) + assert len(p0)==1 and len(p1)==19 + assert sum(row['priority']=='P0' for row in selected)==1 + print({'shard':shard,'wallets':len(selected),'p0':1,'p1':len(selected)-1}) + PY + - name: Collect and verify canonical evidence + shell: bash + env: + SHARD: ${{ matrix.shard }} + run: python runtime/collector.py --wallets runtime/wallets.json --out "out-${SHARD}" + - name: Preserve collector and fixed inputs in artifact + if: always() + shell: bash + env: + SHARD: ${{ matrix.shard }} + run: | + mkdir -p "out-${SHARD}/runtime" + cp runtime/collector.py runtime/wallets.json runtime/wallets-all.json "out-${SHARD}/runtime/" + - uses: actions/upload-artifact@v4 + if: always() + with: + name: rhc-wallet-verification-${{ matrix.shard }} + path: out-${{ matrix.shard }}/ + retention-days: 3 + if-no-files-found: error + compression-level: 9 diff --git a/.github/workflows/rhc-wallet-verification-p1-results.yml b/.github/workflows/rhc-wallet-verification-p1-results.yml new file mode 100644 index 0000000..c6df6f0 --- /dev/null +++ b/.github/workflows/rhc-wallet-verification-p1-results.yml @@ -0,0 +1,160 @@ +name: Persist RHC P1 verification results + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-wallet-verification-p1-results.yml' + - 'wallet-verification-results-trigger/p1.txt' + +permissions: + actions: read + contents: write + +concurrency: + group: persist-rhc-p1-results-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + persist: + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + with: + ref: chatgpt/rhc-wallet-verification-p0p1-20260828 + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Wait for source run, download every shard, and validate coverage + env: + GH_TOKEN: ${{ github.token }} + SOURCE_RUN_ID: '33167365485' + REPOSITORY: ${{ github.repository }} + shell: bash + run: | + set -euo pipefail + python - <<'PY' + import hashlib, io, json, os, pathlib, shutil, time, urllib.request, zipfile + + token=os.environ['GH_TOKEN'] + repo=os.environ['REPOSITORY'] + run_id=os.environ['SOURCE_RUN_ID'] + root=pathlib.Path('wallet-verification-results/p1') + if root.exists(): + shutil.rmtree(root) + raw=root/'raw' + raw.mkdir(parents=True) + + def api(path): + req=urllib.request.Request( + f'https://api.github.com/repos/{repo}{path}', + headers={ + 'Authorization': f'Bearer {token}', + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': 'rhc-verification-results-persist/1.0', + }, + ) + with urllib.request.urlopen(req, timeout=120) as r: + return r.read(), r.status, r.geturl() + + deadline=time.time()+6300 + run=None + while time.time()0] + if len(non_zip)<5: + raise SystemExit(f'insufficient extracted evidence files: {len(non_zip)}') + (root/'FILE_MANIFEST.json').write_text(json.dumps(inventory,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + print(json.dumps(run_record,sort_keys=True)) + PY + - name: Commit immutable P1 result bundle to research branch + shell: bash + run: | + set -euo pipefail + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add wallet-verification-results/p1 + if git diff --cached --quiet; then + echo 'No result changes to commit.' + exit 0 + fi + git commit -m 'data: persist P1 wallet verification evidence' + git push origin HEAD:chatgpt/rhc-wallet-verification-p0p1-20260828 diff --git a/.github/workflows/rhc-wallet-verification-p2-results.yml b/.github/workflows/rhc-wallet-verification-p2-results.yml new file mode 100644 index 0000000..da04955 --- /dev/null +++ b/.github/workflows/rhc-wallet-verification-p2-results.yml @@ -0,0 +1,169 @@ +name: Persist RHC P2 verification results + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-wallet-verification-p2-results.yml' + - 'wallet-verification-results-trigger/p2.txt' + +permissions: + actions: read + contents: write + +concurrency: + group: persist-rhc-p2-results-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + persist: + runs-on: ubuntu-latest + timeout-minutes: 180 + steps: + - uses: actions/checkout@v4 + with: + ref: chatgpt/rhc-wallet-verification-p0p1-20260828 + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Wait for latest P2 run, download all shards, and validate coverage + env: + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + SOURCE_BRANCH: chatgpt/rhc-wallet-verification-p0p1-20260828 + shell: bash + run: | + set -euo pipefail + python - <<'PY' + import hashlib, io, json, os, pathlib, shutil, time, urllib.parse, urllib.request, zipfile + + token=os.environ['GH_TOKEN'] + repo=os.environ['REPOSITORY'] + branch=os.environ['SOURCE_BRANCH'] + root=pathlib.Path('wallet-verification-results/p2') + if root.exists(): + shutil.rmtree(root) + raw=root/'raw' + raw.mkdir(parents=True) + + def api(path): + req=urllib.request.Request( + f'https://api.github.com/repos/{repo}{path}', + headers={ + 'Authorization': f'Bearer {token}', + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': 'rhc-p2-results-persist/1.0', + }, + ) + with urllib.request.urlopen(req, timeout=120) as r: + return r.read() + + deadline=time.time()+9900 + run=None + workflow_path='rhc-wallet-verification-p2.yml' + while time.time()0] + if len(non_zip)<16: + raise SystemExit(f'insufficient P2 extracted evidence files: {len(non_zip)}') + + run_record={ + 'status':'COLLECTED', + 'source_run_id':run_id, + 'source_status':run.get('status'), + 'source_conclusion':run.get('conclusion'), + 'source_head_sha':run.get('head_sha'), + 'source_created_at':run.get('created_at'), + 'source_updated_at':run.get('updated_at'), + 'artifact_count':len(manifest), + 'expected_wallets':212, + 'production_approved_wallets':0, + 'decision_use':'CANONICAL_EVIDENCE_RESEARCH_ONLY', + } + (root/'RUN.json').write_text(json.dumps(run_record,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + (root/'ARTIFACT_MANIFEST.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + (root/'FILE_MANIFEST.json').write_text(json.dumps(inventory,ensure_ascii=False,indent=2,sort_keys=True),encoding='utf-8') + print(json.dumps(run_record,sort_keys=True)) + PY + - name: Commit immutable P2 result bundle to research branch + if: always() + shell: bash + run: | + set -euo pipefail + if [ ! -d wallet-verification-results/p2 ]; then + exit 1 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add wallet-verification-results/p2 + if git diff --cached --quiet; then + echo 'No P2 result changes to commit.' + exit 0 + fi + git commit -m 'data: persist P2 wallet verification evidence' + git push origin HEAD:chatgpt/rhc-wallet-verification-p0p1-20260828 diff --git a/.github/workflows/rhc-wallet-verification-p2.yml b/.github/workflows/rhc-wallet-verification-p2.yml new file mode 100644 index 0000000..77aca98 --- /dev/null +++ b/.github/workflows/rhc-wallet-verification-p2.yml @@ -0,0 +1,97 @@ +name: RHC wallet verification P2 + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-wallet-verification-p2.yml' + - 'wallet-verification/collector.py.gz.b64' + - 'wallet-verification-p2/**' + +permissions: + contents: read + +concurrency: + group: rhc-wallet-verification-p2-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + verify: + strategy: + fail-fast: false + max-parallel: 8 + matrix: + shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Materialize collector and P2 shard + shell: bash + env: + SHARD: ${{ matrix.shard }} + run: | + set -euo pipefail + mkdir -p runtime + base64 -d wallet-verification/collector.py.gz.b64 | gzip -d > runtime/collector.py + base64 -d wallet-verification-p2/wallets-p2.json.gz.b64 | gzip -d > runtime/wallets-all.json + python -m py_compile runtime/collector.py + python - <<'PY' + import json, os + shard = int(os.environ['SHARD']) + rows = json.load(open('runtime/wallets-all.json', encoding='utf-8')) + if len(rows) != 212: + raise SystemExit(f'expected 212 P2 wallets, got {len(rows)}') + selected = [row for index, row in enumerate(rows) if index % 16 == shard] + if not selected: + raise SystemExit(f'empty shard {shard}') + json.dump(selected, open('runtime/wallets.json','w',encoding='utf-8'), ensure_ascii=False, indent=2) + print({'shard': shard, 'wallets': len(selected)}) + PY + - name: Collect canonical evidence + shell: bash + env: + SHARD: ${{ matrix.shard }} + run: python runtime/collector.py --wallets runtime/wallets.json --out "out-wallet-verification-p2-${SHARD}" + - name: Enforce non-empty evidence and preserve inputs + if: always() + shell: bash + env: + SHARD: ${{ matrix.shard }} + run: | + set -euo pipefail + OUT="out-wallet-verification-p2-${SHARD}" + mkdir -p "$OUT/runtime" + cp runtime/collector.py runtime/wallets.json "$OUT/runtime/" + python - <<'PY' + import csv, json, os, pathlib, sys + shard = os.environ['SHARD'] + out = pathlib.Path(f'out-wallet-verification-p2-{shard}') + validation = out / 'VALIDATION.json' + if not validation.exists(): + validation = out / 'validation.json' + if not validation.exists(): + raise SystemExit('missing validation file') + data = json.loads(validation.read_text(encoding='utf-8')) + if data.get('status') not in {'PASS','PARTIAL'}: + raise SystemExit(f"invalid collection status: {data.get('status')}") + # PARTIAL is retained as evidence but never treated as verified. + summary_candidates = [out/'wallet_summary.csv', out/'wallets.csv', out/'summary.csv'] + if not any(p.exists() and p.stat().st_size > 10 for p in summary_candidates): + raise SystemExit('missing non-empty wallet summary') + data['production_approved_wallets'] = 0 + data['decision_use'] = 'RESEARCH_EVIDENCE_ONLY' + validation.write_text(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True), encoding='utf-8') + PY + - uses: actions/upload-artifact@v4 + if: always() + with: + name: rhc-wallet-verification-p2-${{ matrix.shard }} + path: out-wallet-verification-p2-${{ matrix.shard }}/ + retention-days: 3 + if-no-files-found: error + compression-level: 9 diff --git a/canonical-chain-history/MANIFEST.json b/canonical-chain-history/MANIFEST.json new file mode 100644 index 0000000..bf2f72c --- /dev/null +++ b/canonical-chain-history/MANIFEST.json @@ -0,0 +1,27 @@ +[ + { + "bytes": 4943, + "path": "VALIDATION.json", + "sha256": "5ff86277019101f8a9a3bd168363001beac1d05bdfa608e474d2c0806727a7b9" + }, + { + "bytes": 1071, + "path": "seadrop/VALIDATION.json", + "sha256": "d2a16b5975ca1942621e3fe3db5782c39dfddccc547b96f6424bd792f474e330" + }, + { + "bytes": 426515, + "path": "seadrop/seadrop_logs.jsonl.gz", + "sha256": "f731c0a899bebd87a690bde475262658d3b6310b8b9cfca19aac2a54cd145bde" + }, + { + "bytes": 1118, + "path": "seaport/VALIDATION.json", + "sha256": "f3d5599bb311dec2d6050881d1e896e8690dc9e1ec3ce16c41c36a675d36fbc3" + }, + { + "bytes": 44, + "path": "seaport/seaport_logs.jsonl.gz", + "sha256": "c5d2db26aa5c1f7fc1e79c0e9cdd2349f22a00b26244e723fb72a54abcb3e366" + } +] \ No newline at end of file diff --git a/canonical-chain-history/VALIDATION.json b/canonical-chain-history/VALIDATION.json new file mode 100644 index 0000000..b1dbe17 --- /dev/null +++ b/canonical-chain-history/VALIDATION.json @@ -0,0 +1,322 @@ +{ + "events": { + "seadrop": { + "event": "seadrop", + "failures": [ + { + "code": "MISSING_SHARDS", + "shards": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63 + ] + }, + { + "actual_end": 3841488, + "code": "HEAD_NOT_COVERED", + "expected_end": 49171065 + } + ], + "fixed_head": 49171065, + "matrix_result": "cancelled", + "shards_expected": 64, + "shards_found": 5, + "status": "FAIL", + "unique_logs": 3988 + }, + "seaport": { + "event": "seaport", + "failures": [ + { + "code": "MISSING_SHARDS", + "shards": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63 + ] + }, + { + "actual_end": -1, + "code": "HEAD_NOT_COVERED", + "expected_end": 49171065 + } + ], + "fixed_head": 49171065, + "matrix_result": "cancelled", + "shards_expected": 64, + "shards_found": 0, + "status": "FAIL", + "unique_logs": 0 + } + }, + "failures": [ + { + "code": "MISSING_SHARDS", + "event": "seadrop", + "shards": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63 + ] + }, + { + "actual_end": 3841488, + "code": "HEAD_NOT_COVERED", + "event": "seadrop", + "expected_end": 49171065 + }, + { + "code": "MISSING_SHARDS", + "event": "seaport", + "shards": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63 + ] + }, + { + "actual_end": -1, + "code": "HEAD_NOT_COVERED", + "event": "seaport", + "expected_end": 49171065 + } + ], + "fixed_head": 49171065, + "matrix_result": "cancelled", + "production_approved_wallets": 0, + "status": "FAIL" +} \ No newline at end of file diff --git a/canonical-chain-history/seadrop/VALIDATION.json b/canonical-chain-history/seadrop/VALIDATION.json new file mode 100644 index 0000000..2505acb --- /dev/null +++ b/canonical-chain-history/seadrop/VALIDATION.json @@ -0,0 +1,80 @@ +{ + "event": "seadrop", + "failures": [ + { + "code": "MISSING_SHARDS", + "shards": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63 + ] + }, + { + "actual_end": 3841488, + "code": "HEAD_NOT_COVERED", + "expected_end": 49171065 + } + ], + "fixed_head": 49171065, + "matrix_result": "cancelled", + "shards_expected": 64, + "shards_found": 5, + "status": "FAIL", + "unique_logs": 3988 +} \ No newline at end of file diff --git a/canonical-chain-history/seadrop/seadrop_logs.jsonl.gz b/canonical-chain-history/seadrop/seadrop_logs.jsonl.gz new file mode 100644 index 0000000..d9a84b8 Binary files /dev/null and b/canonical-chain-history/seadrop/seadrop_logs.jsonl.gz differ diff --git a/canonical-chain-history/seaport/VALIDATION.json b/canonical-chain-history/seaport/VALIDATION.json new file mode 100644 index 0000000..b327dfc --- /dev/null +++ b/canonical-chain-history/seaport/VALIDATION.json @@ -0,0 +1,85 @@ +{ + "event": "seaport", + "failures": [ + { + "code": "MISSING_SHARDS", + "shards": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63 + ] + }, + { + "actual_end": -1, + "code": "HEAD_NOT_COVERED", + "expected_end": 49171065 + } + ], + "fixed_head": 49171065, + "matrix_result": "cancelled", + "shards_expected": 64, + "shards_found": 0, + "status": "FAIL", + "unique_logs": 0 +} \ No newline at end of file diff --git a/canonical-chain-history/seaport/seaport_logs.jsonl.gz b/canonical-chain-history/seaport/seaport_logs.jsonl.gz new file mode 100644 index 0000000..8b51dc2 Binary files /dev/null and b/canonical-chain-history/seaport/seaport_logs.jsonl.gz differ diff --git a/chain-history-trigger.txt b/chain-history-trigger.txt new file mode 100644 index 0000000..d135673 --- /dev/null +++ b/chain-history-trigger.txt @@ -0,0 +1 @@ +Collect SeaDropMint and OrderFulfilled through a fixed finalized head. diff --git a/chain-history/collect_logs.py b/chain-history/collect_logs.py new file mode 100644 index 0000000..0c055c9 --- /dev/null +++ b/chain-history/collect_logs.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import gzip +import hashlib +import json +import os +import random +import time +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path +from typing import Any + +RPC_URL = "https://rpc.mainnet.chain.robinhood.com" +EXPLORERS = ( + "https://robinhoodchain.blockscout.com/api", + "https://explorer.hoodmarketcap.com/api", +) +EVENTS = { + "seadrop": { + "topic0": "0xe90cf9cc0a552cf52ea6ff74ece0f1c8ae8cc9ad630d3181f55ac43ca076b7d6", + "name": "SeaDropMint", + }, + "seaport": { + "topic0": "0x9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31", + "name": "OrderFulfilled", + }, +} +UA = "RHC-Canonical-History/1.0" + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def normalized_log(row: dict[str, Any], source: str) -> dict[str, Any]: + return { + "address": str(row.get("address") or "").lower(), + "topics": [str(value).lower() for value in (row.get("topics") or [])], + "data": str(row.get("data") or "0x").lower(), + "blockNumber": str(row.get("blockNumber") or row.get("block_number") or "").lower(), + "transactionHash": str(row.get("transactionHash") or row.get("transaction_hash") or "").lower(), + "transactionIndex": str(row.get("transactionIndex") or row.get("transaction_index") or "").lower(), + "blockHash": str(row.get("blockHash") or row.get("block_hash") or "").lower(), + "logIndex": str(row.get("logIndex") or row.get("log_index") or "").lower(), + "removed": bool(row.get("removed", False)), + "source": source, + } + + +def post_json(url: str, payload: dict[str, Any], attempts: int = 7) -> dict[str, Any]: + body = json.dumps(payload, separators=(",", ":")).encode("utf-8") + last: Exception | None = None + for attempt in range(attempts): + time.sleep(0.12 + random.random() * 0.22) + request = urllib.request.Request( + url, + data=body, + method="POST", + headers={"Content-Type": "application/json", "Accept": "application/json", "User-Agent": UA}, + ) + try: + with urllib.request.urlopen(request, timeout=75) as response: + data = json.loads(response.read().decode("utf-8")) + if isinstance(data, dict) and data.get("error"): + raise RuntimeError(json.dumps(data["error"], sort_keys=True)) + return data + except urllib.error.HTTPError as exc: + last = exc + if exc.code not in (408, 425, 429, 500, 502, 503, 504): + raise + except Exception as exc: # noqa: BLE001 + last = exc + time.sleep(min(60.0, (2 ** min(attempt, 5)) + random.random() * 4)) + raise RuntimeError(f"RPC failed after {attempts} attempts: {last}") + + +def get_json(url: str, attempts: int = 6) -> dict[str, Any]: + last: Exception | None = None + for attempt in range(attempts): + time.sleep(0.18 + random.random() * 0.35) + request = urllib.request.Request(url, headers={"Accept": "application/json", "User-Agent": UA}) + try: + with urllib.request.urlopen(request, timeout=75) as response: + return json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as exc: + last = exc + if exc.code not in (408, 425, 429, 500, 502, 503, 504): + raise + except Exception as exc: # noqa: BLE001 + last = exc + time.sleep(min(60.0, (2 ** min(attempt, 5)) + random.random() * 4)) + raise RuntimeError(f"GET failed after {attempts} attempts: {url}: {last}") + + +def rpc_logs(topic0: str, start: int, end: int) -> list[dict[str, Any]]: + payload = { + "jsonrpc": "2.0", + "id": 1, + "method": "eth_getLogs", + "params": [{"fromBlock": hex(start), "toBlock": hex(end), "topics": [topic0]}], + } + data = post_json(RPC_URL, payload) + result = data.get("result") + if not isinstance(result, list): + raise RuntimeError(f"invalid RPC result type: {type(result)}") + return [normalized_log(row, "ROBINHOOD_OFFICIAL_RPC") for row in result if isinstance(row, dict)] + + +def explorer_logs(base: str, topic0: str, start: int, end: int) -> tuple[list[dict[str, Any]], bool]: + query = urllib.parse.urlencode( + { + "module": "logs", + "action": "getLogs", + "fromBlock": start, + "toBlock": end, + "topic0": topic0, + } + ) + data = get_json(f"{base}?{query}") + result = data.get("result") + if not isinstance(result, list): + message = data.get("message") or data.get("result") + if str(message).lower() in {"no records found", "no logs found"}: + return [], False + raise RuntimeError(f"invalid explorer result: {data}") + rows = [normalized_log(row, base) for row in result if isinstance(row, dict)] + return rows, len(rows) >= 1000 + + +def collect_range( + topic0: str, + start: int, + end: int, + rows: dict[tuple[str, str, str], dict[str, Any]], + coverage: list[dict[str, Any]], + unresolved: list[dict[str, Any]], + depth: int = 0, +) -> None: + if start > end: + return + errors: list[str] = [] + try: + found = rpc_logs(topic0, start, end) + for row in found: + key = (row["blockHash"], row["transactionHash"], row["logIndex"]) + rows[key] = row + coverage.append({"start": start, "end": end, "source": "ROBINHOOD_OFFICIAL_RPC", "rows": len(found), "depth": depth}) + return + except Exception as exc: # noqa: BLE001 + errors.append(f"rpc:{exc!r}") + + for explorer in EXPLORERS: + try: + found, capped = explorer_logs(explorer, topic0, start, end) + if capped and start < end: + errors.append(f"{explorer}:CAP_1000") + break + for row in found: + key = (row["blockHash"], row["transactionHash"], row["logIndex"]) + rows[key] = row + coverage.append({"start": start, "end": end, "source": explorer, "rows": len(found), "depth": depth}) + return + except Exception as exc: # noqa: BLE001 + errors.append(f"{explorer}:{exc!r}") + + if start == end: + unresolved.append({"start": start, "end": end, "errors": errors, "depth": depth}) + return + middle = (start + end) // 2 + collect_range(topic0, start, middle, rows, coverage, unresolved, depth + 1) + collect_range(topic0, middle + 1, end, rows, coverage, unresolved, depth + 1) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--event", choices=sorted(EVENTS), required=True) + parser.add_argument("--shard", type=int, required=True) + parser.add_argument("--shards", type=int, default=64) + parser.add_argument("--head", type=int, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + + if not (0 <= args.shard < args.shards): + raise SystemExit("invalid shard") + args.out.mkdir(parents=True, exist_ok=True) + start = ((args.head + 1) * args.shard) // args.shards + end = (((args.head + 1) * (args.shard + 1)) // args.shards) - 1 + event = EVENTS[args.event] + + rows: dict[tuple[str, str, str], dict[str, Any]] = {} + coverage: list[dict[str, Any]] = [] + unresolved: list[dict[str, Any]] = [] + collect_range(event["topic0"], start, end, rows, coverage, unresolved) + + ordered = sorted( + rows.values(), + key=lambda row: ( + int(row["blockNumber"], 16) if row["blockNumber"].startswith("0x") else 0, + int(row["transactionIndex"], 16) if row["transactionIndex"].startswith("0x") else 0, + int(row["logIndex"], 16) if row["logIndex"].startswith("0x") else 0, + ), + ) + log_path = args.out / "logs.jsonl.gz" + with gzip.open(log_path, "wt", encoding="utf-8", newline="\n") as handle: + for row in ordered: + handle.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n") + + coverage_record = { + "event": args.event, + "event_name": event["name"], + "topic0": event["topic0"], + "shard": args.shard, + "shards": args.shards, + "head": args.head, + "start": start, + "end": end, + "resolved_ranges": sorted(coverage, key=lambda row: (row["start"], row["end"])), + "unresolved_ranges": unresolved, + "unique_logs": len(ordered), + } + (args.out / "coverage.json").write_text(json.dumps(coverage_record, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + validation = { + "status": "PASS" if not unresolved else "FAIL", + "event": args.event, + "shard": args.shard, + "range_start": start, + "range_end": end, + "unique_logs": len(ordered), + "unresolved_range_count": len(unresolved), + "removed_log_count": sum(bool(row.get("removed")) for row in ordered), + "production_approved_wallets": 0, + "decision_use": "CANONICAL_HISTORY_ONLY", + } + (args.out / "validation.json").write_text(json.dumps(validation, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + manifest = [] + for path in sorted(args.out.iterdir()): + if path.is_file(): + manifest.append({"path": path.name, "bytes": path.stat().st_size, "sha256": sha256(path)}) + (args.out / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + print(json.dumps(validation, sort_keys=True), flush=True) + if unresolved: + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/nft-population-trigger.txt b/nft-population-trigger.txt new file mode 100644 index 0000000..1402b44 --- /dev/null +++ b/nft-population-trigger.txt @@ -0,0 +1 @@ +Collect the complete ERC-721/ERC-1155 population and token lifecycle through a fixed finalized head. diff --git a/nft-population/collect_transfers.py b/nft-population/collect_transfers.py new file mode 100644 index 0000000..abfae65 --- /dev/null +++ b/nft-population/collect_transfers.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import gzip +import hashlib +import json +import random +import time +import urllib.error +import urllib.parse +import urllib.request +from collections import defaultdict +from pathlib import Path +from typing import Any + +from Crypto.Hash import keccak + +RPC_URL = "https://rpc.mainnet.chain.robinhood.com" +EXPLORERS = ( + "https://robinhoodchain.blockscout.com/api", + "https://explorer.hoodmarketcap.com/api", +) +ZERO = "0x0000000000000000000000000000000000000000" +ZERO_TOPIC = "0x" + "0" * 64 +UA = "RHC-NFT-Transfer-History/1.0" + + +def topic(signature: str) -> str: + digest = keccak.new(digest_bits=256) + digest.update(signature.encode("utf-8")) + return "0x" + digest.hexdigest() + + +TRANSFER = topic("Transfer(address,address,uint256)") +TRANSFER_SINGLE = topic("TransferSingle(address,address,address,uint256,uint256)") +TRANSFER_BATCH = topic("TransferBatch(address,address,address,uint256[],uint256[])") + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def intish(value: Any, default: int = 0) -> int: + try: + if isinstance(value, str) and value.startswith("0x"): + return int(value, 16) + return int(value) + except Exception: + return default + + +def addr_topic(value: str) -> str: + return "0x" + value[-40:].lower() + + +def request_json(url: str, *, payload: dict[str, Any] | list[dict[str, Any]] | None = None, attempts: int = 7) -> Any: + body = json.dumps(payload, separators=(",", ":")).encode("utf-8") if payload is not None else None + last: Exception | None = None + for attempt in range(attempts): + time.sleep(0.10 + random.random() * 0.22) + request = urllib.request.Request( + url, + data=body, + method="POST" if body is not None else "GET", + headers={"Content-Type": "application/json", "Accept": "application/json", "User-Agent": UA}, + ) + try: + with urllib.request.urlopen(request, timeout=80) as response: + return json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as exc: + last = exc + if exc.code not in (408, 425, 429, 500, 502, 503, 504): + raise + except Exception as exc: # noqa: BLE001 + last = exc + time.sleep(min(60.0, 2 ** min(attempt, 5) + random.random() * 4)) + raise RuntimeError(f"request failed: {url}: {last}") + + +def normalize_log(row: dict[str, Any], source: str, standard: str, event_name: str) -> dict[str, Any]: + return { + "contract_address": str(row.get("address") or "").lower(), + "standard": standard, + "event_name": event_name, + "topics": [str(value).lower() for value in (row.get("topics") or [])], + "data": str(row.get("data") or "0x").lower(), + "block_number": intish(row.get("blockNumber") or row.get("block_number")), + "block_hash": str(row.get("blockHash") or row.get("block_hash") or "").lower(), + "transaction_hash": str(row.get("transactionHash") or row.get("transaction_hash") or "").lower(), + "transaction_index": intish(row.get("transactionIndex") or row.get("transaction_index")), + "log_index": intish(row.get("logIndex") or row.get("log_index")), + "removed": bool(row.get("removed", False)), + "source": source, + } + + +def rpc_logs(address: str, topic0: str, start: int, end: int, standard: str, event_name: str) -> list[dict[str, Any]]: + payload = { + "jsonrpc": "2.0", + "id": 1, + "method": "eth_getLogs", + "params": [{"fromBlock": hex(start), "toBlock": hex(end), "address": address, "topics": [topic0]}], + } + data = request_json(RPC_URL, payload=payload) + if isinstance(data, dict) and data.get("error"): + raise RuntimeError(json.dumps(data["error"], sort_keys=True)) + result = data.get("result") if isinstance(data, dict) else None + if not isinstance(result, list): + raise RuntimeError(f"invalid RPC log result: {data}") + return [normalize_log(row, "ROBINHOOD_OFFICIAL_RPC", standard, event_name) for row in result if isinstance(row, dict)] + + +def explorer_logs(base: str, address: str, topic0: str, start: int, end: int, standard: str, event_name: str) -> tuple[list[dict[str, Any]], bool]: + query = urllib.parse.urlencode( + { + "module": "logs", + "action": "getLogs", + "fromBlock": start, + "toBlock": end, + "address": address, + "topic0": topic0, + } + ) + data = request_json(f"{base}?{query}") + result = data.get("result") if isinstance(data, dict) else None + if not isinstance(result, list): + message = str(data.get("message") or data.get("result") or "") if isinstance(data, dict) else "" + if "no record" in message.lower() or "no log" in message.lower(): + return [], False + raise RuntimeError(f"invalid explorer logs: {data}") + rows = [normalize_log(row, base, standard, event_name) for row in result if isinstance(row, dict)] + return rows, len(rows) >= 1000 + + +def collect_range( + address: str, + standard: str, + event_name: str, + topic0: str, + start: int, + end: int, + rows: dict[tuple[str, str, int], dict[str, Any]], + unresolved: list[dict[str, Any]], + depth: int = 0, +) -> None: + errors: list[str] = [] + try: + found = rpc_logs(address, topic0, start, end, standard, event_name) + for row in found: + rows[(row["block_hash"], row["transaction_hash"], row["log_index"])] = row + return + except Exception as exc: # noqa: BLE001 + errors.append(f"rpc:{exc!r}") + for base in EXPLORERS: + try: + found, capped = explorer_logs(base, address, topic0, start, end, standard, event_name) + if capped and start < end: + errors.append(f"{base}:CAP_1000") + break + for row in found: + rows[(row["block_hash"], row["transaction_hash"], row["log_index"])] = row + return + except Exception as exc: # noqa: BLE001 + errors.append(f"{base}:{exc!r}") + if start == end: + unresolved.append({"address": address, "standard": standard, "event_name": event_name, "start": start, "end": end, "errors": errors}) + return + middle = (start + end) // 2 + collect_range(address, standard, event_name, topic0, start, middle, rows, unresolved, depth + 1) + collect_range(address, standard, event_name, topic0, middle + 1, end, rows, unresolved, depth + 1) + + +def decode_uint_array(data: bytes, offset: int) -> list[int]: + if offset < 0 or offset + 32 > len(data): + raise ValueError("array offset out of range") + length = int.from_bytes(data[offset : offset + 32], "big") + end = offset + 32 + 32 * length + if end > len(data): + raise ValueError("array payload truncated") + return [int.from_bytes(data[offset + 32 + i * 32 : offset + 64 + i * 32], "big") for i in range(length)] + + +def decode_transfers(row: dict[str, Any]) -> list[dict[str, Any]]: + topics = row["topics"] + data_hex = row["data"][2:] if row["data"].startswith("0x") else row["data"] + data = bytes.fromhex(data_hex) if data_hex else b"" + base = { + "contract_address": row["contract_address"], + "standard": row["standard"], + "event_name": row["event_name"], + "block_number": row["block_number"], + "block_hash": row["block_hash"], + "transaction_hash": row["transaction_hash"], + "transaction_index": row["transaction_index"], + "log_index": row["log_index"], + "source": row["source"], + } + if row["event_name"] == "Transfer" and len(topics) >= 4: + return [{**base, "operator": None, "from_address": addr_topic(topics[1]), "to_address": addr_topic(topics[2]), "token_id": str(int(topics[3], 16)), "amount": "1", "batch_item_index": 0}] + if row["event_name"] == "TransferSingle" and len(topics) >= 4 and len(data) >= 64: + return [{**base, "operator": addr_topic(topics[1]), "from_address": addr_topic(topics[2]), "to_address": addr_topic(topics[3]), "token_id": str(int.from_bytes(data[0:32], "big")), "amount": str(int.from_bytes(data[32:64], "big")), "batch_item_index": 0}] + if row["event_name"] == "TransferBatch" and len(topics) >= 4 and len(data) >= 64: + ids = decode_uint_array(data, int.from_bytes(data[0:32], "big")) + values = decode_uint_array(data, int.from_bytes(data[32:64], "big")) + if len(ids) != len(values): + raise ValueError("TransferBatch ids/values length mismatch") + return [{**base, "operator": addr_topic(topics[1]), "from_address": addr_topic(topics[2]), "to_address": addr_topic(topics[3]), "token_id": str(token_id), "amount": str(value), "batch_item_index": index} for index, (token_id, value) in enumerate(zip(ids, values))] + return [] + + +def rpc_batch(calls: list[tuple[str, list[Any]]]) -> dict[int, Any]: + payload = [{"jsonrpc": "2.0", "id": index, "method": method, "params": params} for index, (method, params) in enumerate(calls)] + data = request_json(RPC_URL, payload=payload) + if not isinstance(data, list): + data = [data] + result: dict[int, Any] = {} + for row in data: + if isinstance(row, dict) and "id" in row: + result[int(row["id"])] = row.get("result") if "error" not in row else {"__error__": row["error"]} + return result + + +def enrich_mint_transactions(transfer_rows: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + mint_items = [row for row in transfer_rows if row["from_address"] == ZERO] + grouped: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list) + for row in mint_items: + grouped[(row["transaction_hash"], row["contract_address"])].append(row) + tx_hashes = sorted({key[0] for key in grouped}) + txs: dict[str, Any] = {} + receipts: dict[str, Any] = {} + blocks: dict[int, Any] = {} + errors: list[dict[str, Any]] = [] + batch_size = 35 + for start in range(0, len(tx_hashes), batch_size): + chunk = tx_hashes[start : start + batch_size] + calls: list[tuple[str, list[Any]]] = [] + keys: list[tuple[str, str]] = [] + for tx_hash in chunk: + calls.append(("eth_getTransactionByHash", [tx_hash])); keys.append((tx_hash, "tx")) + calls.append(("eth_getTransactionReceipt", [tx_hash])); keys.append((tx_hash, "receipt")) + try: + response = rpc_batch(calls) + except Exception as exc: # noqa: BLE001 + errors.append({"stage": "tx_receipt_batch", "start": start, "error": repr(exc)}) + continue + for index, (tx_hash, kind) in enumerate(keys): + value = response.get(index) + if isinstance(value, dict) and value.get("__error__"): + errors.append({"stage": kind, "transaction_hash": tx_hash, "error": value["__error__"]}) + elif kind == "tx": + txs[tx_hash] = value + else: + receipts[tx_hash] = value + block_numbers = sorted({intish((receipt or {}).get("blockNumber")) for receipt in receipts.values() if receipt}) + for start in range(0, len(block_numbers), 70): + chunk = block_numbers[start : start + 70] + try: + response = rpc_batch([("eth_getBlockByNumber", [hex(number), False]) for number in chunk]) + for index, number in enumerate(chunk): + blocks[number] = response.get(index) + except Exception as exc: # noqa: BLE001 + errors.append({"stage": "block_batch", "start": start, "error": repr(exc)}) + + evidence: list[dict[str, Any]] = [] + for (tx_hash, contract), items in grouped.items(): + tx = txs.get(tx_hash) or {} + receipt = receipts.get(tx_hash) or {} + sender = str(tx.get("from") or "").lower() + native_value = intish(tx.get("value")) + gas_used = intish(receipt.get("gasUsed")) + gas_price = intish(receipt.get("effectiveGasPrice") or tx.get("gasPrice")) + recipients = sorted({row["to_address"] for row in items}) + amount = sum(int(row["amount"]) for row in items) + erc20_outflows: dict[str, int] = defaultdict(int) + for log in receipt.get("logs") or []: + topics = [str(value).lower() for value in (log.get("topics") or [])] + if len(topics) == 3 and topics[0] == TRANSFER and addr_topic(topics[1]) == sender: + erc20_outflows[str(log.get("address") or "").lower()] += intish(log.get("data")) + if native_value > 0 and recipients == [sender]: + payment_class = "NATIVE_SELF_FUNDED" + elif native_value > 0: + payment_class = "NATIVE_THIRD_PARTY_OR_ROUTED" + elif erc20_outflows and recipients == [sender]: + payment_class = "ERC20_SELF_FUNDED" + elif erc20_outflows: + payment_class = "ERC20_THIRD_PARTY_OR_ROUTED" + elif recipients == [sender]: + payment_class = "ZERO_VALUE_SELF_MINT" + else: + payment_class = "ZERO_VALUE_THIRD_PARTY_OR_AIRDROP" + block_number = intish(receipt.get("blockNumber") or items[0]["block_number"]) + block = blocks.get(block_number) or {} + evidence.append( + { + "transaction_hash": tx_hash, + "contract_address": contract, + "block_number": block_number, + "block_hash": str(receipt.get("blockHash") or items[0]["block_hash"] or "").lower(), + "timestamp_unix": intish(block.get("timestamp")), + "tx_from": sender, + "tx_to": str(tx.get("to") or "").lower(), + "method_selector": str(tx.get("input") or "0x")[:10].lower(), + "native_value_wei": str(native_value), + "gas_used": str(gas_used), + "effective_gas_price_wei": str(gas_price), + "entry_gas_cost_wei": str(gas_used * gas_price), + "receipt_status": str(receipt.get("status") or ""), + "mint_recipients": recipients, + "mint_item_rows": len(items), + "mint_quantity": str(amount), + "token_ids": sorted({row["token_id"] for row in items}, key=lambda value: int(value)), + "erc20_outflows": dict(sorted(erc20_outflows.items())), + "payment_class": payment_class, + } + ) + return sorted(evidence, key=lambda row: (row["block_number"], row["transaction_hash"])), errors + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--contracts", type=Path, required=True) + parser.add_argument("--shard", type=int, required=True) + parser.add_argument("--shards", type=int, default=32) + parser.add_argument("--head", type=int, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + args.out.mkdir(parents=True, exist_ok=True) + + contracts = [json.loads(line) for line in args.contracts.read_text(encoding="utf-8").splitlines() if line.strip()] + selected = [row for index, row in enumerate(contracts) if index % args.shards == args.shard] + if not selected: + raise SystemExit(f"empty contract shard {args.shard}") + + raw_logs: dict[tuple[str, str, int], dict[str, Any]] = {} + unresolved: list[dict[str, Any]] = [] + processed: list[str] = [] + for index, contract in enumerate(selected, 1): + address = contract["contract_address"].lower() + token_types = set(contract.get("token_types") or []) + event_specs = [] + if "ERC-721" in token_types: + event_specs.append(("ERC-721", "Transfer", TRANSFER)) + if "ERC-1155" in token_types: + event_specs.extend((("ERC-1155", "TransferSingle", TRANSFER_SINGLE), ("ERC-1155", "TransferBatch", TRANSFER_BATCH))) + for standard, event_name, topic0 in event_specs: + before = len(unresolved) + collect_range(address, standard, event_name, topic0, 0, args.head, raw_logs, unresolved) + if len(unresolved) > before: + continue + processed.append(address) + print(json.dumps({"shard": args.shard, "contract": address, "index": index, "total": len(selected), "logs": len(raw_logs), "unresolved": len(unresolved)}), flush=True) + + decoded: list[dict[str, Any]] = [] + decode_errors: list[dict[str, Any]] = [] + for row in sorted(raw_logs.values(), key=lambda value: (value["block_number"], value["transaction_index"], value["log_index"])): + try: + decoded.extend(decode_transfers(row)) + except Exception as exc: # noqa: BLE001 + decode_errors.append({"transaction_hash": row["transaction_hash"], "log_index": row["log_index"], "error": repr(exc)}) + + transfer_path = args.out / "transfers.jsonl.gz" + with gzip.open(transfer_path, "wt", encoding="utf-8", newline="\n") as handle: + for row in decoded: + handle.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n") + mint_evidence, enrich_errors = enrich_mint_transactions(decoded) + with (args.out / "mint_transactions.jsonl").open("w", encoding="utf-8", newline="\n") as handle: + for row in mint_evidence: + handle.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n") + (args.out / "selected_contracts.json").write_text(json.dumps(selected, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + (args.out / "unresolved.json").write_text(json.dumps(unresolved, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + (args.out / "decode_errors.json").write_text(json.dumps(decode_errors, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + (args.out / "enrichment_errors.json").write_text(json.dumps(enrich_errors, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + + validation = { + "status": "PASS" if not unresolved and not decode_errors and not enrich_errors and len(processed) == len(selected) else "FAIL", + "shard": args.shard, + "shards": args.shards, + "head": args.head, + "contracts_selected": len(selected), + "contracts_processed": len(processed), + "raw_event_logs": len(raw_logs), + "decoded_transfer_rows": len(decoded), + "mint_transaction_rows": len(mint_evidence), + "unresolved_ranges": len(unresolved), + "decode_errors": len(decode_errors), + "enrichment_errors": len(enrich_errors), + "production_approved_wallets": 0, + } + (args.out / "VALIDATION.json").write_text(json.dumps(validation, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + manifest = [] + for path in sorted(args.out.iterdir()): + if path.is_file(): + manifest.append({"path": path.name, "bytes": path.stat().st_size, "sha256": sha256(path)}) + (args.out / "MANIFEST.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + print(json.dumps(validation, sort_keys=True), flush=True) + if validation["status"] != "PASS": + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/nft-population/fetch_population.py b/nft-population/fetch_population.py new file mode 100644 index 0000000..e77b40c --- /dev/null +++ b/nft-population/fetch_population.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import csv +import hashlib +import json +import random +import time +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path +from typing import Any + +BASES = ( + "https://robinhoodchain.blockscout.com/api/v2", + "https://explorer.hoodmarketcap.com/api/v2", +) +TOKEN_TYPES = ("ERC-721", "ERC-1155") +UA = "RHC-NFT-Population/1.0" + + +def request_json(url: str, attempts: int = 7) -> dict[str, Any]: + last: Exception | None = None + for attempt in range(attempts): + time.sleep(0.15 + random.random() * 0.25) + req = urllib.request.Request(url, headers={"Accept": "application/json", "User-Agent": UA}) + try: + with urllib.request.urlopen(req, timeout=75) as response: + return json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as exc: + last = exc + if exc.code not in (408, 425, 429, 500, 502, 503, 504): + raise + except Exception as exc: # noqa: BLE001 + last = exc + time.sleep(min(60.0, 2 ** min(attempt, 5) + random.random() * 3)) + raise RuntimeError(f"request failed: {url}: {last}") + + +def address_of(row: dict[str, Any]) -> str | None: + for value in ( + row.get("address_hash"), + row.get("address"), + (row.get("address") or {}).get("hash") if isinstance(row.get("address"), dict) else None, + (row.get("token") or {}).get("address_hash") if isinstance(row.get("token"), dict) else None, + ): + if isinstance(value, str) and value.startswith("0x") and len(value) == 42: + return value.lower() + return None + + +def paginate(base: str, token_type: str) -> tuple[list[dict[str, Any]], int]: + rows: list[dict[str, Any]] = [] + params: dict[str, Any] = {"type": token_type} + seen: set[str] = set() + for page in range(1, 10001): + query = urllib.parse.urlencode(params, doseq=True) + data = request_json(f"{base}/tokens?{query}") + items = data.get("items") + if not isinstance(items, list): + raise RuntimeError(f"invalid token page from {base}: {data}") + rows.extend(item for item in items if isinstance(item, dict)) + nxt = data.get("next_page_params") + if not nxt: + return rows, page + if not isinstance(nxt, dict): + raise RuntimeError(f"invalid next_page_params from {base}: {nxt!r}") + params = {"type": token_type, **nxt} + key = json.dumps(params, sort_keys=True, separators=(",", ":")) + if key in seen: + raise RuntimeError(f"repeated pagination key: {base}: {key}") + seen.add(key) + raise RuntimeError(f"pagination limit exceeded: {base}: {token_type}") + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + args.out.mkdir(parents=True, exist_ok=True) + + observations: list[dict[str, Any]] = [] + status: list[dict[str, Any]] = [] + successful_types: set[str] = set() + for token_type in TOKEN_TYPES: + type_success = False + for base in BASES: + try: + rows, pages = paginate(base, token_type) + type_success = True + successful_types.add(token_type) + status.append({"base": base, "token_type": token_type, "status": "PASS", "pages": pages, "rows": len(rows)}) + for row in rows: + address = address_of(row) + if not address: + continue + observations.append( + { + "contract_address": address, + "token_type": token_type, + "source": base, + "name": row.get("name"), + "symbol": row.get("symbol"), + "total_supply": row.get("total_supply"), + "holders": row.get("holders") or row.get("holders_count"), + "decimals": row.get("decimals"), + "exchange_rate": row.get("exchange_rate"), + "raw": row, + } + ) + except Exception as exc: # noqa: BLE001 + status.append({"base": base, "token_type": token_type, "status": "FAIL", "error": repr(exc)}) + if not type_success: + raise SystemExit(f"no complete source for token type {token_type}") + + contracts: dict[str, dict[str, Any]] = {} + source_sets: dict[str, set[str]] = {} + for row in observations: + address = row["contract_address"] + source_sets.setdefault(address, set()).add(row["source"]) + current = contracts.setdefault( + address, + { + "contract_address": address, + "token_types": set(), + "names": set(), + "symbols": set(), + "total_supply_values": set(), + "holders_values": set(), + }, + ) + current["token_types"].add(row["token_type"]) + if row.get("name"): + current["names"].add(str(row["name"])) + if row.get("symbol"): + current["symbols"].add(str(row["symbol"])) + if row.get("total_supply") is not None: + current["total_supply_values"].add(str(row["total_supply"])) + if row.get("holders") is not None: + current["holders_values"].add(str(row["holders"])) + + normalized = [] + for address, row in sorted(contracts.items()): + normalized.append( + { + "contract_address": address, + "token_types": sorted(row["token_types"]), + "names": sorted(row["names"]), + "symbols": sorted(row["symbols"]), + "total_supply_values": sorted(row["total_supply_values"]), + "holders_values": sorted(row["holders_values"]), + "sources": sorted(source_sets[address]), + } + ) + + with (args.out / "contracts.jsonl").open("w", encoding="utf-8", newline="\n") as handle: + for row in normalized: + handle.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n") + with (args.out / "contracts.csv").open("w", encoding="utf-8-sig", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=["contract_address", "token_types", "names", "symbols", "total_supply_values", "holders_values", "sources"]) + writer.writeheader() + for row in normalized: + writer.writerow({key: json.dumps(value, ensure_ascii=False, sort_keys=True) if isinstance(value, list) else value for key, value in row.items()}) + (args.out / "source_observations.jsonl").write_text( + "".join(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n" for row in observations), + encoding="utf-8", + ) + (args.out / "source_status.json").write_text(json.dumps(status, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + validation = { + "status": "PASS" if normalized and successful_types == set(TOKEN_TYPES) else "FAIL", + "contract_count": len(normalized), + "erc721_observations": sum(row["token_type"] == "ERC-721" for row in observations), + "erc1155_observations": sum(row["token_type"] == "ERC-1155" for row in observations), + "token_types_with_complete_source": sorted(successful_types), + "production_approved_wallets": 0, + } + (args.out / "VALIDATION.json").write_text(json.dumps(validation, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + manifest = [] + for path in sorted(args.out.iterdir()): + if path.is_file(): + manifest.append({"path": path.name, "bytes": path.stat().st_size, "sha256": sha256(path)}) + (args.out / "MANIFEST.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + print(json.dumps(validation, sort_keys=True)) + if validation["status"] != "PASS": + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/recovered-p1-checkpoint/SHA256SUMS.txt b/recovered-p1-checkpoint/SHA256SUMS.txt new file mode 100644 index 0000000..7a38d19 --- /dev/null +++ b/recovered-p1-checkpoint/SHA256SUMS.txt @@ -0,0 +1 @@ +ac25a4b5d1bec590e08388c183389f55b2dad32eb5f4fe20b667014bfee1506c rhc-p1-recovered.tar.gz diff --git a/recovered-p1-checkpoint/VALIDATION.json b/recovered-p1-checkpoint/VALIDATION.json new file mode 100644 index 0000000..bcf02f2 --- /dev/null +++ b/recovered-p1-checkpoint/VALIDATION.json @@ -0,0 +1,301 @@ +{ + "shards": { + "p1a": { + "file_count": 26, + "row_counts": { + "recovered-p1-work/download/rhc-wallet-verification-p1a/MANIFEST.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/REQUEST_STATS.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/VALIDATION.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/address_transactions.csv": 467, + "recovered-p1-work/download/rhc-wallet-verification-p1a/errors.csv": 0, + "recovered-p1-work/download/rhc-wallet-verification-p1a/known_mint_verification.csv": 6, + "recovered-p1-work/download/rhc-wallet-verification-p1a/known_sale_verification.csv": 1, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/address_0x02e9f0322620260aeac9110fe167a4d7e907314f.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/address_0x1c552cf70d8c34156318d6dad4c6c69c3e956392.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/address_0x562f6ac10723ef6af9f077a83cf25135fb369612.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/address_0x66d7472b717277277bf8ea3b1d07922731d8ea35.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/address_0x745b086f34efd3bfde94612c40f3b03118d0d345.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/tx_0x0013db778f8162d0783e4e1fb16830f40cf69343512b8088a477bdb87e6bb897.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/tx_0x3f0f43a55c6b227a6997aec39be0f1ea873fa962365d1053d193218ed480e4f9.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/tx_0x54df9da687a35b1cc26bba6bca2b5a09ad7db6e23b1b9d8d271bffc8ae915623.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/tx_0x66006b675af42144201034b40ac4c79d61cef0580ac8995dd60155665572551d.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/raw/tx_0xe6ad7eaaeb48164249f83db770c73e1a494c1032047c63c507251c5c35399c12.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/runtime/wallets-all.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/runtime/wallets.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1a/wallet_summary.csv": 6 + }, + "status": "PASS", + "substantive_rows": 473 + }, + "p1b": { + "file_count": 26, + "row_counts": { + "recovered-p1-work/download/rhc-wallet-verification-p1b/MANIFEST.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/REQUEST_STATS.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/VALIDATION.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/address_transactions.csv": 911, + "recovered-p1-work/download/rhc-wallet-verification-p1b/errors.csv": 0, + "recovered-p1-work/download/rhc-wallet-verification-p1b/known_mint_verification.csv": 6, + "recovered-p1-work/download/rhc-wallet-verification-p1b/known_sale_verification.csv": 1, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/address_0x764611d78dc1a8039a713d33ff614423963ae486.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/address_0x9dade467874b15382e6b7dff96486d7126f41116.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/address_0xa006dcb0bf9714eb9feb7f88c19ebcd20cc44cfa.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/address_0xb475b45d9d56334a86aa4e907ad8573f93fd6cb3.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/address_0xedb0f2bdf9f5b2d755a1d8a97154b643a15b3c8e.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/tx_0x1c791f750a52b255c0575f9a11693cd885724322e317de961c39199a6a5c6ff7.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/tx_0x96c0586b5c93239539c8b0eb40a837504c7a73067aa1355130f0cd08dbaf783b.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/tx_0xcacc6375301c1714f301c8a5117344097e03abe0275438cf3cb7a8f1c22f7653.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/tx_0xcc5a45b9858804e6c016584f13e17e08ef1a3d4d2c592203a64f067da653ba75.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/raw/tx_0xf26a26418a70756e99ef75bd9aa90122d9eafa7c4126b8c51ee566caa925f6b0.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/runtime/wallets-all.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/runtime/wallets.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1b/wallet_summary.csv": 6 + }, + "status": "PASS", + "substantive_rows": 917 + }, + "p1c": { + "file_count": 30, + "row_counts": { + "recovered-p1-work/download/rhc-wallet-verification-p1c/MANIFEST.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/REQUEST_STATS.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/VALIDATION.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/address_nft_transfers.csv": 0, + "recovered-p1-work/download/rhc-wallet-verification-p1c/address_transactions.csv": 0, + "recovered-p1-work/download/rhc-wallet-verification-p1c/errors.csv": 0, + "recovered-p1-work/download/rhc-wallet-verification-p1c/known_mint_verification.csv": 8, + "recovered-p1-work/download/rhc-wallet-verification-p1c/known_sale_verification.csv": 10, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/address_0x121a5da145cd6dbe322d75c514481d2efa188816.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/address_0x204a9d05e454619bf713b947c3e87302f4509d3c.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/address_0x43948bababa753a587fc475ced55c74760b716fc.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/address_0x51e99a26a3b538a4023e0e1af16e97153056a814.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/address_0x6ee78170825f65a2bd9cf9cda48ce2282496ddba.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0x0098561274910aa58f30cfea9384bb21bf9712c247a06f95cd3e456e2f74dc0f.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0x18fadee84ee44c78ed3cf9d48479a5272960ae1ad8d44ed3a971547635b78df8.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0x3b584db19fcf6c26e2eaf8adf00b989fbfe7e1c6abea66d6438fc73944aed251.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0x53278df4605afbf43b80d2ab88dbb4187e7e496f5759d57754f14f5921271c3c.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0x8e428f651c1bccca578440f3b5afd9ed5ef8160cd050fd9feb5cb023a7783e5b.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0x99a89fbc3df0a538a41ed4c65252992421a9f4469aa4f976fca68a0b130db6a4.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0xa300d7eeb8a24dc20ecaa15631a5c79ea51b1c8319f2054bde92bf6352d21f67.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0xa7e00281106c4dbbd87e8bdf942c97b9c115d678bf641e87d46905aa166e3a06.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/raw/tx_0xe98e336f2ac0b7f290bb1e5600204eaa40b6ec20808703e4d499c532901ae320.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/runtime/wallets-all.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/runtime/wallets.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1c/wallet_summary.csv": 6 + }, + "status": "PASS", + "substantive_rows": 6 + }, + "p1d": { + "file_count": 25, + "row_counts": { + "recovered-p1-work/download/rhc-wallet-verification-p1d/MANIFEST.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/REQUEST_STATS.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/VALIDATION.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/address_transactions.csv": 3910, + "recovered-p1-work/download/rhc-wallet-verification-p1d/errors.csv": 0, + "recovered-p1-work/download/rhc-wallet-verification-p1d/known_mint_verification.csv": 3, + "recovered-p1-work/download/rhc-wallet-verification-p1d/known_sale_verification.csv": 7, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/address_0x91481f82d8625f7c472c8d666aadf4182fa11ec8.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/address_0x961ef2b282bfa761346464f4f3bffa4bf7eea53f.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/address_0xd04b2aafb1eef38d5004c062adfa46de964602a2.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/address_0xdc979c981cecf9b1e617357c0bc494e53e0f019d.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/tx_0x6e9ea02c1873272d7e5965a74165b0dc11d9b7a2f9d8f8eda652630a6abdcffc.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/tx_0xa3da267f93f94bce2de847ba96017849edbfb31556403cfde6200fbcf1675f74.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/tx_0xb07a788c7906252d6ee5e131c039ebf07fd9fc8e42aa9d5491500bdeaf15058c.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/tx_0xc6c6ac61e4fc09eb3712c77d9004d22f68260f4917f68092d80a17d2e7726441.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/raw/tx_0xff1487b7d945726ad0a347e386d4ad8828271fd157533565f3801313bb1f6b76.json": { + "json_type": "dict" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/runtime/wallets-all.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/runtime/wallets.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "recovered-p1-work/download/rhc-wallet-verification-p1d/wallet_summary.csv": 5 + }, + "status": "PASS", + "substantive_rows": 3915 + } + }, + "source_run_id": 33167365485, + "status": "PASS" +} \ No newline at end of file diff --git a/recovered-p1-checkpoint/rhc-p1-recovered.tar.gz.part-000 b/recovered-p1-checkpoint/rhc-p1-recovered.tar.gz.part-000 new file mode 100644 index 0000000..dea9db2 Binary files /dev/null and b/recovered-p1-checkpoint/rhc-p1-recovered.tar.gz.part-000 differ diff --git a/recovered-p1-final-checkpoint/MANIFEST.json b/recovered-p1-final-checkpoint/MANIFEST.json new file mode 100644 index 0000000..dcf91d6 --- /dev/null +++ b/recovered-p1-final-checkpoint/MANIFEST.json @@ -0,0 +1,642 @@ +[ + { + "bytes": 18737, + "path": "P1_FINAL_VALIDATION.json", + "sha256": "b4b459fbcb405c20a91d2bab8d586c5e941de6164a9cffcebae141d86809d658" + }, + { + "bytes": 26861, + "path": "recovered-p1-partial/MANIFEST.json", + "sha256": "7377cadfcb3c3a24b5c7c7c31a0d2ce397c0d83b39da0a7c8bb4117a93fe435f" + }, + { + "bytes": 18622, + "path": "recovered-p1-partial/RECOVERY_REPORT.json", + "sha256": "39cbbfe9bb4ffc6e88e414cd6b51c4645314b1f6ee41c7adea202d347d22849b" + }, + { + "bytes": 1910, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/MANIFEST.json", + "sha256": "f2619d6c3eb8c3acbfab3590d70a0c293bb79fe14fd8359e4552d082f53192e5" + }, + { + "bytes": 20, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/REQUEST_STATS.json", + "sha256": "a4275a51b228122eef8c23acdf96eba4992760fe2056dbde620ad785656dd06b" + }, + { + "bytes": 511, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/VALIDATION.json", + "sha256": "6d95a17d49069acaf6d14226648167b92e1627d5c2853e6148373190075b4fbb" + }, + { + "bytes": 35843, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/address_nft_transfers.csv", + "sha256": "ae53186b9303d82e35438f5f1a5884ddac6dab81e6d31b980369b344a08e7f94" + }, + { + "bytes": 8080, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/address_transactions.csv", + "sha256": "d505edf57beb7db9302662154cfb206b3dfbd7b00cf90b708aa19b82251f58cc" + }, + { + "bytes": 10, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 1189, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/known_mint_verification.csv", + "sha256": "05411fa333005d55f36c3c07ce04ada5724707e7e6ddd8be61be2d776cddcb0b" + }, + { + "bytes": 2970, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/known_sale_verification.csv", + "sha256": "cfb14e728db9088e45e5881992f153d403432af746711d40312d369420e02d76" + }, + { + "bytes": 51425, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "b245d4faeb21d93b2b80f6696a1249ace3ea6f1352e0b316a72c7806f80b3a20" + }, + { + "bytes": 58213, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "f3dde3e00985313b8991fd8579fbb1178a89c2dcb703d3a79ccc5805c0b68a74" + }, + { + "bytes": 109016, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "0d05c47fd0e59c7392a7c6be07ee753ce5a77ecb042a1883c50bc32f318eddbe" + }, + { + "bytes": 29826, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 1408, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/runtime/wallets.json", + "sha256": "9e3234f00ce6ecd4fa290cb1c09954c8861d0fd71fc04196cff9fa4c3b0cbe67" + }, + { + "bytes": 3067, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/token_lifecycle_evidence.json", + "sha256": "104b3578150a5c8f00e43267fdae89cb41d47c9cc28a99ee7a5449c7f04e4d07" + }, + { + "bytes": 1945, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p0/wallet_summary.csv", + "sha256": "9be495b7f09d0212db384cc2ecbc97118f43458b81376fbcf616f6605ca74cde" + }, + { + "bytes": 3869, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/MANIFEST.json", + "sha256": "d1ccb1ed736b96c71811f87aba0bb40e26cb242aeefef5811e0966051b88e2c2" + }, + { + "bytes": 90, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/REQUEST_STATS.json", + "sha256": "e703dd14921452342b46abe7e83567353e781fe81106cae2c1890976a8241c69" + }, + { + "bytes": 514, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/VALIDATION.json", + "sha256": "e2f2fab03b6c9691c9fcc8f1c57f14e33e9689b3878568b53033afb5d44d2639" + }, + { + "bytes": 2417792, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/address_nft_transfers.csv", + "sha256": "65a5f7e0104e09a77e807ca9bf689efa4ea8765aeb47a85429016db81eedb2f4" + }, + { + "bytes": 1234263, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/address_transactions.csv", + "sha256": "fa68e62f4474d4a197144f81ba3b59ad1b7202d9265ca89f636eabaef7692371" + }, + { + "bytes": 10, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 5496, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/known_mint_verification.csv", + "sha256": "7882ec7184318967c6000738ef0fb746e4a4afec445e1263639527518d9afb67" + }, + { + "bytes": 2970, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/known_sale_verification.csv", + "sha256": "cfb14e728db9088e45e5881992f153d403432af746711d40312d369420e02d76" + }, + { + "bytes": 7440, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x02e9f0322620260aeac9110fe167a4d7e907314f.json", + "sha256": "081312ba156d8bc7ff625f7f8230d47311ba8271de4537727881ffd21e0a83c3" + }, + { + "bytes": 1984190, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x1c552cf70d8c34156318d6dad4c6c69c3e956392.json", + "sha256": "053a84fdcb04f058f14522e0972a669f4d3d7c7f7fa101b2c158fdccef3ac771" + }, + { + "bytes": 1871287, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x562f6ac10723ef6af9f077a83cf25135fb369612.json", + "sha256": "a5bb6a0a10b670777eb813240c235930521f50b9fb8a969bbd3bbbeb1c547596" + }, + { + "bytes": 441122, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x66d7472b717277277bf8ea3b1d07922731d8ea35.json", + "sha256": "91ba17b9e12310c77a151c2574e60bd2f1cfc6b059adf7e6ceb014d1dabe3d02" + }, + { + "bytes": 8198, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x745b086f34efd3bfde94612c40f3b03118d0d345.json", + "sha256": "4843ff375fda205e46f0ee39353dbe3772bd4b515f98befea747f9dd4bcd35b5" + }, + { + "bytes": 51425, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "87c1cece544f1dbb85a1658992d18ba41ef8504a116150d94a0c07ec4a3c9146" + }, + { + "bytes": 113127, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x0013db778f8162d0783e4e1fb16830f40cf69343512b8088a477bdb87e6bb897.json", + "sha256": "8f6caed3c822cf590b0d7c39a35d0cd007b5467fbad0fa1d77c07d01c47b07de" + }, + { + "bytes": 247676, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x3f0f43a55c6b227a6997aec39be0f1ea873fa962365d1053d193218ed480e4f9.json", + "sha256": "5830c9b248ffa1d6175874f4a800ecf5238dd1fadbd508ba5a7e3d56d60b0f56" + }, + { + "bytes": 172322, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x54df9da687a35b1cc26bba6bca2b5a09ad7db6e23b1b9d8d271bffc8ae915623.json", + "sha256": "05ad50ab0250e171ff5bc49ecfe4e916e1ba78b0258b02baa18c435d228657e6" + }, + { + "bytes": 60162, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x66006b675af42144201034b40ac4c79d61cef0580ac8995dd60155665572551d.json", + "sha256": "b4e7bdceece8f6be5f60d4b4641a9f108c0c0424d2b730a38a9ca99e08383503" + }, + { + "bytes": 58213, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "940fa751529a5e203dece7a3afe44f82659957ffa97791a0ccd7f68591b43c7f" + }, + { + "bytes": 109016, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "103b611dc341b1103c4e303db44dd66f0fa5eb40d031e6cb0b4e30074f5d5694" + }, + { + "bytes": 122891, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0xe6ad7eaaeb48164249f83db770c73e1a494c1032047c63c507251c5c35399c12.json", + "sha256": "98500dc053c4973d5b5b9b4a2e3c5166ebf99061125c635d633ab84bf2d505ca" + }, + { + "bytes": 29826, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 5571, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/runtime/wallets.json", + "sha256": "17bdacac8b682438481af65a87ec7a011a1750d500309e4c3ece687705976f6f" + }, + { + "bytes": 29683, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/token_lifecycle_evidence.json", + "sha256": "287cbc2e7c1438ca54f1f10978cc6bf2eb23f7343325e417307dd726ee9339d7" + }, + { + "bytes": 12469, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/wallet_summary.csv", + "sha256": "ffb3ce892126856e06dfbed55a6532ceb39b4dfc9ba2033dbe3fa428515619bd" + }, + { + "bytes": 3874, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/MANIFEST.json", + "sha256": "2af1774bd47f66d449696b4c8616d4ee16dedd53e527ef842412c0c19ae8f2a0" + }, + { + "bytes": 57, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/REQUEST_STATS.json", + "sha256": "54a04f90ba3506b3217427a669b0bda2809d4badc240b1cbc5e61f0246a16979" + }, + { + "bytes": 514, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/VALIDATION.json", + "sha256": "b9b9e14b88fc7b41b09c2c9ddbe7e448c07a2246c0f337340f2b3451be78cb70" + }, + { + "bytes": 5735191, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/address_nft_transfers.csv", + "sha256": "921e293d33b3ce150b68102f0b7ae76013e67a95d975e368464ed717ef176130" + }, + { + "bytes": 2219010, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/address_transactions.csv", + "sha256": "7ad8d4b89dd33a326e10451ace8424340882a5eab2fb0a919575a30d6f5390dc" + }, + { + "bytes": 10, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 5559, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/known_mint_verification.csv", + "sha256": "32b03c7e6079563cd35a9949be7af92e4c6483b4d0784fff7a8b40aa53470337" + }, + { + "bytes": 2970, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/known_sale_verification.csv", + "sha256": "cfb14e728db9088e45e5881992f153d403432af746711d40312d369420e02d76" + }, + { + "bytes": 1171553, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0x764611d78dc1a8039a713d33ff614423963ae486.json", + "sha256": "b996df431efb567d7152a4099d8a9f5d5b254caecc292b1cbbec175a5224e2bc" + }, + { + "bytes": 51425, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "0a797ea1cf58aa935b4bab723f823db650dc2225275897bbe4aa0571056df5c9" + }, + { + "bytes": 243777, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0x9dade467874b15382e6b7dff96486d7126f41116.json", + "sha256": "1eaa14fe7be5f1098b8c4a11d93a2ab502ec4f2eabd9e3ffb7198c448551b6a3" + }, + { + "bytes": 1534986, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0xa006dcb0bf9714eb9feb7f88c19ebcd20cc44cfa.json", + "sha256": "a38eb755fd750dcfaf84f1925a5e3232b31c0c868e6187788dcd306281b43b48" + }, + { + "bytes": 512870, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0xb475b45d9d56334a86aa4e907ad8573f93fd6cb3.json", + "sha256": "1af624ac532967d4ccca42a85224efb6aebfbb069cea256a73c4020c4daae81b" + }, + { + "bytes": 5551441, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0xedb0f2bdf9f5b2d755a1d8a97154b643a15b3c8e.json", + "sha256": "7d3ecbcad0a4ed9be02f9194ecbce880523688b61d12bbb6a4b1c21f182fb1f2" + }, + { + "bytes": 183865, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x1c791f750a52b255c0575f9a11693cd885724322e317de961c39199a6a5c6ff7.json", + "sha256": "6660e628b6063b924da2f6f2b739ddd808df98ff24703cc503f4a8715113c87f" + }, + { + "bytes": 58213, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "e7671de8df81f6dc05665ecf19416ddb9ede1edc847a93050c6827d65af0b59c" + }, + { + "bytes": 109016, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "606210d76aef1df7db5cd138747b774fccbebf9cf19d917a9c91d44150829bb5" + }, + { + "bytes": 58465, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x96c0586b5c93239539c8b0eb40a837504c7a73067aa1355130f0cd08dbaf783b.json", + "sha256": "fef9f99f3e9ddd378facda2f56c90d63fe05152f972965eb9d9fd099b937a7f5" + }, + { + "bytes": 110390, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xcacc6375301c1714f301c8a5117344097e03abe0275438cf3cb7a8f1c22f7653.json", + "sha256": "bd4cd3ebd618f77abd2d88b9ac801ba7c3b88252e5bca0dff3bc6a7b7c5effb5" + }, + { + "bytes": 411867, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xcc5a45b9858804e6c016584f13e17e08ef1a3d4d2c592203a64f067da653ba75.json", + "sha256": "febf5638030129da5d50b28530551d0b923b8761b76ec3d98b853f8cc3cc7959" + }, + { + "bytes": 185289, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xf26a26418a70756e99ef75bd9aa90122d9eafa7c4126b8c51ee566caa925f6b0.json", + "sha256": "8d62ab901088bd317f87dd7b3f3c64fa584aea206793b276badd4ae492151a8c" + }, + { + "bytes": 29826, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 5534, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/runtime/wallets.json", + "sha256": "6e7b535be13c23e6b8bf5b945c0839b0d15a7d330a90e040c110e9fc381c3ac6" + }, + { + "bytes": 40604, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/token_lifecycle_evidence.json", + "sha256": "88b2f3202c823ee26258fdc2661ecd2156fb81ace2c9bfdbb7dd15421fde4ba0" + }, + { + "bytes": 12975, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/wallet_summary.csv", + "sha256": "2663a1e6742f8513a7cb1aa1657dab0003b3de54f49936703e6a8b58e33ad743" + }, + { + "bytes": 4672, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/MANIFEST.json", + "sha256": "ef2cf3db264ba5df0e12479179e2ee3dbc72107b853e72f2e2792afda5d83d36" + }, + { + "bytes": 57, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/REQUEST_STATS.json", + "sha256": "a82e9a211badccf2cff9f51a1e4fea5c9723779b23b2794040daaf75a5655ec4" + }, + { + "bytes": 513, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/VALIDATION.json", + "sha256": "f059387ed85e61baffa23fc27644062f7d9d01f03b90970338f01e54c245807c" + }, + { + "bytes": 10, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/address_nft_transfers.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 10, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/address_transactions.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 10, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 7102, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/known_mint_verification.csv", + "sha256": "88ea43dbe88ee79ce62da5fdaf7925ccc7494264a688ef307778c6bdf3bcf564" + }, + { + "bytes": 139484, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/known_sale_verification.csv", + "sha256": "79c798ad775c9677a6c2444bc0af0fa089e7b5f5fa4988d31717458f83a2539e" + }, + { + "bytes": 24118, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x121a5da145cd6dbe322d75c514481d2efa188816.json", + "sha256": "ceebfa88105160cbc2af90e77f28483a8c7dd383efe705697558a3d8e86e8192" + }, + { + "bytes": 24118, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x204a9d05e454619bf713b947c3e87302f4509d3c.json", + "sha256": "6e97bc670b0912c4ba672315f49e31abec34abb993fad7679c2b2b638b13c488" + }, + { + "bytes": 24118, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x43948bababa753a587fc475ced55c74760b716fc.json", + "sha256": "d7a2f80406c42a48a8ce4c0a4ee3f741e5712720967228472930dd6d82f0e7fb" + }, + { + "bytes": 24118, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x51e99a26a3b538a4023e0e1af16e97153056a814.json", + "sha256": "914fe9adc1ef8e394c5ad0b2612678054baf6746170b18bc70b48400b644c16d" + }, + { + "bytes": 24118, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x6ee78170825f65a2bd9cf9cda48ce2282496ddba.json", + "sha256": "e0eb81fe91c4851321219840249f357b23afcd090cc9f1ccdf9437dd1a62bc78" + }, + { + "bytes": 24118, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "c634acf24d90d1d0da39a329469d23ed3adce89d14d4c578bc7e25f2a3278361" + }, + { + "bytes": 133327, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x0098561274910aa58f30cfea9384bb21bf9712c247a06f95cd3e456e2f74dc0f.json", + "sha256": "6491164dbdfc2351c449ffe6e9eadcfd4302b3f274764ef3b0a87749b3f76131" + }, + { + "bytes": 61192, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x18fadee84ee44c78ed3cf9d48479a5272960ae1ad8d44ed3a971547635b78df8.json", + "sha256": "542da71d6d12b9f6bdf91b00fb01384be65df484832cfe8ce0c0f1047696f45a" + }, + { + "bytes": 63431, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x3b584db19fcf6c26e2eaf8adf00b989fbfe7e1c6abea66d6438fc73944aed251.json", + "sha256": "9610c5735d4e56e2e8e5e62edde8fc1b51fe73ae87ea4648ff96cd9655e68092" + }, + { + "bytes": 62186, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x53278df4605afbf43b80d2ab88dbb4187e7e496f5759d57754f14f5921271c3c.json", + "sha256": "3e2bb3f1f04a8434550189c169837cca804ec235c45fd86100c57d82287ed1d7" + }, + { + "bytes": 60273, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "6909fc1f5df15713a08a69415efaa917441c76e52e3f23e73c764d9d3ffca0b1" + }, + { + "bytes": 64688, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "05195edfcd5eb722e422c8195ff0d9ceaebbf7c922fe28af41174c61fa39e5e3" + }, + { + "bytes": 60997, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x8e428f651c1bccca578440f3b5afd9ed5ef8160cd050fd9feb5cb023a7783e5b.json", + "sha256": "ce09454d82f7c980168784e5e8a9c8a6790409dccfc6e8e856af9d012768596b" + }, + { + "bytes": 103080, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x99a89fbc3df0a538a41ed4c65252992421a9f4469aa4f976fca68a0b130db6a4.json", + "sha256": "11d8f7e49fa718680e74c2066cb0605a62839795c4d86b8e54d5720785315c1f" + }, + { + "bytes": 61877, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xa300d7eeb8a24dc20ecaa15631a5c79ea51b1c8319f2054bde92bf6352d21f67.json", + "sha256": "6c7106dc96976a0c166152364f3ef2a83361ae1a37e213b56d8e5bf77b43d43f" + }, + { + "bytes": 64462, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xa7e00281106c4dbbd87e8bdf942c97b9c115d678bf641e87d46905aa166e3a06.json", + "sha256": "68aa6c179ef8921e6092fdce206196e799d93de08bd8c150c819829ea305ff47" + }, + { + "bytes": 63390, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xe98e336f2ac0b7f290bb1e5600204eaa40b6ec20808703e4d499c532901ae320.json", + "sha256": "72970c23188ae1153a37cb785c0b78a323b46107567e6910f7de0fd0b9a6573e" + }, + { + "bytes": 29826, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 11331, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/runtime/wallets.json", + "sha256": "98687d49d0c87232b3ac8179e4984ad0560dbad729751989f0a29e5876fca9c8" + }, + { + "bytes": 38759, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/token_lifecycle_evidence.json", + "sha256": "b5b0c93adfb70b1dd7923ba298ddc787841b6f9268b1601cdfd8b4d162c0d37b" + }, + { + "bytes": 141940, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/wallet_summary.csv", + "sha256": "e5fc306e46b1742cb45a5dc43ea9e726d709c5352935c74fee28ea5ca37d62e0" + }, + { + "bytes": 3695, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/MANIFEST.json", + "sha256": "c4a307e0167515b7ab3204cde28e0a176073db8b607758de0912f117f257a370" + }, + { + "bytes": 90, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/REQUEST_STATS.json", + "sha256": "8c1ac3a912ff425020a3ac6cbac88adf459bb55cabcf847a3434f230a3ee45b9" + }, + { + "bytes": 517, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/VALIDATION.json", + "sha256": "b67144475f2eb36db9577a79691ac4b2f3b7833796f8391956699f51eca3514d" + }, + { + "bytes": 33760362, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/address_nft_transfers.csv", + "sha256": "3a5740871f132a964618c26829d13e3dcbdfd196d587f38529030d2496eeb7b5" + }, + { + "bytes": 5945812, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/address_transactions.csv", + "sha256": "852ef01ab6b3c12d9295b7420fd4fb2270536c347826a3817673605a7c34998c" + }, + { + "bytes": 10, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 2905, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/known_mint_verification.csv", + "sha256": "5c128b98d7273de038c8895a3247226b2203d1c9df9438ce406f0c389822c020" + }, + { + "bytes": 93642, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/known_sale_verification.csv", + "sha256": "3ced4fa2ae06b7aa1b5a4d1ce8d1ada85c4752e56a81776753ec28467fd4f71d" + }, + { + "bytes": 51425, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "a8cecee7d97ceae05c99ba8f5d26f54ce9d134cf1a3861df6944726df5689a38" + }, + { + "bytes": 13131829, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0x91481f82d8625f7c472c8d666aadf4182fa11ec8.json", + "sha256": "a9797cf83cf5650930934c4b8c459a701ead2b2f33c95231e4d7af544648ebc1" + }, + { + "bytes": 15144413, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0x961ef2b282bfa761346464f4f3bffa4bf7eea53f.json", + "sha256": "5c875db42e50521324947bbed22549971c0c65efc254d13c712c780049bd8071" + }, + { + "bytes": 16849774, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0xd04b2aafb1eef38d5004c062adfa46de964602a2.json", + "sha256": "349c84bc5c02315340b6715ebd8eaf49f2054703829a1137680f24337f37fd68" + }, + { + "bytes": 4384832, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0xdc979c981cecf9b1e617357c0bc494e53e0f019d.json", + "sha256": "61f7e1c7b2bf1f09a6972c32bea5017de9b04734ca53706a2542d798c91e1bcb" + }, + { + "bytes": 491962, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x6e9ea02c1873272d7e5965a74165b0dc11d9b7a2f9d8f8eda652630a6abdcffc.json", + "sha256": "bc1d433b35affd3e6a581d7d83319110f69dd47375f975ee0384ca4bbbea57dd" + }, + { + "bytes": 58213, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "a1dcf77bb5757c3f02c2184f9e26f90c71a30e9f83462710dd97b53683bae8b9" + }, + { + "bytes": 109016, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "197a1a67624949972b9c329642766a2885f0fd64c615e6585c8f7ce32d24a958" + }, + { + "bytes": 424244, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xa3da267f93f94bce2de847ba96017849edbfb31556403cfde6200fbcf1675f74.json", + "sha256": "26050d13625340f386d80ebffd0550f260241a2eca3405bf230fd489e9ec3966" + }, + { + "bytes": 94900, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xb07a788c7906252d6ee5e131c039ebf07fd9fc8e42aa9d5491500bdeaf15058c.json", + "sha256": "c295d139dfcc4d13d6f1ef60771cd49809b97a9e954b8888d00492fe856d215e" + }, + { + "bytes": 1804858, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xc6c6ac61e4fc09eb3712c77d9004d22f68260f4917f68092d80a17d2e7726441.json", + "sha256": "60b57b85eca29491f6bf68535d04a75129b44e75665c6dc6942a8325649fbe6e" + }, + { + "bytes": 235322, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xff1487b7d945726ad0a347e386d4ad8828271fd157533565f3801313bb1f6b76.json", + "sha256": "adb4ad6886c4c3379e241ca2fc0caf407e4fb199ac1c173ef6f503f3bf4db320" + }, + { + "bytes": 29826, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 6850, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/runtime/wallets.json", + "sha256": "95ea646f013cb99aca77ae4ec8de8692fa4aea1574ba7f796af742c3702a0372" + }, + { + "bytes": 26251, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/token_lifecycle_evidence.json", + "sha256": "da96a62994da950f9741f9100edcd1952d655ae6515911a721a4ff0ef236bd89" + }, + { + "bytes": 10821, + "path": "recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/wallet_summary.csv", + "sha256": "533a06d08b1063673f3a349871c3b084a8f96885c95ef2d7c4c5f6cb689495f1" + }, + { + "bytes": 5157, + "path": "recovered-p1-partial/source_artifacts.json", + "sha256": "915b6053fd546aa1c71fcec206f292c7190f162b418f2ab87959f20142b69813" + }, + { + "bytes": 13347, + "path": "recovered-p1-partial/source_jobs.json", + "sha256": "1238f8b2c5de46e31351f0c4ba4c524d81eb367a8a4df7ff4a2bf0b6ee1d88b8" + } +] \ No newline at end of file diff --git a/recovered-p1-final-checkpoint/P1_FINAL_VALIDATION.json b/recovered-p1-final-checkpoint/P1_FINAL_VALIDATION.json new file mode 100644 index 0000000..f396922 --- /dev/null +++ b/recovered-p1-final-checkpoint/P1_FINAL_VALIDATION.json @@ -0,0 +1,302 @@ +{ + "decision_use": "RESEARCH_EVIDENCE_ONLY_NOT_STRONG_WALLET_APPROVAL", + "production_approved_wallets": 0, + "shards": { + "p1a": { + "file_count": 26, + "row_counts": { + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/MANIFEST.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/REQUEST_STATS.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/VALIDATION.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/address_transactions.csv": 467, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/errors.csv": 0, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/known_mint_verification.csv": 6, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/known_sale_verification.csv": 1, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x02e9f0322620260aeac9110fe167a4d7e907314f.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x1c552cf70d8c34156318d6dad4c6c69c3e956392.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x562f6ac10723ef6af9f077a83cf25135fb369612.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x66d7472b717277277bf8ea3b1d07922731d8ea35.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x745b086f34efd3bfde94612c40f3b03118d0d345.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x0013db778f8162d0783e4e1fb16830f40cf69343512b8088a477bdb87e6bb897.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x3f0f43a55c6b227a6997aec39be0f1ea873fa962365d1053d193218ed480e4f9.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x54df9da687a35b1cc26bba6bca2b5a09ad7db6e23b1b9d8d271bffc8ae915623.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x66006b675af42144201034b40ac4c79d61cef0580ac8995dd60155665572551d.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/raw/tx_0xe6ad7eaaeb48164249f83db770c73e1a494c1032047c63c507251c5c35399c12.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/runtime/wallets-all.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/runtime/wallets.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1a/wallet_summary.csv": 6 + }, + "status": "PASS_NONEMPTY", + "substantive_rows": 473 + }, + "p1b": { + "file_count": 26, + "row_counts": { + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/MANIFEST.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/REQUEST_STATS.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/VALIDATION.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/address_transactions.csv": 911, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/errors.csv": 0, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/known_mint_verification.csv": 6, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/known_sale_verification.csv": 1, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0x764611d78dc1a8039a713d33ff614423963ae486.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0x9dade467874b15382e6b7dff96486d7126f41116.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0xa006dcb0bf9714eb9feb7f88c19ebcd20cc44cfa.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0xb475b45d9d56334a86aa4e907ad8573f93fd6cb3.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/address_0xedb0f2bdf9f5b2d755a1d8a97154b643a15b3c8e.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x1c791f750a52b255c0575f9a11693cd885724322e317de961c39199a6a5c6ff7.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x96c0586b5c93239539c8b0eb40a837504c7a73067aa1355130f0cd08dbaf783b.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xcacc6375301c1714f301c8a5117344097e03abe0275438cf3cb7a8f1c22f7653.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xcc5a45b9858804e6c016584f13e17e08ef1a3d4d2c592203a64f067da653ba75.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xf26a26418a70756e99ef75bd9aa90122d9eafa7c4126b8c51ee566caa925f6b0.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/runtime/wallets-all.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/runtime/wallets.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1b/wallet_summary.csv": 6 + }, + "status": "PASS_NONEMPTY", + "substantive_rows": 917 + }, + "p1c": { + "file_count": 30, + "row_counts": { + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/MANIFEST.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/REQUEST_STATS.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/VALIDATION.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/address_nft_transfers.csv": 0, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/address_transactions.csv": 0, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/errors.csv": 0, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/known_mint_verification.csv": 8, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/known_sale_verification.csv": 10, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x121a5da145cd6dbe322d75c514481d2efa188816.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x204a9d05e454619bf713b947c3e87302f4509d3c.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x43948bababa753a587fc475ced55c74760b716fc.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x51e99a26a3b538a4023e0e1af16e97153056a814.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x6ee78170825f65a2bd9cf9cda48ce2282496ddba.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x0098561274910aa58f30cfea9384bb21bf9712c247a06f95cd3e456e2f74dc0f.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x18fadee84ee44c78ed3cf9d48479a5272960ae1ad8d44ed3a971547635b78df8.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x3b584db19fcf6c26e2eaf8adf00b989fbfe7e1c6abea66d6438fc73944aed251.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x53278df4605afbf43b80d2ab88dbb4187e7e496f5759d57754f14f5921271c3c.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x8e428f651c1bccca578440f3b5afd9ed5ef8160cd050fd9feb5cb023a7783e5b.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x99a89fbc3df0a538a41ed4c65252992421a9f4469aa4f976fca68a0b130db6a4.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xa300d7eeb8a24dc20ecaa15631a5c79ea51b1c8319f2054bde92bf6352d21f67.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xa7e00281106c4dbbd87e8bdf942c97b9c115d678bf641e87d46905aa166e3a06.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xe98e336f2ac0b7f290bb1e5600204eaa40b6ec20808703e4d499c532901ae320.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/runtime/wallets-all.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/runtime/wallets.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1c/wallet_summary.csv": 6 + }, + "status": "PASS_NONEMPTY", + "substantive_rows": 6 + }, + "p1d": { + "file_count": 25, + "row_counts": { + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/MANIFEST.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/REQUEST_STATS.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/VALIDATION.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/address_transactions.csv": 3910, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/errors.csv": 0, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/known_mint_verification.csv": 3, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/known_sale_verification.csv": 7, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0x91481f82d8625f7c472c8d666aadf4182fa11ec8.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0x961ef2b282bfa761346464f4f3bffa4bf7eea53f.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0xd04b2aafb1eef38d5004c062adfa46de964602a2.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/address_0xdc979c981cecf9b1e617357c0bc494e53e0f019d.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x6e9ea02c1873272d7e5965a74165b0dc11d9b7a2f9d8f8eda652630a6abdcffc.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xa3da267f93f94bce2de847ba96017849edbfb31556403cfde6200fbcf1675f74.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xb07a788c7906252d6ee5e131c039ebf07fd9fc8e42aa9d5491500bdeaf15058c.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xc6c6ac61e4fc09eb3712c77d9004d22f68260f4917f68092d80a17d2e7726441.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xff1487b7d945726ad0a347e386d4ad8828271fd157533565f3801313bb1f6b76.json": { + "json_type": "dict" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/runtime/wallets-all.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/runtime/wallets.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "final-p1-work/recovered-p1-partial/source-artifacts/rhc-wallet-verification-p1d/wallet_summary.csv": 5 + }, + "status": "PASS_NONEMPTY", + "substantive_rows": 3915 + } + }, + "status": "PASS" +} \ No newline at end of file diff --git a/recovered-p1-final-checkpoint/SHA256SUMS.txt b/recovered-p1-final-checkpoint/SHA256SUMS.txt new file mode 100644 index 0000000..a4e617a --- /dev/null +++ b/recovered-p1-final-checkpoint/SHA256SUMS.txt @@ -0,0 +1 @@ +736ddabc527f02b7dae0f8cae34192a57757e19c9a2d1614c908146ff78f484f rhc-p1-final.tar.gz diff --git a/recovered-p1-final-checkpoint/rhc-p1-final.tar.gz.part-000 b/recovered-p1-final-checkpoint/rhc-p1-final.tar.gz.part-000 new file mode 100644 index 0000000..e8d57aa Binary files /dev/null and b/recovered-p1-final-checkpoint/rhc-p1-final.tar.gz.part-000 differ diff --git a/recovered-p1-partial-checkpoint/MANIFEST.json b/recovered-p1-partial-checkpoint/MANIFEST.json new file mode 100644 index 0000000..aed72f8 --- /dev/null +++ b/recovered-p1-partial-checkpoint/MANIFEST.json @@ -0,0 +1,632 @@ +[ + { + "bytes": 18622, + "path": "RECOVERY_REPORT.json", + "sha256": "39cbbfe9bb4ffc6e88e414cd6b51c4645314b1f6ee41c7adea202d347d22849b" + }, + { + "bytes": 1910, + "path": "source-artifacts/rhc-wallet-verification-p0/MANIFEST.json", + "sha256": "f2619d6c3eb8c3acbfab3590d70a0c293bb79fe14fd8359e4552d082f53192e5" + }, + { + "bytes": 20, + "path": "source-artifacts/rhc-wallet-verification-p0/REQUEST_STATS.json", + "sha256": "a4275a51b228122eef8c23acdf96eba4992760fe2056dbde620ad785656dd06b" + }, + { + "bytes": 511, + "path": "source-artifacts/rhc-wallet-verification-p0/VALIDATION.json", + "sha256": "6d95a17d49069acaf6d14226648167b92e1627d5c2853e6148373190075b4fbb" + }, + { + "bytes": 35843, + "path": "source-artifacts/rhc-wallet-verification-p0/address_nft_transfers.csv", + "sha256": "ae53186b9303d82e35438f5f1a5884ddac6dab81e6d31b980369b344a08e7f94" + }, + { + "bytes": 8080, + "path": "source-artifacts/rhc-wallet-verification-p0/address_transactions.csv", + "sha256": "d505edf57beb7db9302662154cfb206b3dfbd7b00cf90b708aa19b82251f58cc" + }, + { + "bytes": 10, + "path": "source-artifacts/rhc-wallet-verification-p0/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 1189, + "path": "source-artifacts/rhc-wallet-verification-p0/known_mint_verification.csv", + "sha256": "05411fa333005d55f36c3c07ce04ada5724707e7e6ddd8be61be2d776cddcb0b" + }, + { + "bytes": 2970, + "path": "source-artifacts/rhc-wallet-verification-p0/known_sale_verification.csv", + "sha256": "cfb14e728db9088e45e5881992f153d403432af746711d40312d369420e02d76" + }, + { + "bytes": 51425, + "path": "source-artifacts/rhc-wallet-verification-p0/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "b245d4faeb21d93b2b80f6696a1249ace3ea6f1352e0b316a72c7806f80b3a20" + }, + { + "bytes": 58213, + "path": "source-artifacts/rhc-wallet-verification-p0/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "f3dde3e00985313b8991fd8579fbb1178a89c2dcb703d3a79ccc5805c0b68a74" + }, + { + "bytes": 109016, + "path": "source-artifacts/rhc-wallet-verification-p0/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "0d05c47fd0e59c7392a7c6be07ee753ce5a77ecb042a1883c50bc32f318eddbe" + }, + { + "bytes": 29826, + "path": "source-artifacts/rhc-wallet-verification-p0/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "source-artifacts/rhc-wallet-verification-p0/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 1408, + "path": "source-artifacts/rhc-wallet-verification-p0/runtime/wallets.json", + "sha256": "9e3234f00ce6ecd4fa290cb1c09954c8861d0fd71fc04196cff9fa4c3b0cbe67" + }, + { + "bytes": 3067, + "path": "source-artifacts/rhc-wallet-verification-p0/token_lifecycle_evidence.json", + "sha256": "104b3578150a5c8f00e43267fdae89cb41d47c9cc28a99ee7a5449c7f04e4d07" + }, + { + "bytes": 1945, + "path": "source-artifacts/rhc-wallet-verification-p0/wallet_summary.csv", + "sha256": "9be495b7f09d0212db384cc2ecbc97118f43458b81376fbcf616f6605ca74cde" + }, + { + "bytes": 3869, + "path": "source-artifacts/rhc-wallet-verification-p1a/MANIFEST.json", + "sha256": "d1ccb1ed736b96c71811f87aba0bb40e26cb242aeefef5811e0966051b88e2c2" + }, + { + "bytes": 90, + "path": "source-artifacts/rhc-wallet-verification-p1a/REQUEST_STATS.json", + "sha256": "e703dd14921452342b46abe7e83567353e781fe81106cae2c1890976a8241c69" + }, + { + "bytes": 514, + "path": "source-artifacts/rhc-wallet-verification-p1a/VALIDATION.json", + "sha256": "e2f2fab03b6c9691c9fcc8f1c57f14e33e9689b3878568b53033afb5d44d2639" + }, + { + "bytes": 2417792, + "path": "source-artifacts/rhc-wallet-verification-p1a/address_nft_transfers.csv", + "sha256": "65a5f7e0104e09a77e807ca9bf689efa4ea8765aeb47a85429016db81eedb2f4" + }, + { + "bytes": 1234263, + "path": "source-artifacts/rhc-wallet-verification-p1a/address_transactions.csv", + "sha256": "fa68e62f4474d4a197144f81ba3b59ad1b7202d9265ca89f636eabaef7692371" + }, + { + "bytes": 10, + "path": "source-artifacts/rhc-wallet-verification-p1a/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 5496, + "path": "source-artifacts/rhc-wallet-verification-p1a/known_mint_verification.csv", + "sha256": "7882ec7184318967c6000738ef0fb746e4a4afec445e1263639527518d9afb67" + }, + { + "bytes": 2970, + "path": "source-artifacts/rhc-wallet-verification-p1a/known_sale_verification.csv", + "sha256": "cfb14e728db9088e45e5881992f153d403432af746711d40312d369420e02d76" + }, + { + "bytes": 7440, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/address_0x02e9f0322620260aeac9110fe167a4d7e907314f.json", + "sha256": "081312ba156d8bc7ff625f7f8230d47311ba8271de4537727881ffd21e0a83c3" + }, + { + "bytes": 1984190, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/address_0x1c552cf70d8c34156318d6dad4c6c69c3e956392.json", + "sha256": "053a84fdcb04f058f14522e0972a669f4d3d7c7f7fa101b2c158fdccef3ac771" + }, + { + "bytes": 1871287, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/address_0x562f6ac10723ef6af9f077a83cf25135fb369612.json", + "sha256": "a5bb6a0a10b670777eb813240c235930521f50b9fb8a969bbd3bbbeb1c547596" + }, + { + "bytes": 441122, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/address_0x66d7472b717277277bf8ea3b1d07922731d8ea35.json", + "sha256": "91ba17b9e12310c77a151c2574e60bd2f1cfc6b059adf7e6ceb014d1dabe3d02" + }, + { + "bytes": 8198, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/address_0x745b086f34efd3bfde94612c40f3b03118d0d345.json", + "sha256": "4843ff375fda205e46f0ee39353dbe3772bd4b515f98befea747f9dd4bcd35b5" + }, + { + "bytes": 51425, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "87c1cece544f1dbb85a1658992d18ba41ef8504a116150d94a0c07ec4a3c9146" + }, + { + "bytes": 113127, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x0013db778f8162d0783e4e1fb16830f40cf69343512b8088a477bdb87e6bb897.json", + "sha256": "8f6caed3c822cf590b0d7c39a35d0cd007b5467fbad0fa1d77c07d01c47b07de" + }, + { + "bytes": 247676, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x3f0f43a55c6b227a6997aec39be0f1ea873fa962365d1053d193218ed480e4f9.json", + "sha256": "5830c9b248ffa1d6175874f4a800ecf5238dd1fadbd508ba5a7e3d56d60b0f56" + }, + { + "bytes": 172322, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x54df9da687a35b1cc26bba6bca2b5a09ad7db6e23b1b9d8d271bffc8ae915623.json", + "sha256": "05ad50ab0250e171ff5bc49ecfe4e916e1ba78b0258b02baa18c435d228657e6" + }, + { + "bytes": 60162, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x66006b675af42144201034b40ac4c79d61cef0580ac8995dd60155665572551d.json", + "sha256": "b4e7bdceece8f6be5f60d4b4641a9f108c0c0424d2b730a38a9ca99e08383503" + }, + { + "bytes": 58213, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "940fa751529a5e203dece7a3afe44f82659957ffa97791a0ccd7f68591b43c7f" + }, + { + "bytes": 109016, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "103b611dc341b1103c4e303db44dd66f0fa5eb40d031e6cb0b4e30074f5d5694" + }, + { + "bytes": 122891, + "path": "source-artifacts/rhc-wallet-verification-p1a/raw/tx_0xe6ad7eaaeb48164249f83db770c73e1a494c1032047c63c507251c5c35399c12.json", + "sha256": "98500dc053c4973d5b5b9b4a2e3c5166ebf99061125c635d633ab84bf2d505ca" + }, + { + "bytes": 29826, + "path": "source-artifacts/rhc-wallet-verification-p1a/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "source-artifacts/rhc-wallet-verification-p1a/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 5571, + "path": "source-artifacts/rhc-wallet-verification-p1a/runtime/wallets.json", + "sha256": "17bdacac8b682438481af65a87ec7a011a1750d500309e4c3ece687705976f6f" + }, + { + "bytes": 29683, + "path": "source-artifacts/rhc-wallet-verification-p1a/token_lifecycle_evidence.json", + "sha256": "287cbc2e7c1438ca54f1f10978cc6bf2eb23f7343325e417307dd726ee9339d7" + }, + { + "bytes": 12469, + "path": "source-artifacts/rhc-wallet-verification-p1a/wallet_summary.csv", + "sha256": "ffb3ce892126856e06dfbed55a6532ceb39b4dfc9ba2033dbe3fa428515619bd" + }, + { + "bytes": 3874, + "path": "source-artifacts/rhc-wallet-verification-p1b/MANIFEST.json", + "sha256": "2af1774bd47f66d449696b4c8616d4ee16dedd53e527ef842412c0c19ae8f2a0" + }, + { + "bytes": 57, + "path": "source-artifacts/rhc-wallet-verification-p1b/REQUEST_STATS.json", + "sha256": "54a04f90ba3506b3217427a669b0bda2809d4badc240b1cbc5e61f0246a16979" + }, + { + "bytes": 514, + "path": "source-artifacts/rhc-wallet-verification-p1b/VALIDATION.json", + "sha256": "b9b9e14b88fc7b41b09c2c9ddbe7e448c07a2246c0f337340f2b3451be78cb70" + }, + { + "bytes": 5735191, + "path": "source-artifacts/rhc-wallet-verification-p1b/address_nft_transfers.csv", + "sha256": "921e293d33b3ce150b68102f0b7ae76013e67a95d975e368464ed717ef176130" + }, + { + "bytes": 2219010, + "path": "source-artifacts/rhc-wallet-verification-p1b/address_transactions.csv", + "sha256": "7ad8d4b89dd33a326e10451ace8424340882a5eab2fb0a919575a30d6f5390dc" + }, + { + "bytes": 10, + "path": "source-artifacts/rhc-wallet-verification-p1b/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 5559, + "path": "source-artifacts/rhc-wallet-verification-p1b/known_mint_verification.csv", + "sha256": "32b03c7e6079563cd35a9949be7af92e4c6483b4d0784fff7a8b40aa53470337" + }, + { + "bytes": 2970, + "path": "source-artifacts/rhc-wallet-verification-p1b/known_sale_verification.csv", + "sha256": "cfb14e728db9088e45e5881992f153d403432af746711d40312d369420e02d76" + }, + { + "bytes": 1171553, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/address_0x764611d78dc1a8039a713d33ff614423963ae486.json", + "sha256": "b996df431efb567d7152a4099d8a9f5d5b254caecc292b1cbbec175a5224e2bc" + }, + { + "bytes": 51425, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "0a797ea1cf58aa935b4bab723f823db650dc2225275897bbe4aa0571056df5c9" + }, + { + "bytes": 243777, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/address_0x9dade467874b15382e6b7dff96486d7126f41116.json", + "sha256": "1eaa14fe7be5f1098b8c4a11d93a2ab502ec4f2eabd9e3ffb7198c448551b6a3" + }, + { + "bytes": 1534986, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/address_0xa006dcb0bf9714eb9feb7f88c19ebcd20cc44cfa.json", + "sha256": "a38eb755fd750dcfaf84f1925a5e3232b31c0c868e6187788dcd306281b43b48" + }, + { + "bytes": 512870, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/address_0xb475b45d9d56334a86aa4e907ad8573f93fd6cb3.json", + "sha256": "1af624ac532967d4ccca42a85224efb6aebfbb069cea256a73c4020c4daae81b" + }, + { + "bytes": 5551441, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/address_0xedb0f2bdf9f5b2d755a1d8a97154b643a15b3c8e.json", + "sha256": "7d3ecbcad0a4ed9be02f9194ecbce880523688b61d12bbb6a4b1c21f182fb1f2" + }, + { + "bytes": 183865, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x1c791f750a52b255c0575f9a11693cd885724322e317de961c39199a6a5c6ff7.json", + "sha256": "6660e628b6063b924da2f6f2b739ddd808df98ff24703cc503f4a8715113c87f" + }, + { + "bytes": 58213, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "e7671de8df81f6dc05665ecf19416ddb9ede1edc847a93050c6827d65af0b59c" + }, + { + "bytes": 109016, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "606210d76aef1df7db5cd138747b774fccbebf9cf19d917a9c91d44150829bb5" + }, + { + "bytes": 58465, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/tx_0x96c0586b5c93239539c8b0eb40a837504c7a73067aa1355130f0cd08dbaf783b.json", + "sha256": "fef9f99f3e9ddd378facda2f56c90d63fe05152f972965eb9d9fd099b937a7f5" + }, + { + "bytes": 110390, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xcacc6375301c1714f301c8a5117344097e03abe0275438cf3cb7a8f1c22f7653.json", + "sha256": "bd4cd3ebd618f77abd2d88b9ac801ba7c3b88252e5bca0dff3bc6a7b7c5effb5" + }, + { + "bytes": 411867, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xcc5a45b9858804e6c016584f13e17e08ef1a3d4d2c592203a64f067da653ba75.json", + "sha256": "febf5638030129da5d50b28530551d0b923b8761b76ec3d98b853f8cc3cc7959" + }, + { + "bytes": 185289, + "path": "source-artifacts/rhc-wallet-verification-p1b/raw/tx_0xf26a26418a70756e99ef75bd9aa90122d9eafa7c4126b8c51ee566caa925f6b0.json", + "sha256": "8d62ab901088bd317f87dd7b3f3c64fa584aea206793b276badd4ae492151a8c" + }, + { + "bytes": 29826, + "path": "source-artifacts/rhc-wallet-verification-p1b/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "source-artifacts/rhc-wallet-verification-p1b/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 5534, + "path": "source-artifacts/rhc-wallet-verification-p1b/runtime/wallets.json", + "sha256": "6e7b535be13c23e6b8bf5b945c0839b0d15a7d330a90e040c110e9fc381c3ac6" + }, + { + "bytes": 40604, + "path": "source-artifacts/rhc-wallet-verification-p1b/token_lifecycle_evidence.json", + "sha256": "88b2f3202c823ee26258fdc2661ecd2156fb81ace2c9bfdbb7dd15421fde4ba0" + }, + { + "bytes": 12975, + "path": "source-artifacts/rhc-wallet-verification-p1b/wallet_summary.csv", + "sha256": "2663a1e6742f8513a7cb1aa1657dab0003b3de54f49936703e6a8b58e33ad743" + }, + { + "bytes": 4672, + "path": "source-artifacts/rhc-wallet-verification-p1c/MANIFEST.json", + "sha256": "ef2cf3db264ba5df0e12479179e2ee3dbc72107b853e72f2e2792afda5d83d36" + }, + { + "bytes": 57, + "path": "source-artifacts/rhc-wallet-verification-p1c/REQUEST_STATS.json", + "sha256": "a82e9a211badccf2cff9f51a1e4fea5c9723779b23b2794040daaf75a5655ec4" + }, + { + "bytes": 513, + "path": "source-artifacts/rhc-wallet-verification-p1c/VALIDATION.json", + "sha256": "f059387ed85e61baffa23fc27644062f7d9d01f03b90970338f01e54c245807c" + }, + { + "bytes": 10, + "path": "source-artifacts/rhc-wallet-verification-p1c/address_nft_transfers.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 10, + "path": "source-artifacts/rhc-wallet-verification-p1c/address_transactions.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 10, + "path": "source-artifacts/rhc-wallet-verification-p1c/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 7102, + "path": "source-artifacts/rhc-wallet-verification-p1c/known_mint_verification.csv", + "sha256": "88ea43dbe88ee79ce62da5fdaf7925ccc7494264a688ef307778c6bdf3bcf564" + }, + { + "bytes": 139484, + "path": "source-artifacts/rhc-wallet-verification-p1c/known_sale_verification.csv", + "sha256": "79c798ad775c9677a6c2444bc0af0fa089e7b5f5fa4988d31717458f83a2539e" + }, + { + "bytes": 24118, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/address_0x121a5da145cd6dbe322d75c514481d2efa188816.json", + "sha256": "ceebfa88105160cbc2af90e77f28483a8c7dd383efe705697558a3d8e86e8192" + }, + { + "bytes": 24118, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/address_0x204a9d05e454619bf713b947c3e87302f4509d3c.json", + "sha256": "6e97bc670b0912c4ba672315f49e31abec34abb993fad7679c2b2b638b13c488" + }, + { + "bytes": 24118, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/address_0x43948bababa753a587fc475ced55c74760b716fc.json", + "sha256": "d7a2f80406c42a48a8ce4c0a4ee3f741e5712720967228472930dd6d82f0e7fb" + }, + { + "bytes": 24118, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/address_0x51e99a26a3b538a4023e0e1af16e97153056a814.json", + "sha256": "914fe9adc1ef8e394c5ad0b2612678054baf6746170b18bc70b48400b644c16d" + }, + { + "bytes": 24118, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/address_0x6ee78170825f65a2bd9cf9cda48ce2282496ddba.json", + "sha256": "e0eb81fe91c4851321219840249f357b23afcd090cc9f1ccdf9437dd1a62bc78" + }, + { + "bytes": 24118, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "c634acf24d90d1d0da39a329469d23ed3adce89d14d4c578bc7e25f2a3278361" + }, + { + "bytes": 133327, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x0098561274910aa58f30cfea9384bb21bf9712c247a06f95cd3e456e2f74dc0f.json", + "sha256": "6491164dbdfc2351c449ffe6e9eadcfd4302b3f274764ef3b0a87749b3f76131" + }, + { + "bytes": 61192, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x18fadee84ee44c78ed3cf9d48479a5272960ae1ad8d44ed3a971547635b78df8.json", + "sha256": "542da71d6d12b9f6bdf91b00fb01384be65df484832cfe8ce0c0f1047696f45a" + }, + { + "bytes": 63431, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x3b584db19fcf6c26e2eaf8adf00b989fbfe7e1c6abea66d6438fc73944aed251.json", + "sha256": "9610c5735d4e56e2e8e5e62edde8fc1b51fe73ae87ea4648ff96cd9655e68092" + }, + { + "bytes": 62186, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x53278df4605afbf43b80d2ab88dbb4187e7e496f5759d57754f14f5921271c3c.json", + "sha256": "3e2bb3f1f04a8434550189c169837cca804ec235c45fd86100c57d82287ed1d7" + }, + { + "bytes": 60273, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "6909fc1f5df15713a08a69415efaa917441c76e52e3f23e73c764d9d3ffca0b1" + }, + { + "bytes": 64688, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "05195edfcd5eb722e422c8195ff0d9ceaebbf7c922fe28af41174c61fa39e5e3" + }, + { + "bytes": 60997, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x8e428f651c1bccca578440f3b5afd9ed5ef8160cd050fd9feb5cb023a7783e5b.json", + "sha256": "ce09454d82f7c980168784e5e8a9c8a6790409dccfc6e8e856af9d012768596b" + }, + { + "bytes": 103080, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0x99a89fbc3df0a538a41ed4c65252992421a9f4469aa4f976fca68a0b130db6a4.json", + "sha256": "11d8f7e49fa718680e74c2066cb0605a62839795c4d86b8e54d5720785315c1f" + }, + { + "bytes": 61877, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xa300d7eeb8a24dc20ecaa15631a5c79ea51b1c8319f2054bde92bf6352d21f67.json", + "sha256": "6c7106dc96976a0c166152364f3ef2a83361ae1a37e213b56d8e5bf77b43d43f" + }, + { + "bytes": 64462, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xa7e00281106c4dbbd87e8bdf942c97b9c115d678bf641e87d46905aa166e3a06.json", + "sha256": "68aa6c179ef8921e6092fdce206196e799d93de08bd8c150c819829ea305ff47" + }, + { + "bytes": 63390, + "path": "source-artifacts/rhc-wallet-verification-p1c/raw/tx_0xe98e336f2ac0b7f290bb1e5600204eaa40b6ec20808703e4d499c532901ae320.json", + "sha256": "72970c23188ae1153a37cb785c0b78a323b46107567e6910f7de0fd0b9a6573e" + }, + { + "bytes": 29826, + "path": "source-artifacts/rhc-wallet-verification-p1c/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "source-artifacts/rhc-wallet-verification-p1c/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 11331, + "path": "source-artifacts/rhc-wallet-verification-p1c/runtime/wallets.json", + "sha256": "98687d49d0c87232b3ac8179e4984ad0560dbad729751989f0a29e5876fca9c8" + }, + { + "bytes": 38759, + "path": "source-artifacts/rhc-wallet-verification-p1c/token_lifecycle_evidence.json", + "sha256": "b5b0c93adfb70b1dd7923ba298ddc787841b6f9268b1601cdfd8b4d162c0d37b" + }, + { + "bytes": 141940, + "path": "source-artifacts/rhc-wallet-verification-p1c/wallet_summary.csv", + "sha256": "e5fc306e46b1742cb45a5dc43ea9e726d709c5352935c74fee28ea5ca37d62e0" + }, + { + "bytes": 3695, + "path": "source-artifacts/rhc-wallet-verification-p1d/MANIFEST.json", + "sha256": "c4a307e0167515b7ab3204cde28e0a176073db8b607758de0912f117f257a370" + }, + { + "bytes": 90, + "path": "source-artifacts/rhc-wallet-verification-p1d/REQUEST_STATS.json", + "sha256": "8c1ac3a912ff425020a3ac6cbac88adf459bb55cabcf847a3434f230a3ee45b9" + }, + { + "bytes": 517, + "path": "source-artifacts/rhc-wallet-verification-p1d/VALIDATION.json", + "sha256": "b67144475f2eb36db9577a79691ac4b2f3b7833796f8391956699f51eca3514d" + }, + { + "bytes": 33760362, + "path": "source-artifacts/rhc-wallet-verification-p1d/address_nft_transfers.csv", + "sha256": "3a5740871f132a964618c26829d13e3dcbdfd196d587f38529030d2496eeb7b5" + }, + { + "bytes": 5945812, + "path": "source-artifacts/rhc-wallet-verification-p1d/address_transactions.csv", + "sha256": "852ef01ab6b3c12d9295b7420fd4fb2270536c347826a3817673605a7c34998c" + }, + { + "bytes": 10, + "path": "source-artifacts/rhc-wallet-verification-p1d/errors.csv", + "sha256": "cbf2ec508591df442cf34ebef01c3794bbe2c036cd92e251965c8de3033c9c22" + }, + { + "bytes": 2905, + "path": "source-artifacts/rhc-wallet-verification-p1d/known_mint_verification.csv", + "sha256": "5c128b98d7273de038c8895a3247226b2203d1c9df9438ce406f0c389822c020" + }, + { + "bytes": 93642, + "path": "source-artifacts/rhc-wallet-verification-p1d/known_sale_verification.csv", + "sha256": "3ced4fa2ae06b7aa1b5a4d1ce8d1ada85c4752e56a81776753ec28467fd4f71d" + }, + { + "bytes": 51425, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json", + "sha256": "a8cecee7d97ceae05c99ba8f5d26f54ce9d134cf1a3861df6944726df5689a38" + }, + { + "bytes": 13131829, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/address_0x91481f82d8625f7c472c8d666aadf4182fa11ec8.json", + "sha256": "a9797cf83cf5650930934c4b8c459a701ead2b2f33c95231e4d7af544648ebc1" + }, + { + "bytes": 15144413, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/address_0x961ef2b282bfa761346464f4f3bffa4bf7eea53f.json", + "sha256": "5c875db42e50521324947bbed22549971c0c65efc254d13c712c780049bd8071" + }, + { + "bytes": 16849774, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/address_0xd04b2aafb1eef38d5004c062adfa46de964602a2.json", + "sha256": "349c84bc5c02315340b6715ebd8eaf49f2054703829a1137680f24337f37fd68" + }, + { + "bytes": 4384832, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/address_0xdc979c981cecf9b1e617357c0bc494e53e0f019d.json", + "sha256": "61f7e1c7b2bf1f09a6972c32bea5017de9b04734ca53706a2542d798c91e1bcb" + }, + { + "bytes": 491962, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x6e9ea02c1873272d7e5965a74165b0dc11d9b7a2f9d8f8eda652630a6abdcffc.json", + "sha256": "bc1d433b35affd3e6a581d7d83319110f69dd47375f975ee0384ca4bbbea57dd" + }, + { + "bytes": 58213, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json", + "sha256": "a1dcf77bb5757c3f02c2184f9e26f90c71a30e9f83462710dd97b53683bae8b9" + }, + { + "bytes": 109016, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json", + "sha256": "197a1a67624949972b9c329642766a2885f0fd64c615e6585c8f7ce32d24a958" + }, + { + "bytes": 424244, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xa3da267f93f94bce2de847ba96017849edbfb31556403cfde6200fbcf1675f74.json", + "sha256": "26050d13625340f386d80ebffd0550f260241a2eca3405bf230fd489e9ec3966" + }, + { + "bytes": 94900, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xb07a788c7906252d6ee5e131c039ebf07fd9fc8e42aa9d5491500bdeaf15058c.json", + "sha256": "c295d139dfcc4d13d6f1ef60771cd49809b97a9e954b8888d00492fe856d215e" + }, + { + "bytes": 1804858, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xc6c6ac61e4fc09eb3712c77d9004d22f68260f4917f68092d80a17d2e7726441.json", + "sha256": "60b57b85eca29491f6bf68535d04a75129b44e75665c6dc6942a8325649fbe6e" + }, + { + "bytes": 235322, + "path": "source-artifacts/rhc-wallet-verification-p1d/raw/tx_0xff1487b7d945726ad0a347e386d4ad8828271fd157533565f3801313bb1f6b76.json", + "sha256": "adb4ad6886c4c3379e241ca2fc0caf407e4fb199ac1c173ef6f503f3bf4db320" + }, + { + "bytes": 29826, + "path": "source-artifacts/rhc-wallet-verification-p1d/runtime/collector.py", + "sha256": "90607617bfe8bfc579c36f8fc0763ccfc0e1323cc0d38ce5181824abc87833b2" + }, + { + "bytes": 25062, + "path": "source-artifacts/rhc-wallet-verification-p1d/runtime/wallets-all.json", + "sha256": "6fcc19e70dae6429e1deff0c33e0cb25f557f33654a31071816e29d1e04667d2" + }, + { + "bytes": 6850, + "path": "source-artifacts/rhc-wallet-verification-p1d/runtime/wallets.json", + "sha256": "95ea646f013cb99aca77ae4ec8de8692fa4aea1574ba7f796af742c3702a0372" + }, + { + "bytes": 26251, + "path": "source-artifacts/rhc-wallet-verification-p1d/token_lifecycle_evidence.json", + "sha256": "da96a62994da950f9741f9100edcd1952d655ae6515911a721a4ff0ef236bd89" + }, + { + "bytes": 10821, + "path": "source-artifacts/rhc-wallet-verification-p1d/wallet_summary.csv", + "sha256": "533a06d08b1063673f3a349871c3b084a8f96885c95ef2d7c4c5f6cb689495f1" + }, + { + "bytes": 5157, + "path": "source_artifacts.json", + "sha256": "915b6053fd546aa1c71fcec206f292c7190f162b418f2ab87959f20142b69813" + }, + { + "bytes": 13347, + "path": "source_jobs.json", + "sha256": "1238f8b2c5de46e31351f0c4ba4c524d81eb367a8a4df7ff4a2bf0b6ee1d88b8" + } +] \ No newline at end of file diff --git a/recovered-p1-partial-checkpoint/RECOVERY_REPORT.json b/recovered-p1-partial-checkpoint/RECOVERY_REPORT.json new file mode 100644 index 0000000..b5eae3a --- /dev/null +++ b/recovered-p1-partial-checkpoint/RECOVERY_REPORT.json @@ -0,0 +1,379 @@ +{ + "missing_shards": 0, + "recovered_shards": 4, + "shards": { + "p1a": { + "file_count": 26, + "row_counts": { + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/MANIFEST.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/REQUEST_STATS.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/VALIDATION.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/address_transactions.csv": 467, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/errors.csv": 0, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/known_mint_verification.csv": 6, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/known_sale_verification.csv": 1, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/address_0x02e9f0322620260aeac9110fe167a4d7e907314f.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/address_0x1c552cf70d8c34156318d6dad4c6c69c3e956392.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/address_0x562f6ac10723ef6af9f077a83cf25135fb369612.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/address_0x66d7472b717277277bf8ea3b1d07922731d8ea35.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/address_0x745b086f34efd3bfde94612c40f3b03118d0d345.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/tx_0x0013db778f8162d0783e4e1fb16830f40cf69343512b8088a477bdb87e6bb897.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/tx_0x3f0f43a55c6b227a6997aec39be0f1ea873fa962365d1053d193218ed480e4f9.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/tx_0x54df9da687a35b1cc26bba6bca2b5a09ad7db6e23b1b9d8d271bffc8ae915623.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/tx_0x66006b675af42144201034b40ac4c79d61cef0580ac8995dd60155665572551d.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/raw/tx_0xe6ad7eaaeb48164249f83db770c73e1a494c1032047c63c507251c5c35399c12.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/runtime/wallets-all.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/runtime/wallets.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1a/wallet_summary.csv": 6 + }, + "status": "RECOVERED_NONEMPTY", + "substantive_rows": 473 + }, + "p1b": { + "file_count": 26, + "row_counts": { + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/MANIFEST.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/REQUEST_STATS.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/VALIDATION.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/address_transactions.csv": 911, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/errors.csv": 0, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/known_mint_verification.csv": 6, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/known_sale_verification.csv": 1, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/address_0x764611d78dc1a8039a713d33ff614423963ae486.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/address_0x9dade467874b15382e6b7dff96486d7126f41116.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/address_0xa006dcb0bf9714eb9feb7f88c19ebcd20cc44cfa.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/address_0xb475b45d9d56334a86aa4e907ad8573f93fd6cb3.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/address_0xedb0f2bdf9f5b2d755a1d8a97154b643a15b3c8e.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/tx_0x1c791f750a52b255c0575f9a11693cd885724322e317de961c39199a6a5c6ff7.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/tx_0x96c0586b5c93239539c8b0eb40a837504c7a73067aa1355130f0cd08dbaf783b.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/tx_0xcacc6375301c1714f301c8a5117344097e03abe0275438cf3cb7a8f1c22f7653.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/tx_0xcc5a45b9858804e6c016584f13e17e08ef1a3d4d2c592203a64f067da653ba75.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/raw/tx_0xf26a26418a70756e99ef75bd9aa90122d9eafa7c4126b8c51ee566caa925f6b0.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/runtime/wallets-all.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/runtime/wallets.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1b/wallet_summary.csv": 6 + }, + "status": "RECOVERED_NONEMPTY", + "substantive_rows": 917 + }, + "p1c": { + "file_count": 30, + "row_counts": { + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/MANIFEST.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/REQUEST_STATS.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/VALIDATION.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/address_nft_transfers.csv": 0, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/address_transactions.csv": 0, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/errors.csv": 0, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/known_mint_verification.csv": 8, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/known_sale_verification.csv": 10, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/address_0x121a5da145cd6dbe322d75c514481d2efa188816.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/address_0x204a9d05e454619bf713b947c3e87302f4509d3c.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/address_0x43948bababa753a587fc475ced55c74760b716fc.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/address_0x51e99a26a3b538a4023e0e1af16e97153056a814.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/address_0x6ee78170825f65a2bd9cf9cda48ce2282496ddba.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0x0098561274910aa58f30cfea9384bb21bf9712c247a06f95cd3e456e2f74dc0f.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0x18fadee84ee44c78ed3cf9d48479a5272960ae1ad8d44ed3a971547635b78df8.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0x3b584db19fcf6c26e2eaf8adf00b989fbfe7e1c6abea66d6438fc73944aed251.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0x53278df4605afbf43b80d2ab88dbb4187e7e496f5759d57754f14f5921271c3c.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0x8e428f651c1bccca578440f3b5afd9ed5ef8160cd050fd9feb5cb023a7783e5b.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0x99a89fbc3df0a538a41ed4c65252992421a9f4469aa4f976fca68a0b130db6a4.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0xa300d7eeb8a24dc20ecaa15631a5c79ea51b1c8319f2054bde92bf6352d21f67.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0xa7e00281106c4dbbd87e8bdf942c97b9c115d678bf641e87d46905aa166e3a06.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/raw/tx_0xe98e336f2ac0b7f290bb1e5600204eaa40b6ec20808703e4d499c532901ae320.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/runtime/wallets-all.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/runtime/wallets.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1c/wallet_summary.csv": 6 + }, + "status": "RECOVERED_NONEMPTY", + "substantive_rows": 6 + }, + "p1d": { + "file_count": 25, + "row_counts": { + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/MANIFEST.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/REQUEST_STATS.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/VALIDATION.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/address_nft_transfers.csv": { + "error": "Error('field larger than field limit (131072)')" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/address_transactions.csv": 3910, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/errors.csv": 0, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/known_mint_verification.csv": 3, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/known_sale_verification.csv": 7, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/address_0x76d387388bea6b60ca6d1e97f446f7e26d39d313.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/address_0x91481f82d8625f7c472c8d666aadf4182fa11ec8.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/address_0x961ef2b282bfa761346464f4f3bffa4bf7eea53f.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/address_0xd04b2aafb1eef38d5004c062adfa46de964602a2.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/address_0xdc979c981cecf9b1e617357c0bc494e53e0f019d.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/tx_0x6e9ea02c1873272d7e5965a74165b0dc11d9b7a2f9d8f8eda652630a6abdcffc.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/tx_0x7d02ee071fe595521f1d09671f9b8ad9f0d2d7e1e2bfa017de53d27449cddd23.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/tx_0x8b9f009dbde5e4b55d3fc47242bf0c6b7094c4b07723dcc2148f23fe71dc1e5b.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/tx_0xa3da267f93f94bce2de847ba96017849edbfb31556403cfde6200fbcf1675f74.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/tx_0xb07a788c7906252d6ee5e131c039ebf07fd9fc8e42aa9d5491500bdeaf15058c.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/tx_0xc6c6ac61e4fc09eb3712c77d9004d22f68260f4917f68092d80a17d2e7726441.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/raw/tx_0xff1487b7d945726ad0a347e386d4ad8828271fd157533565f3801313bb1f6b76.json": { + "json_type": "dict" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/runtime/wallets-all.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/runtime/wallets.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/token_lifecycle_evidence.json": { + "json_type": "list" + }, + "recovered-p1-partial-work/download/rhc-wallet-verification-p1d/wallet_summary.csv": 5 + }, + "status": "RECOVERED_NONEMPTY", + "substantive_rows": 3915 + } + }, + "source_artifacts": [ + { + "expired": false, + "id": 9702624209, + "name": "rhc-wallet-verification-p1c", + "size_in_bytes": 344421 + }, + { + "expired": false, + "id": 9692767887, + "name": "rhc-wallet-verification-p1c", + "size_in_bytes": 7331693 + }, + { + "expired": false, + "id": 9685315886, + "name": "rhc-wallet-verification-p1c", + "size_in_bytes": 7320185 + }, + { + "expired": false, + "id": 9685075472, + "name": "rhc-wallet-verification-p1b", + "size_in_bytes": 1392958 + }, + { + "expired": false, + "id": 9684961276, + "name": "rhc-wallet-verification-p1d", + "size_in_bytes": 6096107 + }, + { + "expired": false, + "id": 9684926773, + "name": "rhc-wallet-verification-p1a", + "size_in_bytes": 863208 + }, + { + "expired": false, + "id": 9684164451, + "name": "rhc-wallet-verification-p0", + "size_in_bytes": 45139 + } + ], + "source_jobs": [ + { + "conclusion": "success", + "id": 98991907820, + "name": "verify (p1c)", + "status": "completed" + }, + { + "conclusion": "success", + "id": 98991908398, + "name": "verify (p1b)", + "status": "completed" + }, + { + "conclusion": "success", + "id": 98991908658, + "name": "verify (p1a)", + "status": "completed" + }, + { + "conclusion": "success", + "id": 98991908769, + "name": "verify (p1d)", + "status": "completed" + }, + { + "conclusion": "success", + "id": 98991932933, + "name": "verify (p0)", + "status": "completed" + } + ], + "source_run_id": 33167365485, + "status": "PASS_ALL" +} \ No newline at end of file diff --git a/recovered-p1-partial-checkpoint/SHA256SUMS.txt b/recovered-p1-partial-checkpoint/SHA256SUMS.txt new file mode 100644 index 0000000..8227757 --- /dev/null +++ b/recovered-p1-partial-checkpoint/SHA256SUMS.txt @@ -0,0 +1 @@ +035de71672394b69d9ddad41c3ac6480f50cae3437cd64cd03a3e10a345e666e rhc-p1-partial-recovered.tar.gz diff --git a/recovered-p1-partial-checkpoint/rhc-p1-partial-recovered.tar.gz.part-000 b/recovered-p1-partial-checkpoint/rhc-p1-partial-recovered.tar.gz.part-000 new file mode 100644 index 0000000..d50f15e Binary files /dev/null and b/recovered-p1-partial-checkpoint/rhc-p1-partial-recovered.tar.gz.part-000 differ diff --git a/wallet-intelligence/build_dataset.py b/wallet-intelligence/build_dataset.py new file mode 100644 index 0000000..d1e2bd0 --- /dev/null +++ b/wallet-intelligence/build_dataset.py @@ -0,0 +1,776 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import csv +import gzip +import hashlib +import json +import math +import random +import statistics +import time +import urllib.error +import urllib.request +from collections import defaultdict, deque +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Iterable + +RPC_URL = "https://rpc.mainnet.chain.robinhood.com" +ZERO = "0x0000000000000000000000000000000000000000" +NATIVE = "0x0000000000000000000000000000000000000000" +KNOWN_WETH = {"0x0bd7d308f8e1639fab988df18a8011f41eacad73"} +TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" +UA = "RHC-Wallet-Intelligence-Builder/1.0" +WINDOWS = {"15m": 15 * 60, "30m": 30 * 60, "2h": 2 * 3600, "24h": 24 * 3600} +LABELS = ("liquid_100_24h", "liquid_115_24h", "liquid_150_24h") + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def intish(value: Any, default: int = 0) -> int: + try: + if isinstance(value, str) and value.startswith("0x"): + return int(value, 16) + return int(value) + except Exception: + return default + + +def address_word(word: bytes) -> str: + return "0x" + word[-20:].hex() + + +def word(data: bytes, index: int) -> bytes: + start = index * 32 + end = start + 32 + if end > len(data): + raise ValueError(f"word {index} out of range") + return data[start:end] + + +def decode_static_tuple_array(data: bytes, offset: int, width: int) -> list[list[bytes]]: + if offset < 0 or offset + 32 > len(data): + raise ValueError("array offset out of bounds") + count = int.from_bytes(data[offset : offset + 32], "big") + cursor = offset + 32 + end = cursor + count * width * 32 + if end > len(data): + raise ValueError("array payload truncated") + return [[data[cursor + (i * width + j) * 32 : cursor + (i * width + j + 1) * 32] for j in range(width)] for i in range(count)] + + +def read_jsonl_gz(path: Path) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + with gzip.open(path, "rt", encoding="utf-8") as handle: + for line in handle: + if line.strip(): + rows.append(json.loads(line)) + return rows + + +def read_jsonl(path: Path) -> list[dict[str, Any]]: + return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] + + +def write_jsonl(path: Path, rows: Iterable[dict[str, Any]]) -> int: + count = 0 + with path.open("w", encoding="utf-8", newline="\n") as handle: + for row in rows: + handle.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n") + count += 1 + return count + + +def write_csv(path: Path, rows: list[dict[str, Any]]) -> int: + fields = sorted({key for row in rows for key in row}) if rows else ["empty"] + with path.open("w", encoding="utf-8-sig", newline="") as handle: + writer = csv.DictWriter(handle, fieldnames=fields, extrasaction="ignore") + writer.writeheader() + for row in rows: + writer.writerow({key: json.dumps(value, ensure_ascii=False, sort_keys=True) if isinstance(value, (list, dict, tuple)) else value for key, value in row.items()}) + return len(rows) + + +def rpc_request(payload: Any, attempts: int = 7) -> Any: + body = json.dumps(payload, separators=(",", ":")).encode("utf-8") + last: Exception | None = None + for attempt in range(attempts): + time.sleep(0.09 + random.random() * 0.18) + request = urllib.request.Request(RPC_URL, data=body, method="POST", headers={"Content-Type": "application/json", "Accept": "application/json", "User-Agent": UA}) + try: + with urllib.request.urlopen(request, timeout=90) as response: + return json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as exc: + last = exc + if exc.code not in (408, 425, 429, 500, 502, 503, 504): + raise + except Exception as exc: # noqa: BLE001 + last = exc + time.sleep(min(60.0, 2 ** min(attempt, 5) + random.random() * 4)) + raise RuntimeError(f"RPC request failed: {last}") + + +def rpc_batch(calls: list[tuple[str, list[Any]]]) -> dict[int, Any]: + payload = [{"jsonrpc": "2.0", "id": i, "method": method, "params": params} for i, (method, params) in enumerate(calls)] + data = rpc_request(payload) + if not isinstance(data, list): + data = [data] + result: dict[int, Any] = {} + for row in data: + if isinstance(row, dict) and "id" in row: + result[int(row["id"])] = row.get("result") if "error" not in row else {"__error__": row["error"]} + return result + + +def fetch_chain_context(log_rows: list[dict[str, Any]]) -> tuple[dict[int, dict[str, Any]], dict[str, dict[str, Any]], dict[str, dict[str, Any]], list[dict[str, Any]]]: + block_numbers = sorted({intish(row.get("blockNumber") or row.get("block_number")) for row in log_rows}) + tx_hashes = sorted({str(row.get("transactionHash") or row.get("transaction_hash") or "").lower() for row in log_rows if row.get("transactionHash") or row.get("transaction_hash")}) + blocks: dict[int, dict[str, Any]] = {} + txs: dict[str, dict[str, Any]] = {} + receipts: dict[str, dict[str, Any]] = {} + errors: list[dict[str, Any]] = [] + + for start in range(0, len(block_numbers), 80): + chunk = block_numbers[start : start + 80] + try: + response = rpc_batch([("eth_getBlockByNumber", [hex(number), False]) for number in chunk]) + for index, number in enumerate(chunk): + value = response.get(index) + if isinstance(value, dict) and "__error__" not in value: + blocks[number] = value + else: + errors.append({"kind": "block", "block_number": number, "value": value}) + except Exception as exc: # noqa: BLE001 + errors.append({"kind": "block_batch", "start": start, "error": repr(exc)}) + + for start in range(0, len(tx_hashes), 32): + chunk = tx_hashes[start : start + 32] + calls: list[tuple[str, list[Any]]] = [] + keys: list[tuple[str, str]] = [] + for tx_hash in chunk: + calls.append(("eth_getTransactionByHash", [tx_hash])); keys.append((tx_hash, "tx")) + calls.append(("eth_getTransactionReceipt", [tx_hash])); keys.append((tx_hash, "receipt")) + try: + response = rpc_batch(calls) + for index, (tx_hash, kind) in enumerate(keys): + value = response.get(index) + if not isinstance(value, dict) or "__error__" in value: + errors.append({"kind": kind, "transaction_hash": tx_hash, "value": value}) + elif kind == "tx": + txs[tx_hash] = value + else: + receipts[tx_hash] = value + except Exception as exc: # noqa: BLE001 + errors.append({"kind": "tx_receipt_batch", "start": start, "error": repr(exc)}) + return blocks, txs, receipts, errors + + +def decode_seadrop(row: dict[str, Any], blocks: dict[int, dict[str, Any]], txs: dict[str, dict[str, Any]], receipts: dict[str, dict[str, Any]]) -> dict[str, Any]: + topics = row["topics"] + data = bytes.fromhex(row["data"][2:]) + if len(topics) < 4 or len(data) < 160: + raise ValueError("invalid SeaDropMint event") + block_number = int(row["blockNumber"], 16) + tx_hash = row["transactionHash"].lower() + tx = txs.get(tx_hash) or {} + receipt = receipts.get(tx_hash) or {} + return { + "nft_contract": "0x" + topics[1][-40:], + "minter": "0x" + topics[2][-40:], + "fee_recipient": "0x" + topics[3][-40:], + "payer": address_word(word(data, 0)), + "quantity": int.from_bytes(word(data, 1), "big"), + "unit_mint_price_wei": int.from_bytes(word(data, 2), "big"), + "fee_bps": int.from_bytes(word(data, 3), "big"), + "drop_stage_index": int.from_bytes(word(data, 4), "big"), + "block_number": block_number, + "block_hash": row["blockHash"].lower(), + "transaction_hash": tx_hash, + "transaction_index": int(row.get("transactionIndex") or "0x0", 16), + "log_index": int(row["logIndex"], 16), + "timestamp_unix": intish((blocks.get(block_number) or {}).get("timestamp")), + "tx_from": str(tx.get("from") or "").lower(), + "tx_to": str(tx.get("to") or "").lower(), + "tx_value_wei": intish(tx.get("value")), + "receipt_status": intish(receipt.get("status")), + "entry_gas_cost_wei": intish(receipt.get("gasUsed")) * intish(receipt.get("effectiveGasPrice") or tx.get("gasPrice")), + } + + +def parse_item(words: list[bytes], received: bool) -> dict[str, Any]: + item = { + "item_type": int.from_bytes(words[0], "big"), + "token": address_word(words[1]), + "identifier": int.from_bytes(words[2], "big"), + "amount": int.from_bytes(words[3], "big"), + } + if received: + item["recipient"] = address_word(words[4]) + return item + + +def decode_order_fulfilled(row: dict[str, Any], blocks: dict[int, dict[str, Any]], txs: dict[str, dict[str, Any]], receipts: dict[str, dict[str, Any]]) -> dict[str, Any]: + topics = row["topics"] + data = bytes.fromhex(row["data"][2:]) + if len(topics) == 3: + if len(data) < 128: + raise ValueError("OrderFulfilled data too short") + order_hash = "0x" + word(data, 0).hex() + offerer = "0x" + topics[1][-40:] + zone = "0x" + topics[2][-40:] + recipient = address_word(word(data, 1)) + offer_offset = int.from_bytes(word(data, 2), "big") + consideration_offset = int.from_bytes(word(data, 3), "big") + elif len(topics) >= 4: + if len(data) < 96: + raise ValueError("indexed OrderFulfilled data too short") + order_hash = topics[1].lower() + offerer = "0x" + topics[2][-40:] + zone = "0x" + topics[3][-40:] + recipient = address_word(word(data, 0)) + offer_offset = int.from_bytes(word(data, 1), "big") + consideration_offset = int.from_bytes(word(data, 2), "big") + else: + raise ValueError("OrderFulfilled topics invalid") + offers = [parse_item(words, False) for words in decode_static_tuple_array(data, offer_offset, 4)] + consideration = [parse_item(words, True) for words in decode_static_tuple_array(data, consideration_offset, 5)] + block_number = int(row["blockNumber"], 16) + tx_hash = row["transactionHash"].lower() + tx = txs.get(tx_hash) or {} + receipt = receipts.get(tx_hash) or {} + return { + "order_hash": order_hash, + "offerer": offerer, + "zone": zone, + "recipient": recipient, + "offer": offers, + "consideration": consideration, + "block_number": block_number, + "block_hash": row["blockHash"].lower(), + "transaction_hash": tx_hash, + "transaction_index": int(row.get("transactionIndex") or "0x0", 16), + "log_index": int(row["logIndex"], 16), + "timestamp_unix": intish((blocks.get(block_number) or {}).get("timestamp")), + "tx_from": str(tx.get("from") or "").lower(), + "receipt_status": intish(receipt.get("status")), + "gas_used": intish(receipt.get("gasUsed")), + "effective_gas_price_wei": intish(receipt.get("effectiveGasPrice") or tx.get("gasPrice")), + } + + +def sale_rows(events: list[dict[str, Any]], transfer_rows: list[dict[str, Any]]) -> list[dict[str, Any]]: + transfers_by_tx: dict[str, list[dict[str, Any]]] = defaultdict(list) + for row in transfer_rows: + transfers_by_tx[row["transaction_hash"]].append(row) + sales: list[dict[str, Any]] = [] + for event in events: + offer_nfts = [item for item in event["offer"] if item["item_type"] in (2, 3, 4, 5)] + consideration_nfts = [item for item in event["consideration"] if item["item_type"] in (2, 3, 4, 5)] + if offer_nfts and consideration_nfts: + direction = "NFT_FOR_NFT_OR_COMPLEX" + elif offer_nfts: + direction = "LISTING" + elif consideration_nfts: + direction = "OFFER" + else: + continue + nfts = offer_nfts or consideration_nfts + contract_units: dict[str, int] = defaultdict(int) + token_ids: dict[str, set[str]] = defaultdict(set) + for item in nfts: + contract = item["token"].lower() + amount = item["amount"] if item["item_type"] in (3, 5) else 1 + contract_units[contract] += amount + token_ids[contract].add(str(item["identifier"])) + if len(contract_units) != 1: + bundle_status = "MULTI_CONTRACT_BUNDLE" + else: + bundle_status = "SINGLE_CONTRACT" + + currency_offer = [item for item in event["offer"] if item["item_type"] in (0, 1)] + currency_consideration = [item for item in event["consideration"] if item["item_type"] in (0, 1)] + currency_items = currency_consideration if direction == "LISTING" else currency_offer if direction == "OFFER" else [] + currency_tokens = {NATIVE if item["item_type"] == 0 else item["token"].lower() for item in currency_items} + payment_token = next(iter(currency_tokens)) if len(currency_tokens) == 1 else None + gross = sum(item["amount"] for item in currency_items) if payment_token else None + + tx_transfers = transfers_by_tx.get(event["transaction_hash"], []) + for contract, units in contract_units.items(): + relevant = [row for row in tx_transfers if row["contract_address"] == contract and row["from_address"] != ZERO and row["to_address"] != ZERO] + sellers = {row["from_address"] for row in relevant} + buyers = {row["to_address"] for row in relevant} + seller = next(iter(sellers)) if len(sellers) == 1 else (event["offerer"] if direction == "LISTING" else None) + buyer = next(iter(buyers)) if len(buyers) == 1 else (event["offerer"] if direction == "OFFER" else event["recipient"] or event["tx_from"]) + seller_net = None + if seller and direction == "LISTING" and payment_token: + seller_items = [item for item in currency_consideration if item.get("recipient", "").lower() == seller and (NATIVE if item["item_type"] == 0 else item["token"].lower()) == payment_token] + if seller_items: + seller_net = sum(item["amount"] for item in seller_items) + price_per_unit = (gross / units) if gross is not None and units else None + sales.append( + { + **{key: event[key] for key in ("order_hash", "block_number", "block_hash", "transaction_hash", "log_index", "timestamp_unix", "tx_from")}, + "direction": direction, + "bundle_status": bundle_status, + "nft_contract": contract, + "nft_units": units, + "token_ids": sorted(token_ids[contract], key=lambda value: int(value)), + "seller": seller, + "buyer": buyer, + "payment_token": payment_token, + "gross_payment_raw": gross, + "seller_net_raw": seller_net, + "price_per_unit_raw": price_per_unit, + "is_native_or_weth": payment_token == NATIVE or payment_token in KNOWN_WETH, + "independent_party": bool(seller and buyer and seller != buyer), + "transfer_rows_matched": len(relevant), + "offer_json": event["offer"], + "consideration_json": event["consideration"], + } + ) + return sales + + +def project_tables(seadrop: list[dict[str, Any]], transfers: list[dict[str, Any]], sales: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]: + by_contract: dict[str, list[dict[str, Any]]] = defaultdict(list) + for row in seadrop: + by_contract[row["nft_contract"]].append(row) + zero_mints_by_contract: dict[str, list[dict[str, Any]]] = defaultdict(list) + for row in transfers: + if row["from_address"] == ZERO: + zero_mints_by_contract[row["contract_address"]].append(row) + sales_by_contract: dict[str, list[dict[str, Any]]] = defaultdict(list) + for row in sales: + if row["is_native_or_weth"] and row["independent_party"] and row["gross_payment_raw"] is not None: + sales_by_contract[row["nft_contract"]].append(row) + + opportunities: list[dict[str, Any]] = [] + outcomes: list[dict[str, Any]] = [] + entries: list[dict[str, Any]] = [] + milestones: list[dict[str, Any]] = [] + for contract, events in sorted(by_contract.items()): + public_paid = sorted([row for row in events if row["drop_stage_index"] == 0 and row["unit_mint_price_wei"] > 0 and row["receipt_status"] == 1], key=lambda row: (row["block_number"], row["log_index"])) + if not public_paid: + continue + first = public_paid[0] + start_time = first["timestamp_unix"] + prices = sorted({row["unit_mint_price_wei"] for row in public_paid}) + zero_seadrop_before = [row for row in events if row["unit_mint_price_wei"] == 0 and (row["block_number"], row["log_index"]) < (first["block_number"], first["log_index"])] + zero_transfer_before = [row for row in zero_mints_by_contract.get(contract, []) if (row["block_number"], row["log_index"]) < (first["block_number"], first["log_index"])] + total_paid_quantity = sum(row["quantity"] for row in public_paid) + project_status = "STRICT_PAID_PUBLIC_FROM_START" if not zero_seadrop_before and not zero_transfer_before and len(prices) == 1 else "PAID_PUBLIC_WITH_COST_BASIS_DIVERGENCE" + opportunity = { + "project_entity_id": f"contract:{contract}", + "nft_contract": contract, + "primary_route": "SEADROP_PUBLIC", + "project_status": project_status, + "first_paid_public_block": first["block_number"], + "first_paid_public_time": start_time, + "initial_mint_price_wei": first["unit_mint_price_wei"], + "observed_paid_public_prices_wei": prices, + "paid_public_event_count": len(public_paid), + "paid_public_quantity": total_paid_quantity, + "zero_seadrop_events_before_paid": len(zero_seadrop_before), + "zero_transfer_items_before_paid": len(zero_transfer_before), + } + opportunities.append(opportunity) + + project_sales = sorted([row for row in sales_by_contract.get(contract, []) if row["timestamp_unix"] >= start_time], key=lambda row: (row["timestamp_unix"], row["block_number"], row["log_index"])) + milestone = {"project_entity_id": opportunity["project_entity_id"], "nft_contract": contract} + for multiple in (1.0, 1.15, 1.5): + qualifying = [row for row in project_sales if row["price_per_unit_raw"] is not None and row["price_per_unit_raw"] >= first["unit_mint_price_wei"] * multiple] + milestone[f"first_{str(multiple).replace('.', '_')}x_sale_time"] = qualifying[0]["timestamp_unix"] if qualifying else None + milestones.append(milestone) + + outcome = {**opportunity} + for window_name, seconds in WINDOWS.items(): + window_sales = [row for row in project_sales if row["timestamp_unix"] <= start_time + seconds and row["bundle_status"] == "SINGLE_CONTRACT"] + order_count = len({(row["transaction_hash"], row["log_index"], row["order_hash"]) for row in window_sales}) + buyers = {row["buyer"] for row in window_sales if row["buyer"]} + prices_per_unit = [float(row["price_per_unit_raw"]) for row in window_sales if row["price_per_unit_raw"] is not None] + outcome[f"orders_{window_name}"] = order_count + outcome[f"buyers_{window_name}"] = len(buyers) + outcome[f"sale_units_{window_name}"] = sum(row["nft_units"] for row in window_sales) + outcome[f"max_multiple_{window_name}"] = (max(prices_per_unit) / first["unit_mint_price_wei"]) if prices_per_unit else None + outcome[f"median_multiple_{window_name}"] = (statistics.median(prices_per_unit) / first["unit_mint_price_wei"]) if prices_per_unit else None + outcome["liquid_100_24h"] = int((outcome["max_multiple_24h"] or 0) >= 1.0 and outcome["orders_24h"] >= 2 and outcome["buyers_24h"] >= 2) + outcome["liquid_115_24h"] = int((outcome["max_multiple_24h"] or 0) >= 1.15 and outcome["orders_24h"] >= 2 and outcome["buyers_24h"] >= 2) + outcome["liquid_150_24h"] = int((outcome["max_multiple_24h"] or 0) >= 1.5 and outcome["orders_24h"] >= 2 and outcome["buyers_24h"] >= 2) + outcomes.append(outcome) + + cumulative = 0 + first_profit_time = milestone.get("first_1_0x_sale_time") + for event in public_paid: + quantile = cumulative / total_paid_quantity if total_paid_quantity else None + cumulative += event["quantity"] + entries.append( + { + "project_entity_id": opportunity["project_entity_id"], + "nft_contract": contract, + "wallet": event["minter"], + "payer": event["payer"], + "tx_from": event["tx_from"], + "economic_role": "SELF_FUNDED" if event["payer"] == event["minter"] else "ROUTED_OR_SPONSORED", + "block_number": event["block_number"], + "transaction_hash": event["transaction_hash"], + "log_index": event["log_index"], + "timestamp_unix": event["timestamp_unix"], + "quantity": event["quantity"], + "unit_mint_price_wei": event["unit_mint_price_wei"], + "entry_quantity_quantile": quantile, + "early_20pct": bool(quantile is not None and quantile < 0.20), + "before_first_nonloss_sale": bool(first_profit_time is None or event["timestamp_unix"] < first_profit_time), + "entry_gas_cost_wei": event["entry_gas_cost_wei"], + "project_status": project_status, + } + ) + return opportunities, outcomes, entries, milestones + + +def wallet_selection_metrics(opportunities: list[dict[str, Any]], outcomes: list[dict[str, Any]], entries: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + opportunity_by_id = {row["project_entity_id"]: row for row in opportunities} + outcome_by_id = {row["project_entity_id"]: row for row in outcomes} + eligible_entries = [row for row in entries if row["economic_role"] == "SELF_FUNDED" and row["early_20pct"] and row["before_first_nonloss_sale"]] + wallet_projects: dict[str, set[str]] = defaultdict(set) + entry_rows: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list) + for row in eligible_entries: + wallet_projects[row["wallet"]].add(row["project_entity_id"]) + entry_rows[(row["wallet"], row["project_entity_id"])].append(row) + + matched_pairs: list[dict[str, Any]] = [] + metrics: list[dict[str, Any]] = [] + for wallet, entered in sorted(wallet_projects.items()): + matched_by_label: dict[str, list[int]] = defaultdict(list) + entered_labels: dict[str, list[int]] = defaultdict(list) + for project_id in sorted(entered): + project = opportunity_by_id[project_id] + outcome = outcome_by_id[project_id] + price = project["initial_mint_price_wei"] + start = project["first_paid_public_time"] + controls = [] + for candidate in opportunities: + candidate_id = candidate["project_entity_id"] + if candidate_id in entered: + continue + candidate_price = candidate["initial_mint_price_wei"] + if not candidate_price or not price: + continue + if abs(candidate["first_paid_public_time"] - start) > 7 * 86400: + continue + ratio = candidate_price / price + if not (0.5 <= ratio <= 2.0): + continue + if candidate["primary_route"] != project["primary_route"]: + continue + controls.append(candidate_id) + for label in LABELS: + entered_labels[label].append(int(outcome[label])) + control_values = [int(outcome_by_id[candidate_id][label]) for candidate_id in controls] + matched_by_label[label].extend(control_values) + matched_pairs.append( + { + "wallet": wallet, + "entered_project": project_id, + "label": label, + "entered_outcome": int(outcome[label]), + "matched_control_projects": controls, + "matched_control_count": len(controls), + "matched_control_success_rate": (sum(control_values) / len(control_values)) if control_values else None, + } + ) + row: dict[str, Any] = { + "wallet": wallet, + "entered_projects": len(entered), + "earliest_entry_time": min(min(value["timestamp_unix"] for value in entry_rows[(wallet, project_id)]) for project_id in entered), + "median_entry_quantile": statistics.median(min(value["entry_quantity_quantile"] for value in entry_rows[(wallet, project_id)]) for project_id in entered), + "production_approved": False, + "decision_use": "RESEARCH_SELECTION_SIGNAL_ONLY", + } + positive_lifts = 0 + for label in LABELS: + values = entered_labels[label] + controls = matched_by_label[label] + hit_rate = sum(values) / len(values) if values else None + baseline = sum(controls) / len(controls) if controls else None + lift = hit_rate - baseline if hit_rate is not None and baseline is not None else None + row[f"{label}_hit_rate"] = hit_rate + row[f"{label}_matched_baseline"] = baseline + row[f"{label}_predictive_lift"] = lift + row[f"{label}_matched_control_observations"] = len(controls) + if lift is not None and lift > 0: + positive_lifts += 1 + if len(entered) < 3: + classification = "LOW_SAMPLE_NOT_EVALUATED" + elif positive_lifts == len(LABELS): + classification = "PROVISIONAL_POSITIVE_SELECTION_SIGNAL" + elif positive_lifts: + classification = "MIXED_SELECTION_SIGNAL" + else: + classification = "NO_POSITIVE_SELECTION_LIFT" + row["selection_classification"] = classification + metrics.append(row) + return metrics, matched_pairs + + +def copy_proxy(entries: list[dict[str, Any]], seadrop: list[dict[str, Any]], sales: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + by_contract = defaultdict(list) + for row in seadrop: + if row["drop_stage_index"] == 0 and row["unit_mint_price_wei"] > 0 and row["payer"] == row["minter"] and row["receipt_status"] == 1: + by_contract[row["nft_contract"]].append(row) + sales_by_contract = defaultdict(list) + for sale in sales: + if sale["is_native_or_weth"] and sale["independent_party"] and sale["price_per_unit_raw"] is not None: + sales_by_contract[sale["nft_contract"]].append(sale) + rows: list[dict[str, Any]] = [] + for signal in entries: + if not (signal["economic_role"] == "SELF_FUNDED" and signal["early_20pct"] and signal["before_first_nonloss_sale"]): + continue + candidates = sorted(by_contract.get(signal["nft_contract"], []), key=lambda row: (row["timestamp_unix"], row["block_number"], row["log_index"])) + for delay_name, min_block_delta, min_seconds in (("1block", 1, 0), ("30s", 0, 30), ("60s", 0, 60)): + later = [row for row in candidates if row["minter"] != signal["wallet"] and row["unit_mint_price_wei"] == signal["unit_mint_price_wei"] and row["block_number"] >= signal["block_number"] + min_block_delta and row["timestamp_unix"] >= signal["timestamp_unix"] + min_seconds] + proxy = later[0] if later else None + exit_sales = [] + if proxy: + exit_sales = [sale for sale in sales_by_contract.get(signal["nft_contract"], []) if proxy["timestamp_unix"] <= sale["timestamp_unix"] <= proxy["timestamp_unix"] + 24 * 3600 and sale["bundle_status"] == "SINGLE_CONTRACT"] + best_exit = max((sale["price_per_unit_raw"] for sale in exit_sales), default=None) + entry_total = None + if proxy: + entry_total = proxy["unit_mint_price_wei"] + (proxy["entry_gas_cost_wei"] / max(proxy["quantity"], 1)) + rows.append( + { + "signal_wallet": signal["wallet"], + "project_entity_id": signal["project_entity_id"], + "nft_contract": signal["nft_contract"], + "signal_transaction_hash": signal["transaction_hash"], + "delay": delay_name, + "proxy_entry_available": bool(proxy), + "proxy_entry_wallet": proxy["minter"] if proxy else None, + "proxy_entry_transaction_hash": proxy["transaction_hash"] if proxy else None, + "proxy_entry_time": proxy["timestamp_unix"] if proxy else None, + "proxy_entry_price_wei": proxy["unit_mint_price_wei"] if proxy else None, + "proxy_entry_gas_per_item_wei": (proxy["entry_gas_cost_wei"] / max(proxy["quantity"], 1)) if proxy else None, + "proxy_total_entry_cost_wei": entry_total, + "best_observed_sale_price_per_unit_24h": best_exit, + "market_exit_nonloss_proxy": bool(entry_total is not None and best_exit is not None and best_exit >= entry_total), + "status": "MARKET_EXIT_PROXY_ONLY_NOT_EXECUTABLE_BID", + } + ) + aggregate: list[dict[str, Any]] = [] + grouped: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list) + for row in rows: + grouped[(row["signal_wallet"], row["delay"])].append(row) + for (wallet, delay), values in sorted(grouped.items()): + available = [row for row in values if row["proxy_entry_available"]] + aggregate.append( + { + "wallet": wallet, + "delay": delay, + "signals": len(values), + "proxy_entries_available": len(available), + "proxy_entry_availability_rate": len(available) / len(values) if values else None, + "market_exit_nonloss_proxy_count": sum(row["market_exit_nonloss_proxy"] for row in available), + "market_exit_nonloss_proxy_rate": sum(row["market_exit_nonloss_proxy"] for row in available) / len(available) if available else None, + "status": "MARKET_EXIT_PROXY_ONLY_NOT_COPY_ALPHA", + "production_approved": False, + } + ) + return rows, aggregate + + +def execution_research(seadrop: list[dict[str, Any]], transfers: list[dict[str, Any]], sales: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + seadrop_by_tx_contract = {(row["transaction_hash"], row["nft_contract"]): row for row in seadrop} + token_events: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list) + for row in transfers: + if row["standard"] == "ERC-721" and int(row["amount"]) == 1: + token_events[(row["contract_address"], row["token_id"])].append(row) + sale_by_token: dict[tuple[str, str, str], dict[str, Any]] = {} + for sale in sales: + if sale["bundle_status"] != "SINGLE_CONTRACT" or sale["nft_units"] <= 0 or sale["seller_net_raw"] is None: + continue + unit_net = sale["seller_net_raw"] / sale["nft_units"] + unit_gross = sale["gross_payment_raw"] / sale["nft_units"] if sale["gross_payment_raw"] is not None else None + for token_id in sale["token_ids"]: + sale_by_token[(sale["nft_contract"], token_id, sale["transaction_hash"])] = {**sale, "unit_seller_net": unit_net, "unit_gross": unit_gross} + + realized: list[dict[str, Any]] = [] + for (contract, token_id), events in token_events.items(): + events.sort(key=lambda row: (row["block_number"], row["transaction_index"], row["log_index"], row.get("batch_item_index", 0))) + owner = None + cost_basis = None + acquisition_type = None + acquisition_tx = None + acquisition_gas = None + for event in events: + frm = event["from_address"] + to = event["to_address"] + if frm == ZERO: + owner = to + mint = seadrop_by_tx_contract.get((event["transaction_hash"], contract)) + if mint and mint["minter"] == to and mint["payer"] == to and mint["quantity"] > 0: + cost_basis = mint["unit_mint_price_wei"] + acquisition_gas = mint["entry_gas_cost_wei"] / mint["quantity"] + acquisition_type = "SELF_FUNDED_SEADROP_MINT" + acquisition_tx = event["transaction_hash"] + else: + cost_basis = None + acquisition_gas = None + acquisition_type = "MINT_COST_UNRESOLVED" + acquisition_tx = event["transaction_hash"] + continue + sale = sale_by_token.get((contract, token_id, event["transaction_hash"])) + if sale and owner == frm and sale["seller"] == frm and sale["buyer"] == to: + exact = cost_basis is not None and acquisition_gas is not None + net_pnl = sale["unit_seller_net"] - cost_basis - acquisition_gas if exact else None + realized.append( + { + "wallet": frm, + "nft_contract": contract, + "token_id": token_id, + "acquisition_type": acquisition_type, + "acquisition_transaction_hash": acquisition_tx, + "sale_transaction_hash": sale["transaction_hash"], + "sale_order_hash": sale["order_hash"], + "acquisition_cost_wei": cost_basis, + "entry_gas_allocated_wei": acquisition_gas, + "seller_net_wei": sale["unit_seller_net"], + "exit_gas_allocated_wei": 0, + "net_realized_pnl_wei": net_pnl, + "pnl_status": "PROVEN" if exact else "ACQUISITION_COST_UNRESOLVED", + "sale_time": sale["timestamp_unix"], + } + ) + owner = to + cost_basis = sale["unit_gross"] + acquisition_gas = None + acquisition_type = "SECONDARY_PURCHASE_GAS_UNRESOLVED" + acquisition_tx = sale["transaction_hash"] + else: + owner = to + cost_basis = None + acquisition_gas = None + acquisition_type = "TRANSFERRED_COST_UNRESOLVED" + acquisition_tx = event["transaction_hash"] + + wallet_rows: list[dict[str, Any]] = [] + grouped: dict[str, list[dict[str, Any]]] = defaultdict(list) + for row in realized: + grouped[row["wallet"]].append(row) + for wallet, rows in sorted(grouped.items()): + proven = [row for row in rows if row["pnl_status"] == "PROVEN"] + pnls = [float(row["net_realized_pnl_wei"]) for row in proven] + wallet_rows.append( + { + "wallet": wallet, + "observed_sale_lots": len(rows), + "proven_realized_sale_lots": len(proven), + "proven_win_rate": sum(value > 0 for value in pnls) / len(pnls) if pnls else None, + "median_realized_pnl_wei": statistics.median(pnls) if pnls else None, + "total_realized_pnl_wei": sum(pnls) if pnls else None, + "execution_classification": "PROVISIONAL_EXECUTION_DATA" if proven else "EXECUTION_NOT_EVALUATED", + "production_approved": False, + } + ) + return realized, wallet_rows + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--chain-history", type=Path, required=True) + parser.add_argument("--nft-population", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + args.out.mkdir(parents=True, exist_ok=True) + + chain_validation = json.loads((args.chain_history / "VALIDATION.json").read_text(encoding="utf-8")) + nft_validation = json.loads((args.nft_population / "VALIDATION.json").read_text(encoding="utf-8")) + if chain_validation.get("status") != "PASS" or nft_validation.get("status") != "PASS": + raise SystemExit("upstream canonical data is not PASS") + + raw_seadrop = read_jsonl_gz(args.chain_history / "seadrop" / "seadrop_logs.jsonl.gz") + raw_seaport = read_jsonl_gz(args.chain_history / "seaport" / "seaport_logs.jsonl.gz") + transfers = read_jsonl_gz(args.nft_population / "all_transfers.jsonl.gz") + raw_context_logs = raw_seadrop + raw_seaport + blocks, txs, receipts, context_errors = fetch_chain_context(raw_context_logs) + + seadrop: list[dict[str, Any]] = [] + seadrop_errors: list[dict[str, Any]] = [] + for row in raw_seadrop: + try: + seadrop.append(decode_seadrop(row, blocks, txs, receipts)) + except Exception as exc: # noqa: BLE001 + seadrop_errors.append({"transaction_hash": row.get("transactionHash"), "log_index": row.get("logIndex"), "error": repr(exc)}) + orders: list[dict[str, Any]] = [] + order_errors: list[dict[str, Any]] = [] + for row in raw_seaport: + try: + orders.append(decode_order_fulfilled(row, blocks, txs, receipts)) + except Exception as exc: # noqa: BLE001 + order_errors.append({"transaction_hash": row.get("transactionHash"), "log_index": row.get("logIndex"), "error": repr(exc)}) + + sales = sale_rows(orders, transfers) + opportunities, outcomes, entries, milestones = project_tables(seadrop, transfers, sales) + selection, matched_pairs = wallet_selection_metrics(opportunities, outcomes, entries) + copy_rows, copy_wallets = copy_proxy(entries, seadrop, sales) + realized, execution_wallets = execution_research(seadrop, transfers, sales) + + write_jsonl(args.out / "seadrop_mints.jsonl", seadrop) + write_jsonl(args.out / "seaport_orders.jsonl", orders) + write_csv(args.out / "seaport_sales.csv", sales) + write_csv(args.out / "project_opportunities.csv", opportunities) + write_csv(args.out / "project_outcomes.csv", outcomes) + write_csv(args.out / "wallet_project_entries.csv", entries) + write_csv(args.out / "project_sale_milestones.csv", milestones) + write_csv(args.out / "wallet_selection_alpha.csv", selection) + write_jsonl(args.out / "matched_baseline_pairs.jsonl", matched_pairs) + write_csv(args.out / "copy_market_exit_proxy_events.csv", copy_rows) + write_csv(args.out / "wallet_copy_market_exit_proxy.csv", copy_wallets) + write_csv(args.out / "realized_token_sales.csv", realized) + write_csv(args.out / "wallet_execution_alpha.csv", execution_wallets) + (args.out / "chain_context_errors.json").write_text(json.dumps(context_errors, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + (args.out / "seadrop_decode_errors.json").write_text(json.dumps(seadrop_errors, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + (args.out / "seaport_decode_errors.json").write_text(json.dumps(order_errors, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + + failures = [] + if context_errors: + failures.append({"code": "CHAIN_CONTEXT_ERRORS", "count": len(context_errors)}) + if seadrop_errors or len(seadrop) != len(raw_seadrop): + failures.append({"code": "SEADROP_DECODE_INCOMPLETE", "raw": len(raw_seadrop), "decoded": len(seadrop), "errors": len(seadrop_errors)}) + if order_errors or len(orders) != len(raw_seaport): + failures.append({"code": "SEAPORT_DECODE_INCOMPLETE", "raw": len(raw_seaport), "decoded": len(orders), "errors": len(order_errors)}) + if not opportunities or len(outcomes) != len(opportunities): + failures.append({"code": "PROJECT_UNIVERSE_INCOMPLETE", "opportunities": len(opportunities), "outcomes": len(outcomes)}) + validation = { + "status": "PASS" if not failures else "FAIL", + "raw_seadrop_logs": len(raw_seadrop), + "decoded_seadrop_mints": len(seadrop), + "raw_seaport_logs": len(raw_seaport), + "decoded_seaport_orders": len(orders), + "sale_rows": len(sales), + "project_opportunities": len(opportunities), + "project_outcomes": len(outcomes), + "wallet_project_entries": len(entries), + "wallet_selection_rows": len(selection), + "copy_proxy_event_rows": len(copy_rows), + "execution_sale_lots": len(realized), + "execution_wallet_rows": len(execution_wallets), + "failures": failures, + "production_approved_wallets": 0, + "live_mint_decision_ready": False, + } + (args.out / "VALIDATION.json").write_text(json.dumps(validation, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + manifest = [] + for path in sorted(args.out.iterdir()): + if path.is_file(): + manifest.append({"path": path.name, "bytes": path.stat().st_size, "sha256": sha256(path)}) + (args.out / "MANIFEST.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + print(json.dumps(validation, sort_keys=True)) + if validation["status"] != "PASS": + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/wallet-verification-results-trigger/p1.txt b/wallet-verification-results-trigger/p1.txt new file mode 100644 index 0000000..98aba0f --- /dev/null +++ b/wallet-verification-results-trigger/p1.txt @@ -0,0 +1 @@ +Persist artifacts from source workflow run 33167365485. diff --git a/wallet-verification-results-trigger/p2.txt b/wallet-verification-results-trigger/p2.txt new file mode 100644 index 0000000..26f1f34 --- /dev/null +++ b/wallet-verification-results-trigger/p2.txt @@ -0,0 +1 @@ +Persist the latest completed 16-shard P2 wallet verification run. diff --git a/wallet-verification-results/p2/RUN.json b/wallet-verification-results/p2/RUN.json new file mode 100644 index 0000000..c62e689 --- /dev/null +++ b/wallet-verification-results/p2/RUN.json @@ -0,0 +1,26 @@ +{ + "available_artifacts": [], + "missing_artifacts": [ + "rhc-wallet-verification-p2-0", + "rhc-wallet-verification-p2-1", + "rhc-wallet-verification-p2-10", + "rhc-wallet-verification-p2-11", + "rhc-wallet-verification-p2-12", + "rhc-wallet-verification-p2-13", + "rhc-wallet-verification-p2-14", + "rhc-wallet-verification-p2-15", + "rhc-wallet-verification-p2-2", + "rhc-wallet-verification-p2-3", + "rhc-wallet-verification-p2-4", + "rhc-wallet-verification-p2-5", + "rhc-wallet-verification-p2-6", + "rhc-wallet-verification-p2-7", + "rhc-wallet-verification-p2-8", + "rhc-wallet-verification-p2-9" + ], + "production_approved_wallets": 0, + "reason": "MISSING_P2_SHARD_ARTIFACTS", + "source_conclusion": "action_required", + "source_run_id": 33256018240, + "status": "FAIL" +} \ No newline at end of file diff --git a/wallet-verification/collector.py.gz.b64 b/wallet-verification/collector.py.gz.b64 new file mode 100644 index 0000000..503fbf6 --- /dev/null +++ b/wallet-verification/collector.py.gz.b64 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/wallet-verification/consolidate_previous_run.py b/wallet-verification/consolidate_previous_run.py new file mode 100644 index 0000000..227ba8d --- /dev/null +++ b/wallet-verification/consolidate_previous_run.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import csv +import hashlib +import io +import json +import os +import re +import shutil +import sys +import urllib.error +import urllib.request +import zipfile +from pathlib import Path +from typing import Any + +REPO = os.environ.get("GITHUB_REPOSITORY", "dcftradingcrypt/chart") +RUN_ID = int(os.environ.get("SOURCE_RUN_ID", "33167365485")) +TOKEN = os.environ.get("GITHUB_TOKEN", "") +OUT = Path(os.environ.get("OUT_DIR", "out-p0p1-consolidated")) +DOWNLOADS = OUT / "artifacts" +REQUIRED = { + "rhc-wallet-verification-p0", + "rhc-wallet-verification-p1a", + "rhc-wallet-verification-p1b", + "rhc-wallet-verification-p1c", + "rhc-wallet-verification-p1d", +} + + +def request_json(url: str) -> Any: + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": "rhc-wallet-verification-consolidator/1.0", + "X-GitHub-Api-Version": "2022-11-28", + } + if TOKEN: + headers["Authorization"] = f"Bearer {TOKEN}" + req = urllib.request.Request(url, headers=headers) + with urllib.request.urlopen(req, timeout=90) as response: + return json.loads(response.read().decode("utf-8")) + + +def request_bytes(url: str) -> bytes: + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": "rhc-wallet-verification-consolidator/1.0", + "X-GitHub-Api-Version": "2022-11-28", + } + if TOKEN: + headers["Authorization"] = f"Bearer {TOKEN}" + req = urllib.request.Request(url, headers=headers) + with urllib.request.urlopen(req, timeout=180) as response: + return response.read() + + +def safe_extract(data: bytes, destination: Path) -> None: + destination.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(io.BytesIO(data)) as archive: + root = destination.resolve() + for member in archive.infolist(): + target = (destination / member.filename).resolve() + if root not in target.parents and target != root: + raise RuntimeError(f"unsafe zip member: {member.filename}") + archive.extractall(destination) + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def count_csv(path: Path) -> int: + with path.open(newline="", encoding="utf-8-sig", errors="replace") as handle: + return sum(1 for _ in csv.DictReader(handle)) + + +def count_jsonl(path: Path) -> int: + with path.open(encoding="utf-8", errors="replace") as handle: + return sum(1 for line in handle if line.strip()) + + +def summarize_artifact(name: str, root: Path, metadata: dict[str, Any]) -> dict[str, Any]: + files = sorted(path for path in root.rglob("*") if path.is_file()) + validations = [] + csv_counts: dict[str, int] = {} + jsonl_counts: dict[str, int] = {} + nonempty_error_files = [] + for path in files: + rel = str(path.relative_to(root)).replace("\\", "/") + lower = path.name.lower() + if lower in {"validation.json", "evidence_gate.json", "final_validation.json"}: + try: + validations.append({"path": rel, "value": json.loads(path.read_text(encoding="utf-8"))}) + except Exception as exc: + validations.append({"path": rel, "parse_error": repr(exc)}) + if path.suffix.lower() == ".csv": + try: + csv_counts[rel] = count_csv(path) + except Exception: + csv_counts[rel] = -1 + elif path.suffix.lower() == ".jsonl": + try: + jsonl_counts[rel] = count_jsonl(path) + except Exception: + jsonl_counts[rel] = -1 + if "error" in lower and path.stat().st_size > 0: + if path.suffix.lower() == ".csv": + rows = csv_counts.get(rel, -1) + if rows != 0: + nonempty_error_files.append({"path": rel, "rows": rows}) + else: + nonempty_error_files.append({"path": rel, "bytes": path.stat().st_size}) + validation_statuses = [] + for item in validations: + value = item.get("value") + if isinstance(value, dict): + validation_statuses.append(str(value.get("status", "MISSING_STATUS"))) + else: + validation_statuses.append("UNPARSEABLE") + return { + "artifact": name, + "artifact_id": metadata.get("id"), + "artifact_size_bytes": metadata.get("size_in_bytes"), + "artifact_digest": metadata.get("digest"), + "file_count": len(files), + "total_uncompressed_bytes": sum(path.stat().st_size for path in files), + "validations": validations, + "validation_statuses": validation_statuses, + "csv_row_counts": csv_counts, + "jsonl_row_counts": jsonl_counts, + "nonempty_error_files": nonempty_error_files, + "file_manifest": [ + { + "path": str(path.relative_to(root)).replace("\\", "/"), + "bytes": path.stat().st_size, + "sha256": sha256(path), + } + for path in files + ], + } + + +def main() -> int: + OUT.mkdir(parents=True, exist_ok=True) + DOWNLOADS.mkdir(parents=True, exist_ok=True) + url = f"https://api.github.com/repos/{REPO}/actions/runs/{RUN_ID}/artifacts?per_page=100" + report: dict[str, Any] = { + "source_repository": REPO, + "source_run_id": RUN_ID, + "required_artifacts": sorted(REQUIRED), + "artifacts": [], + "missing_artifacts": [], + "download_errors": [], + "status": "FAIL", + } + try: + payload = request_json(url) + except Exception as exc: + report["metadata_error"] = repr(exc) + (OUT / "consolidated_status.json").write_text(json.dumps(report, indent=2, sort_keys=True), encoding="utf-8") + return 1 + + available = { + item.get("name"): item + for item in payload.get("artifacts", []) + if isinstance(item, dict) and not item.get("expired") + } + report["available_artifacts"] = sorted(available) + report["missing_artifacts"] = sorted(REQUIRED - set(available)) + + for name in sorted(REQUIRED & set(available)): + item = available[name] + try: + data = request_bytes(item["archive_download_url"]) + zip_path = DOWNLOADS / f"{name}.zip" + zip_path.write_bytes(data) + extract_root = DOWNLOADS / name + safe_extract(data, extract_root) + summary = summarize_artifact(name, extract_root, item) + summary["download_zip_sha256"] = hashlib.sha256(data).hexdigest() + summary["download_zip_bytes"] = len(data) + report["artifacts"].append(summary) + except Exception as exc: + report["download_errors"].append({"artifact": name, "error": repr(exc)}) + + artifact_names = {row["artifact"] for row in report["artifacts"]} + validation_failures = [] + for row in report["artifacts"]: + if not row["validation_statuses"]: + validation_failures.append({"artifact": row["artifact"], "reason": "NO_VALIDATION_FILE"}) + for status in row["validation_statuses"]: + if status not in {"PASS", "PASS_WITH_WARNINGS", "PARTIAL"}: + validation_failures.append({"artifact": row["artifact"], "reason": f"VALIDATION_{status}"}) + report["validation_failures"] = validation_failures + report["all_required_downloaded"] = artifact_names == REQUIRED + report["status"] = ( + "PASS" + if not report["missing_artifacts"] + and not report["download_errors"] + and not validation_failures + else "FAIL" + ) + + status_path = OUT / "consolidated_status.json" + status_path.write_text(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8") + + manifest_rows = [] + for path in sorted(p for p in OUT.rglob("*") if p.is_file() and p.name != "MANIFEST.json"): + manifest_rows.append({ + "path": str(path.relative_to(OUT)).replace("\\", "/"), + "bytes": path.stat().st_size, + "sha256": sha256(path), + }) + (OUT / "MANIFEST.json").write_text(json.dumps(manifest_rows, indent=2, sort_keys=True), encoding="utf-8") + + archive_path = Path("rhc-wallet-verification-p0p1-consolidated.zip") + with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive: + for path in sorted(p for p in OUT.rglob("*") if p.is_file()): + archive.write(path, arcname=str(path.relative_to(OUT)).replace("\\", "/")) + print(json.dumps({ + "status": report["status"], + "downloaded": sorted(artifact_names), + "missing": report["missing_artifacts"], + "download_errors": report["download_errors"], + "validation_failures": validation_failures, + "archive": str(archive_path), + "archive_sha256": sha256(archive_path), + }, sort_keys=True)) + return 0 if report["status"] == "PASS" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/wallet-verification/wallets.json.gz.b64 b/wallet-verification/wallets.json.gz.b64 new file mode 100644 index 0000000..42b3abf --- /dev/null +++ b/wallet-verification/wallets.json.gz.b64 @@ -0,0 +1 @@ +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 \ No newline at end of file