Skip to content

Latest commit

Β 

History

104 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Spark

Spark

Pay once. Unlock credit.

Verified Sepolia payment history is your credit score β€” no oracle.

license network attestcoin ui contracts status live

spark.sithunyein.com Β· Help Β· GitHub Β· MIT License

Attestcoin Protocol integration at a glance

15 surfaces Β· 3 attested event kinds Β· 5 on-chain entry points. Ten surfaces sit on the critical path β€” remove any one and the product stops working. The other five are integrated and tested off the payment path. The split is stated rather than blurred, and every row names the code and the test that exercises it, so the claim is checkable rather than asserted.

# Surface Where Exercised by
1 verifyAndEmit (0x0FD2) AttestcoinPaymentVerifier._proveOnChain() bash run-negative-paths.sh β€” 8 forged proofs rejected by the live precompile, read-only, zero cost
2 MerkleProof construction AttestcoinPaymentVerifier._proveOnChain() same, plus every open on both chains
3 ContinuityProof construction AttestcoinPaymentVerifier._proveOnChain() same
4 Receipt RLP parsing _parseReceiptLogs() VerifierStrict.t.sol β€” testStrictPathParsesReceiptLogs, testStrictPathParsesLongFormBloom
5 Topic matching _verifyLogStrict() VerifierStrict.t.sol β€” testStrictPathMultipleLogsFindsCorrectOne
6 Payer validation _verifyLogStrict() VerifierStrict.t.sol β€” testStrictPathRejectsWrongPayer; Spark.t.sol β€” testWrongPayerReverts, testNegativePath_TamperedPayerRejected
7 Amount binding _verifyLogStrict() VerifierStrict.t.sol β€” testStrictPathAcceptsMatchingAmount, testStrictPathRevertsOnAmountMismatch
8 ProofBuilder app/src/lib/usc.ts the live opens in this README β€” off-chain by nature, not unit-tested
9 waitUntilHeightAttested app/src/lib/usc.ts (Promise.all) as above β€” one attestation window, not two sequential
10 getProof app/src/lib/usc.ts as above
11 ChainInfo (0x0FD3) verifier + usc.ts live cast call against CC3; no automated test
12 previewIngest usc.ts dry run no automated test β€” Spark.t.sol:1135 records that it needs the live precompile
13 executeBatch submitAttestMultiple Spark.t.sol β€” testExecuteBatchSuccess, testExecuteBatchEmptyReverts, testExecuteBatchLengthMismatchReverts, testExecuteBatchReplayReverts
14 calculateTxIndex verifier, exposed mocked in VerifierStrict.t.sol; no live test
15 getBatchProof app/src/lib/usc.ts SDK path only

Attested event kinds: kind 1 DepositPaid Β· kind 2 RepaymentPaid Β· kind 3 BalanceAttested β€” the solvency check, which is what makes wash lending impossible by construction rather than by a scoring rule.

On-chain entry points: openCredit (two proofs) Β· openCreditFromBalance (one proof, no deposit) Β· repayCredit Β· submitAttestedPayment Β· submitAttestMultiple.

Five rows have no automated test, and they are named rather than hidden: three are off-chain SDK calls and two need the live precompile. The six with a named test are the six that decide whether a payment claim is real.

Where to check any of it: npm run test:contracts (518 tests, 0 failures β€” 417 were the submitted suite) Β· cd contracts && bash run-negative-paths.sh (the live precompile rejecting 8 forged proofs) Β· spark.sithunyein.com/onchain (the whole record, no wallet: 82 events, 5 wallets, 8 lines opened, 6 closed) Β· docs/evidence/position-scale.json (the 40-wallet mainnet reconstruction).

Judge path in 90 seconds

Everything here is reproducible from a clean clone. No wallet, no CTC, no faucet.

1. Contract suite: 518 tests, 0 failures

417 of these are the submitted suite. The other 101 cover AttestedStanding.sol, StandingGatedCheckout.sol and GroupCredit.sol, written after the deadline and marked * below. The submission is frozen; these are additions to the repository, not revisions to what was judged.

npm run test:contracts          # or: cd contracts && forge test

2. The real BlockProver precompile rejecting forged proofs (read-only, zero cost)

cd contracts && bash run-negative-paths.sh

Eight forged proofs (forged merkle root, wrong chain key, zero height, empty encoded transaction, mismatched sibling lengths, large chain key, max uint64 height, random bytes) are rejected by the live 0x0FD2 precompile on CC3 via eth_call.

3. Click the product

spark.sithunyein.com: pay a testnet deposit, watch the dual proof run, withdraw sCREDIT, repay, close.

spark.sithunyein.com/balance is the other generation: no deposit at all. It proves only the Sepolia balance, sizes the line at 20% of it, and carries its own draw, redeem and repay actions. Four wallets that are not mine have now run the entire loop through it β€” connect, attest, open, draw, repay, close β€” each with its own transaction trail:

Wallet Balance attest (Sepolia) Open, kind-3 (Creditcoin) Draw Repay, kind-2 (Sepolia) Β· close (Creditcoin)

