Pay once. Unlock credit.
Verified Sepolia payment history is your credit score β no oracle.
spark.sithunyein.com Β· Help Β· GitHub Β· MIT License
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).
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 test2. The real BlockProver precompile rejecting forged proofs (read-only, zero cost)
cd contracts && bash run-negative-paths.shEight 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.mjsThis 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.mjsSet 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 --broadcastDeploys 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, EligibleThe 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.
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.
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:
- 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.
- 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.
- 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.
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
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.
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]
See docs/architecture.md and docs/attestcoin.md.
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.
| 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 |
| 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.
On-chain demo wallet: 0x7A35f63F81357DaDE2cff8f5699b935786Aa9Da2. All txs below are on the production CreditLine (0x2C358501β¦) with real Attestcoin USC proofs (BlockProver TransactionVerified in each tx).
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).
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.
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):
- Loop 1:
0x5fa5d7a2β¦9785 - Loop 2:
0xf3825f7fβ¦ebe6
# 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 devOpen http://localhost:3000. User guide: in-app Help or spark.sithunyein.com/help.
| Target | How |
|---|---|
| App | Vercel, root directory app β docs/deploy-vercel.md |
| Contracts | Foundry contracts/script/Deploy.s.sol β update docs/addresses.md + Vercel env |
| 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().
| 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 |
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.
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.shResults:
- 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.
MIT β LICENSE.