| 0x75507D…CAE7 | 0x0a360e92… | 0x404393fc… | 0xf160ffd2… Β· 0.00398 ETH | 0xf8870b20… Β· 0xa165a57f… | | 0x4271A2…ADdb | 0x666e5479… | 0x229d053f… | 0x543afada… Β· 0.0038 ETH | 0xfe7e2af6… + 0x8ef8cf5e… Β· 0x87487800… | | 0x97a8da…876C | 0x7135e977… | 0x199ad666… | 0xef94edf3… Β· 0.0038 ETH | 0x287379a6… Β· 0xdfa72b48… | | 0xEE7BEF…2bac | 0x85fb2dc2… | 0x9182cf67… | 0x0e230506… Β· 0.0038 ETH | 0x0893814a… Β· 0x8a596c3d… |

The first of those was drawn in full β€” 0.00398 ETH against 0.0199 ETH attested β€” and each of the four closed through a proven repayment rather than an admin call. Every hash above is the borrower's own wallet; none is a script run by me.

Six completed credit loops, verifiable on Blockscout

Open (dual proof) Repay + close
Loop 1, Aug 13, 90% LTV 0xe5ec5506... 0x5092e516...
Loop 2, Aug 14, 95% LTV 0xbbec27e6... 0x5fc0b4fb...
Loop 3, Sep 14, deposit-free, drawn in full 0x404393fc... 0xa165a57f...
Loop 4, Sep 14, deposit-free 0x229d053f... 0x87487800...
Loop 5, Sep 14, deposit-free 0x199ad666... 0xdfa72b48...
Loop 6, Sep 15, deposit-free, closed in one repayment round 0x9182cf67... 0x8a596c3d...

On-chain creditScore() = 850, which is the cap: 650 plus 40 per linked payment, and 6 payments are linked, so the score is clamped rather than exactly derived. Artifacts: docs/evidence/README.md Β· Gas benchmarks: docs/evidence/gas.md Β· Deck: deck.pdf

4. The whole on-chain record, with no wallet (read-only, zero cost)

spark.sithunyein.com/onchain renders all 82 events the deployed contracts have emitted, across both chains, oldest first, each row linking to its Blockscout entry. No wallet, no sign-in, nothing to take on trust.

The page leads with its own scope rather than burying it: those events came from 5 distinct wallets, and the funnel it prints reads 1 Β· 5 Β· 5 Β· 5 Β· 5 Β· 5 β€” five wallets reached every stage from the balance attestation onward, with no drop-off between opening a line and closing it. Measured, not asserted: 8 credit lines opened (four with no deposit), 6 closed, 11 attested payments linked, 0.03288 ETH of credit drawn, 6 Sepolia deposits, 10 repayments, 13 balance attestations. Rows are generated from chain reads into a typed module, so the page cannot drift from the chain:

npm run activity:regen          # or: cd app && node scripts/gen-chain-activity.mjs

This exists because the product's own history was invisible: /activity is scoped to the connected address, so a reviewer with a fresh wallet saw an empty product. It should not have worked that way.

5. Reconstruction is measured, not asserted (read-only, zero cost)

cd app && TARGET=40 DISCOVERY_CHUNKS=8 node scripts/position-scale.mjs

Set TARGET. The bare command reproduces a different number. The script defaults to TARGET=8, so running it with no arguments reconstructs 8 wallets and reports 7/7, not the 40-wallet corpus above. DISCOVERY_CHUNKS is what the committed run used alongside it, and the script now prints both its parameters and a warning when the combination cannot reach the requested sample. This is not a fast command: the committed run spent 1,613 archive RPC calls and several minutes, and the public endpoint throttles under repetition. The exact output is committed at docs/evidence/position-scale.json, so the numbers can be checked against a recorded run instead of repeating the wait.

Across 40 real mainnet wallets holding 2 wei to 13,005 aWETH, a token-ledger reconstruction lands within 10 bps of the live balance in 38 of 38 measurable cases (largest residual 4.36 bps, median 2.17 bps β€” the interpolated median of the absolute residuals, recorded as medianResidualBps in the evidence below), and 7/40 moved between wallets peer-to-peer, up to 1,715 aWETH β€” movement no Aave event describes, so no event-only method could have been correct. Two sub-dust wallets are excluded because a percentage against a near-zero denominator is an artifact. The submitted deck reports the earlier 8-wallet sample; this is the expanded post-deadline corpus. Design and limits: docs/PROOF_OF_NET_POSITION.md. Same evidence rendered live, regenerated from these artifacts so the page cannot drift from the data: spark.sithunyein.com/bonus.

6. Prove a real mainnet position and size credit from it, 10 transactions (one command)

Runs against a local chain, which is how it was executed and recorded. It cannot be run against CC3 with forge script: Creditcoin's block headers omit mixHash, which Foundry validates when it forks, so the run fails before broadcasting. See the warning at the top of docs/DEPLOY_CC3.md for the CC3 path, where the equivalent is DRY_RUN=0 bash script/deploy-position-cc3.sh.

cd contracts && PRIVATE_KEY=<funded dev key> forge script \
  script/ProveMainnetPosition.s.sol:ProveMainnetPosition \
  --rpc-url http://127.0.0.1:8545 --broadcast

Deploys the position stack, anchors at a provably-zero mainnet balance, ingests the real token ledger, reconciles against the real attested balance, and submits the real Chainlink answer β€” then reads the net worth back off-chain-verified. Executed end to end against a local chain; independent reads of the deployed contracts returned netPosition = 433033874843288486772, $1,086,382 of proven net worth, and a $217,276 credit limit sized from it at a 20% policy LTV. Transcript, including the 10 transactions in order and the mainnet re-verification of every input: docs/evidence/position-stack-e2e.txt.

The same stack is also broadcast to CC3. PositionSizedCredit is 0xD19E758C30bD97fe1CFA4d023a4016f2741e9A04 (owner 0x7A35f63F81357DaDE2cff8f5699b935786Aa9Da2), its valuer is 0x95847A47248BA848Fc1Bd43bB8C1F733A4845282, and its attested Chainlink feed is 0xFE16ea120848D75caCbc69BfCee8cb32ec7916c2. All four position-stack contracts are named and verified on Blockscout. Read live off CC3:

cast call 0xD19E758C30bD97fe1CFA4d023a4016f2741e9A04 \
  "limitFor(address,address[],address[])((int256,uint256,uint8))" \
  0x0Cc688BF78bDCC3C072903100B2b821cC8d7d666 \
  "[0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8]" \
  "[0x7d4E742018fb52E48b08BE73d041C18B21de6Fb5]" \
  --rpc-url https://rpc.cc3-testnet.creditcoin.network
# β†’ (104020019677448, 20804003935489, 0)
#   $1,040,200.20 proven net worth β†’ $208,040.04 limit, Eligible

The live figure moves with the attested price, and that is the design rather than an inconsistency: the local run above priced ETH at one answer and the live read prices it at another. The stored answer was refreshed on September 16 from attested mainnet round 33688 (submission tx 0xe8730a2f…), so it is anchored to a round anyone can look up rather than to a number typed in. When an answer ages past the feed's own 24-hour bound the read reverts with StalePrice instead of valuing a position on old data β€” the guard working, not a failure.

One honest note on verification. scripts/verify-cc3-position-stack.mjs requires a forge broadcast artifact, and forge script --broadcast cannot run against Creditcoin: its block headers omit mixHash, which Foundry validates when forking. The CC3 deployment therefore went through script/deploy-position-cc3.sh, which produces no such artifact, so the automated verifier does not run against CC3. The cast read above is the verification, and it is reproducible. Steps and caveats: docs/DEPLOY_CC3.md.

What is different here: two BlockProver proofs on every credit open (payment + solvency), and strict receipt RLP decoding in which a decoded amount that differs from the claim reverts. That path is proven by crafted-receipt tests in contracts/test/VerifierStrict.t.sol; the bug it replaced is documented in SECURITY_FINDINGS.md.

Attestcoin Protocol Integration Summary

Spark makes 15 distinct Attestcoin Protocol surfaces load-bearing across 3 attested event kinds and 5 on-chain entry points. Ten sit on the path a user actually walks; five more are integrated at the contract or SDK layer and covered by tests. Both are counted, and the split is stated rather than blurred.

On the critical path (10) β€” remove any one and the product stops working:

Surface What It Does Why Needed
verifyAndEmit (0x0FD2) BlockProver precompile β€” proves tx inclusion + continuity Without it, no Sepolia fact can be verified on Creditcoin
MerkleProof struct Merkle inclusion proof construction Required by precompile for block inclusion check
ContinuityProof struct Chain continuity proof construction Ensures source block is genuinely part of the chain
Receipt RLP parsing _parseReceiptLogs() decodes Ethereum receipt on-chain Extracts events from proven transaction data
Topic matching Matches event signature from decoded logs Identifies the correct payment event
Payer validation Requires topics[1] to equal the claimed payer Stops one address claiming credit for another's payment
Amount binding Requires decoded amount == claimed amount Prevents amount forgery β€” value is cryptographically bound
ProofBuilder SDK Off-chain proof construction via @gluwa/usc-sdk Assembles Merkle + continuity proofs
waitUntilHeightAttested Polls until the source block is attested, in parallel for the dual proofs Required before proof generation; parallel avoids a second 16-20 min wait
getProof Generates the proof blob for on-chain verification Produces the artifact every entry point consumes

Integrated and tested, not on the payment path (5):

Surface What It Does Why Needed
ChainInfo (0x0FD3) Reads supported chains + attested heights Discovers protocol state, explains attestation lag
previewIngest Dry-run proof validation (staticcall) Saves gas by checking validity before submitting
executeBatch Atomic multi-proof verification Batch N proofs in one tx, all-or-nothing
calculateTxIndex Merkle path position of a transaction in its block Exposed through the verifier for index queries
getBatchProof (SDK) Batch proof generation via @gluwa/usc-sdk Generates multiple proofs atomically in one SDK call

Plus 3 attested event kinds (DepositPaid, RepaymentPaid, BalanceAttested) β€” 18 integration points in total.

What is distinctive here: every credit open requires two proofs β€” one that the payment happened, one that the wallet holds funds at that moment. A single-proof design lets a borrower with an empty wallet open credit by making one payment; the second proof checks the balance at the moment of the decision.

Stated precisely, because the distinction is checkable on chain: the balance claim is verified by the second proof and recorded as CreditOpened.attestedBalance and the Sepolia BalanceAttested event. The linked-history events (AttestedPaymentLinked) observed on chain carry kind 1 and kind 2; the BalanceAttested event type is present as the solvency input.

Full surface enumeration: docs/ATTESTCOIN_SURFACE.md. Where the project goes next, and what is honestly not done yet: docs/ROADMAP.md. What the economics actually look like, with every fact separated from every labelled assumption: docs/UNIT_ECONOMICS.md.

The Problem

2.5 billion people worldwide cannot access credit because they lack bank history, documentation, or infrastructure. Even in crypto, cross-chain credit requires trusting a middleman to verify what happened on another chain. That single point of failure defeats the purpose of decentralization.

Current solutions have three fundamental flaws:

  1. Oracles require trust. A centralized price feed or attestation service can be manipulated, censored, or go offline. The borrower has no guarantee the oracle reports honestly.
  2. Bridges are single points of failure. Bridges have been drained for billions. Moving assets cross-chain to prove creditworthiness exposes the borrower to bridge exploits.
  3. Self-reported history is worthless. A borrower can open and repay their own loan 100 times to build a perfect score. Without a real counterparty, payment history proves nothing.

Spark solves all three by using the Attestcoin Protocol to cryptographically verify Sepolia payments on Creditcoin β€” no oracle, no bridge, no trust. And critically, Spark's dual proofs verify not just that a payment happened, but that the borrower has the funds to cover the credit (solvency check), so a single forged or hollow payment is not enough to open a line.

What it is

Spark proves Sepolia payments with Attestcoin (USC / BlockProver), then opens or clears credit on Creditcoin testnet. No bank forms, no centralized price oracle.

  • Pay deposit on Sepolia β†’ dual Attestcoin proofs (deposit + ETH balance) β†’ open credit on Creditcoin
  • Credit score from on-chain attested payment history (650–850)
  • LTV bonus from linked history (+2.5% at β‰₯1 payment, +5% at β‰₯3) plus balance-based LTV
  • Withdraw sCREDIT, redeem against debt, repay on Sepolia to close

Live app: https://spark.sithunyein.com Β· Deck: deck.pdf

Demo flow (testnet)

Pay deposit β†’ Verify (Attestcoin ~8–20 min) β†’ Withdraw β†’ Redeem β†’ Repay β†’ Closed

Optional first: link past Sepolia payments on Credit score to raise score and LTV before opening a new line.

Architecture

flowchart TB
  User[Borrower] --> App[Spark App]
  App --> Pay[SepoliaPayment]
  App --> Prover[Attestcoin Proof API]
  Prover --> Verifier[AttestcoinPaymentVerifier]
  Verifier --> Credit[CreditLine]
  Credit --> Token[SparkCredit sCREDIT]
Loading

See docs/architecture.md and docs/attestcoin.md.

Project structure

Full repo layout (excluding node_modules/, .next/, contracts/lib/ vendored deps):

spark/
β”œβ”€β”€ .env.example                      # Root env template (optional)
β”œβ”€β”€ .gitignore
β”œβ”€β”€ .gitmodules                       # forge-std submodule
β”œβ”€β”€ package.json                      # Root scripts: dev, build, test:contracts
β”œβ”€β”€ README.md
β”œβ”€β”€ LICENSE
β”œβ”€β”€ SECURITY.md
β”œβ”€β”€ SECURITY_FINDINGS.md              # Two vulnerabilities found and fixed during the build
β”œβ”€β”€ CONTRIBUTING.md
β”‚
β”œβ”€β”€ brand/                            # Logo source (copied into app/public/brand/)
β”‚   β”œβ”€β”€ logo.png
β”‚   β”œβ”€β”€ logo-mark.svg
β”‚   β”œβ”€β”€ logo-on-orange.png
β”‚   β”œβ”€β”€ logo-on-orange.svg
β”‚   β”œβ”€β”€ logo-wordmark-dark.svg
β”‚   └── logo-wordmark-light.svg
β”‚
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ addresses.md                  # Production + legacy contract addresses & Vercel env
β”‚   β”œβ”€β”€ architecture.md               # System diagram, sequences
β”‚   β”œβ”€β”€ attestcoin.md                 # USC / BlockProver integration
β”‚   β”œβ”€β”€ ATTESTCOIN_SURFACE.md         # Every Attestcoin surface Spark uses, why needed
β”‚   β”œβ”€β”€ THREAT_MODEL.md               # What attacks are prevented, what is still open
β”‚   β”œβ”€β”€ SCORING.md                    # Credit score formula, LTV bonus, constants rationale
β”‚   β”œβ”€β”€ PROOF_OF_NET_POSITION.md      # Net-position primitive: design + measured evidence
β”‚   β”œβ”€β”€ UNIT_ECONOMICS.md             # Interest, LTV and loss given default: facts vs assumptions
β”‚   β”œβ”€β”€ evidence/                     # On-chain proof artifacts
β”‚   β”œβ”€β”€ ROADMAP.md                    # Milestones, what is built vs deployed vs planned
β”‚   β”œβ”€β”€ DEPLOY_CC3.md                 # Deploying the position stack and generation 2 to CC3
β”‚   β”œβ”€β”€ PARTICIPATE.md                # How to run the loop yourself, and what it costs you
β”‚   β”œβ”€β”€ deck.md                       # Pitch deck notes
β”‚   └── deploy-vercel.md              # Vercel deploy (root dir = app)
β”‚
β”œβ”€β”€ app/                              # Next.js 15 β€” Vercel root directory
β”‚   β”œβ”€β”€ .env.example                  # Local / production env template
β”‚   β”œβ”€β”€ .gitignore
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ pnpm-lock.yaml
β”‚   β”œβ”€β”€ next.config.ts
β”‚   β”œβ”€β”€ next-env.d.ts
β”‚   β”œβ”€β”€ tsconfig.json
β”‚   β”œβ”€β”€ postcss.config.js
β”‚   β”œβ”€β”€ tailwind.config.js
β”‚   β”‚
β”‚   β”œβ”€β”€ public/
β”‚   β”‚   β”œβ”€β”€ favicon.svg
β”‚   β”‚   β”œβ”€β”€ favicon.png
β”‚   β”‚   β”œβ”€β”€ deck.pdf
β”‚   β”‚   β”œβ”€β”€ deck.html
β”‚   β”‚   └── brand/
β”‚   β”‚       β”œβ”€β”€ logo.png
β”‚   β”‚       β”œβ”€β”€ logo-mark.svg
β”‚   β”‚       β”œβ”€β”€ logo-on-orange.png
β”‚   β”‚       β”œβ”€β”€ logo-on-orange.svg
β”‚   β”‚       β”œβ”€β”€ logo-wordmark-dark.svg
β”‚   β”‚       β”œβ”€β”€ logo-wordmark-light.svg
β”‚   β”‚       └── metamask.png
β”‚   β”‚
β”‚   β”œβ”€β”€ scripts/                      # Read-only evidence scripts (no keys, no gas)
β”‚   β”‚   β”œβ”€β”€ spike-mainnet.mjs         # Day 1: prove a real mainnet tx into chainKey 3
β”‚   β”‚   β”œβ”€β”€ spike-position.mjs        # Day 1: extract fields from the proven EvmV1 payload
β”‚   β”‚   β”œβ”€β”€ aave-indexer.mjs          # Day 2: explorer-based event index + drift
β”‚   β”‚   β”œβ”€β”€ aave-drift-window.mjs     # Day 2: anchored ledger vs Aave-event reconciliation
β”‚   β”‚   β”œβ”€β”€ protocol-topics.mjs       # Day 4: topic parity + negative controls
β”‚   β”‚   β”œβ”€β”€ position-scale.mjs        # Day 5: reconciliation across many wallets
β”‚   β”‚   β”œβ”€β”€ gen-evidence-module.mjs   # Renders the mainnet evidence into a typed module
β”‚   β”‚   β”œβ”€β”€ gen-chain-activity.mjs    # Reads every deployed event into a typed module
β”‚   β”‚   └── verify-cc3-position-stack.mjs # Reads CC3 back and asserts it against mainnet facts
β”‚   β”‚
β”‚   └── src/
β”‚       β”œβ”€β”€ styles/
β”‚       β”‚   └── globals.css
β”‚       β”‚
β”‚       β”œβ”€β”€ app/                      # App Router
β”‚       β”‚   β”œβ”€β”€ layout.tsx            # Root layout, providers
β”‚       β”‚   β”œβ”€β”€ page.tsx              # / β†’ redirect overview
β”‚       β”‚   β”œβ”€β”€ overview/page.tsx     # Dashboard, score, position, checklist
β”‚       β”‚   β”œβ”€β”€ pay/page.tsx          # Sepolia deposit + Attestcoin verify + openCredit
β”‚       β”‚   β”œβ”€β”€ score/page.tsx        # Link history β†’ creditScore + LTV bonus
β”‚       β”‚   β”œβ”€β”€ withdraw/page.tsx     # Withdraw + redeem sCREDIT
β”‚       β”‚   β”œβ”€β”€ transfer/page.tsx     # Send & receive sCREDIT
β”‚       β”‚   β”œβ”€β”€ repay/page.tsx        # Sepolia repay + verify + close
β”‚       β”‚   β”œβ”€β”€ activity/page.tsx     # Payment journal (sidebar: Payments)
β”‚       β”‚   β”œβ”€β”€ onchain/page.tsx       # Public on-chain record, no wallet required
β”‚       β”‚   β”œβ”€β”€ bonus/page.tsx        # Mainnet position proof (measured evidence)
β”‚       β”‚   β”œβ”€β”€ help/page.tsx         # User guide
β”‚       β”‚   β”œβ”€β”€ settings/page.tsx     # Wallet, networks, security
β”‚       β”‚   └── advanced/page.tsx     # Developer / contract links
β”‚       β”‚
β”‚       β”œβ”€β”€ components/
β”‚       β”‚   β”œβ”€β”€ AppShell.tsx          # Page shell + sidebar
β”‚       β”‚   β”œβ”€β”€ Sidebar.tsx           # Nav: overview, pay, score, withdraw, …
β”‚       β”‚   β”œβ”€β”€ Logo.tsx
β”‚       β”‚   β”œβ”€β”€ ConnectButton.tsx
β”‚       β”‚   β”œβ”€β”€ ConnectModal.tsx
β”‚       β”‚   β”œβ”€β”€ AccountMenu.tsx
β”‚       β”‚   β”œβ”€β”€ MetricCard.tsx
β”‚       β”‚   β”œβ”€β”€ PositionSnapshot.tsx
β”‚       β”‚   β”œβ”€β”€ ActivityTable.tsx
β”‚       β”‚   β”œβ”€β”€ OnboardingChecklist.tsx
β”‚       β”‚   β”œβ”€β”€ ConfirmingStages.tsx  # Pay/repay stepper
β”‚       β”‚   β”œβ”€β”€ AttestcoinProofPanel.tsx
β”‚       β”‚   β”œβ”€β”€ LinkHistoryPanel.tsx  # Credit score linking UI
β”‚       β”‚   β”œβ”€β”€ SuccessBanner.tsx
β”‚       β”‚   β”œβ”€β”€ PaymentHistoryStrip.tsx
β”‚       β”‚   └── SimpleChart.tsx
β”‚       β”‚
β”‚       β”œβ”€β”€ hooks/
β”‚       β”‚   β”œβ”€β”€ usePaymentActivity.ts # Journal + Sepolia log scan
β”‚       β”‚   └── useChainTxConfirmation.ts
β”‚       β”‚
β”‚       └── lib/
β”‚           β”œβ”€β”€ config.ts             # NEXT_PUBLIC_* addresses & RPC
β”‚           β”œβ”€β”€ abi.ts                # Contract ABIs
β”‚           β”œβ”€β”€ wagmi.tsx             # MetaMask connector, Creditcoin chain
β”‚           β”œβ”€β”€ sparkInjected.js      # Custom injected connector
β”‚           β”œβ”€β”€ sparkInjected.d.ts
β”‚           β”œβ”€β”€ usc.ts                # Attestcoin proof builder (parallel waits)
β”‚           β”œβ”€β”€ chains.ts             # ensureCreditcoinChain / ensureSepoliaChain
β”‚           β”œβ”€β”€ errors.ts             # friendlyError messages
β”‚           β”œβ”€β”€ flowState.ts          # sessionStorage pay/repay resume
β”‚           β”œβ”€β”€ mainnetEvidence.ts    # GENERATED: measured mainnet facts
β”‚           β”œβ”€β”€ chainActivity.ts      # GENERATED: every deployed event, with tx hashes
β”‚           └── format.ts             # ETH formatting, proof encoding
β”‚
└── contracts/                        # Foundry
    β”œβ”€β”€ foundry.toml
    β”œβ”€β”€ foundry.lock
    β”œβ”€β”€ remappings.txt
    β”œβ”€β”€ lib/
    β”‚   └── forge-std/                # Git submodule
    β”‚
    β”œβ”€β”€ src/
    β”‚   β”œβ”€β”€ SepoliaPayment.sol        # payDeposit, payRepayment, attestBalance
    β”‚   β”œβ”€β”€ CreditLine.sol            # openCredit (deposit-sized) + openCreditFromBalance (proven-balance-sized), score, history, redeem, repay
    β”‚   β”œβ”€β”€ AttestcoinPaymentVerifier.sol
    β”‚   β”œβ”€β”€ SparkCredit.sol           # sCREDIT ERC-20
    β”‚   β”œβ”€β”€ MockPaymentVerifier.sol   # Unit tests only
    β”‚   β”œβ”€β”€ MainnetPositionRegistry.sol  # Ledger net position, proven-zero anchor, capped interest
    β”‚   β”œβ”€β”€ AttestedPriceFeed.sol     # BlockProver-verified Chainlink AnswerUpdated
    β”‚   β”œβ”€β”€ MainnetTokenRegistry.sol  # Attested mainnet decimals + asset/liability
    β”‚   β”œβ”€β”€ PositionValuer.sol        # Signed USD net worth (8dp base units)
    β”‚   β”œβ”€β”€ PositionSizedCredit.sol   # Proven net worth -> credit limit under an explicit policy
    β”‚   β”œβ”€β”€ AttestedStanding.sol      # Portable standing: one record any consumer reads, evidence ref carried* 
    β”‚   β”œβ”€β”€ GroupCredit.sol           # Attested group credit with vouching, capped at aggregate proven history*
    β”‚   β”œβ”€β”€ MainnetTopics.sol         # Mainnet topic constants + verification status
    β”‚   └── interfaces/
    β”‚       β”œβ”€β”€ IPaymentVerifier.sol
    β”‚       └── ICreditLineView.sol   # Read-only CreditLine surface downstream contracts consume
    β”‚
    β”œβ”€β”€ test/
    β”‚   β”œβ”€β”€ Spark.t.sol               # 300 tests: score, history, dual-proof, batch, negative-path, edge cases, stress, lifecycle, events, combos
    β”‚   β”œβ”€β”€ VerifierStrict.t.sol      # 6 tests: strict RLP decode, amount binding, wrong payer, long-form bloom, multi-log
    β”‚   β”œβ”€β”€ MainnetPositionRegistry.t.sol # 30 tests: anchor rule, ordering, replay, residual bound, Day 2 regressions
    β”‚   β”œβ”€β”€ AttestedValuation.t.sol   # 31 tests: prices, token metadata, net worth, topic parity
    β”‚   β”œβ”€β”€ PositionStackIntegration.t.sol # 6 tests: full stack, exact real mainnet numbers
    β”‚   β”œβ”€β”€ PositionSizedCredit.t.sol # 23 tests: policy, half-of-net-worth cap, refusal status codes
    β”‚   β”œβ”€β”€ BalanceSizedCredit.t.sol  # 21 tests: balance sizing, refusals, score neutrality, path separation
    β”‚   β”œβ”€β”€ AttestedStanding.t.sol    # 19 tests: evidence required, exact mirroring, eligibility, freshness*
    β”‚   └── GroupCredit.t.sol         # 31 tests: admission gate, vouch bounds, aggregate-proven ceiling*
    β”‚
    β”œβ”€β”€ script/
    β”‚   β”œβ”€β”€ Deploy.s.sol
    β”‚   β”œβ”€β”€ NegativePathLive.s.sol     # 8 forged proofs vs the live 0x0FD2 precompile
    β”‚   β”œβ”€β”€ ProveMainnetPosition.s.sol # one command: deploy stack + prove a real mainnet position
    β”‚   └── DeployGeneration2CreditLine.s.sol # one command: generation 2 CreditLine, reusing the deployed verifier
    β”‚
    └── scripts/
        β”œβ”€β”€ deploy-all.sh
        β”œβ”€β”€ deploy-attestcoin.ps1
        β”œβ”€β”€ attestcoin-ctor-args.txt
        └── creditline-ctor-args.txt

* Written after the submission deadline. AttestedStanding.sol, StandingGatedCheckout.sol and GroupCredit.sol are items of docs/CEIP.md built as real code rather than plan text. They are labelled in-source and here because the BUIDL CTC 2026 Fall submission closed on 2026-09-13 23:59 ET, and work added afterwards should say so. None is reachable from the live app, and nothing in the submission has been edited to include them.

AttestedStanding.sol and StandingGatedCheckout.sol are deployed to CC3 and exercised end to end: a record was issued from generation-1 Attestcoin evidence, a merchant applied its own policy to it, deferred an order, and was settled. The addresses and the full read-back are in docs/addresses.md, and /bonus reads the pair live. GroupCredit.sol is not deployed.

That is also why the suite reads 518 here and 417 in docs/deck.md and docs/DORAHACKS_UPDATE.md: the deck and the submission text describe the frozen entry, and this README describes the repository as it stands.

Deployed contracts

Production (live site β€” credit-score stack)

Contract Network Address Verified
SepoliaPayment Ethereum Sepolia 0x63F0c69cf9F8b53E8eDD141d07fF2eEd2237ccc4 Yes (Blockscout)
AttestcoinPaymentVerifier Creditcoin testnet 0xF13205Bdf48A3159d4A46309C639930aE8faC130 Yes
CreditLine (history + score + LTV bonus) Creditcoin testnet 0x2C3585019B957b16459C409f34973b583267C742 Yes (Blockscout)
SparkCredit (sCREDIT) Creditcoin testnet 0x1BaDE07F2F3295528a2F7316119813b6846dFfaD Yes
BlockProver (USC precompile) Creditcoin 0x0000000000000000000000000000000000000FD2 n/a

Legacy (Aug 13 dual-proof β€” finish open repay via Repay page)

Contract Network Address
SepoliaPayment Ethereum Sepolia 0x4B137F56A0b5A8633D079d2d6b34d6aC5CdD22E9
AttestcoinPaymentVerifier Creditcoin testnet 0x372BF96DFfa019A03E861d57CfC8a129172C8A3C
CreditLine (dual-proof + interest) Creditcoin testnet 0x1Ba750b08dC4C06B993DfDedE45d22cbD540D319
SparkCredit (sCREDIT) Creditcoin testnet 0xFa18A5458a973a4E8a3eF327A88262683B64b02b

Retired stacks and Vercel env values: docs/addresses.md.

Proof of record

On-chain demo wallet: 0x7A35f63F81357DaDE2cff8f5699b935786Aa9Da2. All txs below are on the production CreditLine (0x2C358501…) with real Attestcoin USC proofs (BlockProver TransactionVerified in each tx).

Aug 14 open β€” 95% LTV (history bonus at cap)

CreditOpened: deposit 0.01 ETH, attested balance ~0.357 ETH, credit 0.0095 ETH, factorBps = 9500 (95% LTV β€” base 90% + 500 bps history bonus at β‰₯3 linked payments).

Attested payment history β€” score exercised

5 AttestedPaymentLinked events on production CreditLine (kinds 1 = deposit, 2 = repayment). On-chain creditScore() = 850 (650 base + 5 Γ— 40).

# Kind Creditcoin tx
1 deposit 0xe5ec5506…da9c1
2 repayment 0xe7313fef…9f15
3 repayment 0x5092e516…18eb4
4 deposit 0xbbec27e6…a618
5 repayment 0x5fc0b4fb…e122

Full log index: CreditLine events.

Two full closed loops (Open β†’ Withdraw β†’ Redeem β†’ Repay β†’ Close)

Both loops use real USC proofs end-to-end. Sepolia repay txs linked at close via CreditClosed.

Loop 1 β€” Aug 13 (factorBps = 9000, credit 0.009 ETH)

Step Creditcoin tx
Open 0xe5ec5506…da9c1
Withdraw 0xbf411c5a…1f3d
Redeem 0x48980365…cfb01
Repay + close 0x5092e516…18eb4

Loop 2 β€” Aug 14 (factorBps = 9500, credit 0.0095 ETH)

Step Creditcoin tx
Open 0xbbec27e6…a618
Withdraw 0x3bc160b1…2789
Redeem 0x9177c410…d34d
Repay + close 0x5fc0b4fb…e122

Sepolia repayments (kind-2 USC proofs consumed at close):

Quickstart

# Install (from repo root)
pnpm install --dir app

# Contracts
cd contracts && forge test

# App
cd ../app
cp .env.example .env.local   # fill RPC URLs if needed
pnpm dev

Open http://localhost:3000. User guide: in-app Help or spark.sithunyein.com/help.

Deploy

Target How
App Vercel, root directory app β€” docs/deploy-vercel.md
Contracts Foundry contracts/script/Deploy.s.sol β†’ update docs/addresses.md + Vercel env

Credit score (on-chain)

Metric Rule
Score 650 base + 40 Γ— attested payments (cap 850)
LTV bonus +250 bps (β‰₯1 payment), +500 bps (β‰₯3 payments)
Balance LTV β‰₯2Γ— deposit β†’ 90%, β‰₯1Γ— β†’ 85%, else 80% base
Balance-sized line (generation 2) No deposit. openCreditFromBalance: limit = attested balance Γ— 20%, floor 0.0001 ETH, and a balance attestation never moves the score because a balance is not a payment. Live on CC3 since 2026-09-14 with a line opened and drawn against it β€” docs/DEPLOY_CC3.md
Proof submitAttestedPayment links past Sepolia txs; openCredit / repayCredit also count

Formula lives in contracts/src/CreditLine.sol β€” readable via creditScore() and getHistory().

Roadmap

Phase Focus
Now Live testnet: dual Attestcoin proofs, score, history LTV, strict receipt log decoding with amount binding, full borrow/repay loop
Deposit-free credit, live Credit that does not require a deposit: sized from a proven Sepolia balance (openCreditFromBalance) or from a proven Ethereum mainnet net worth (PositionSizedCredit). Both are live on CC3 and verified. The balance-sized path runs end to end from the product rather than from a script: a first line was opened on 2026-09-14 and the full 0.00398 ETH limit was drawn from /balance, which carries that generation's draw, redeem and close actions, while /repay settles it from a proven Sepolia repayment. The demo-described deposit flow is untouched on /pay. The mainnet position layer is executed locally and not broadcast. docs/ROADMAP.md
Portable standing A verified record that a different product reads and gates on, with the Attestcoin evidence reference carried into the decision. Deployed and exercised end to end β€” docs/addresses.md
Next Faster verify UX (parallel attestation, caching)
Later Mainnet, audit, lending pool, single-network UX

Security

Not audited. Testnet only. See SECURITY.md. No private keys on Vercel. Two vulnerabilities were found and fixed during the build: SECURITY_FINDINGS.md.

Verifier note: BlockProver proves inclusion cryptographically. The adapter strictly decodes the receipt RLP from the proven encodedTransaction, matching event topic, indexed payer, and non-indexed amount from decoded logs. Amount is cryptographically bound (not trusting claim.amount) β€” once the receipt parses, a decoded amount that differs from claim.amount reverts; it never falls through to the weaker substring scan. The strict path is proven by 6 dedicated tests with crafted RLP receipts (test/VerifierStrict.t.sol). Per the Aug 18 AMA, receipt log data is confirmed available via BlockProver.

ChainInfo: the 0x0FD3 precompile uses snake_case selectors (get_supported_chains, get_latest_attestation_height_and_hash) β€” verified live on CC3, and it reports both Sepolia (chainKey 1) and Ethereum mainnet (chainKey 3) as attested source chains. See docs/THREAT_MODEL.md.

Live Precompile Tests (Zero Cost)

Spark includes 8 live negative-path tests against the real BlockProver precompile on CC3 testnet. These use eth_call (read-only) β€” zero gas, zero CTC, zero cost.

cd contracts && bash run-negative-paths.sh

Results:

  • Forged merkle root β†’ REJECTED
  • Wrong chain key (99) β†’ REJECTED
  • Zero height β†’ REJECTED
  • Empty encoded transaction β†’ REJECTED
  • Mismatched sibling lengths β†’ REJECTED
  • Very large chain key (9999) β†’ REJECTED
  • Max uint64 height β†’ REJECTED
  • Random bytes as proof β†’ REJECTED

All 8 forged proofs rejected by the real BlockProver precompile. Judges can run this themselves.

License

MIT β€” LICENSE.

About

Spark: DeFi credit on Creditcoin gated by Attestcoin USC proofs. Pay on Sepolia, verify on Creditcoin, unlock credit score and LTV bonus. BUIDL CTC Fall 2026.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages