Repository navigation
Expand file tree
/
Copy pathpredicate.ts
More file actions
132 lines (121 loc) · 4.93 KB
/
Copy pathpredicate.ts
File metadata and controls
132 lines (121 loc) · 4.93 KB
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { hash, num } from "starknet";
import { provider } from "./starknet";
import { STRK20_POOL_ADDRESS } from "./config";
import { evaluateDepositCount as computeDepositCount, evaluateHeldSince, isAlwaysTruePredicate } from "./predicateMath";
/**
* Reads the STRK20 pool's public `Deposit` events for a given address.
*
* Deposits into the pool are public by design (Day-0 mainnet guide: "deposits
* undergo compliance screening and are publicly visible") — so this is real,
* on-chain, no-trust-required evidence, unlike the private note balance that
* results afterward. It is the honest v1 predicate source: see README
* "What is private / what is not" for why note-level history isn't used yet.
*/
export interface DepositRecord {
amount: bigint;
token: string;
blockNumber: number;
timestampSec: number;
txHash: string;
}
const DEPOSIT_EVENT_KEY = num.toHex(hash.starknetKeccak("Deposit"));
export async function getDepositHistory(userAddress: string): Promise<DepositRecord[]> {
const p = provider();
const userFelt = num.toHex(userAddress);
const events: DepositRecord[] = [];
let continuationToken: string | undefined;
do {
const page = await p.getEvents({
address: STRK20_POOL_ADDRESS,
keys: [[DEPOSIT_EVENT_KEY], [userFelt]],
chunk_size: 100,
continuation_token: continuationToken,
from_block: { block_number: 0 },
to_block: "latest",
});
for (const ev of page.events) {
const [, token, amountLow] = ev.data ?? [];
const block = await p.getBlockWithTxHashes(ev.block_hash ?? "latest");
events.push({
amount: BigInt(amountLow ?? "0"),
token: token ?? "",
blockNumber: ev.block_number ?? 0,
timestampSec: "timestamp" in block ? (block.timestamp as number) : 0,
txHash: ev.transaction_hash,
});
}
continuationToken = page.continuation_token;
} while (continuationToken);
return events;
}
export async function evaluateHeldSinceDays(
userAddress: string,
tokenAddress: string,
minAmount: bigint,
minDays: number
): Promise<{ eligible: boolean; evidence: DepositRecord[] }> {
const deposits = (await getDepositHistory(userAddress))
.filter((d) => d.token.toLowerCase() === tokenAddress.toLowerCase())
.sort((a, b) => a.timestampSec - b.timestampSec);
const { eligible, evidence } = evaluateHeldSince(deposits, minAmount, minDays);
return { eligible, evidence };
}
/**
* Balance-threshold predicate: total deposited ≥ minAmount, no holding-period
* requirement. Same public-data source as evaluateHeldSinceDays with the
* time cutoff removed (equivalent to minDays = 0).
*/
export async function evaluateBalanceThreshold(
userAddress: string,
tokenAddress: string,
minAmount: bigint
): Promise<{ eligible: boolean; evidence: DepositRecord[] }> {
return evaluateHeldSinceDays(userAddress, tokenAddress, minAmount, 0);
}
/**
* Deposit-count predicate: number of separate deposits into the pool ≥
* minCount. NOTE: this counts public *deposit* events, not private
* note-to-note transfers — the pool does not expose the latter publicly by
* design (see README "What is private / what is not"), so a genuine
* "private transfer count" predicate is not honestly computable from public
* data today. This is a distinct, real predicate type (activity-based
* rather than balance-based) built from the same honest data source.
*/
export async function evaluateDepositCount(
userAddress: string,
tokenAddress: string,
minCount: bigint
): Promise<{ eligible: boolean; evidence: DepositRecord[] }> {
const deposits = (await getDepositHistory(userAddress)).filter(
(d) => d.token.toLowerCase() === tokenAddress.toLowerCase()
);
const { eligible } = computeDepositCount(deposits, minCount);
return { eligible, evidence: deposits };
}
export type PredicateType = "held_since" | "balance_threshold" | "deposit_count";
export async function evaluatePredicate(
predicateType: string,
userAddress: string,
tokenAddress: string,
minAmount: bigint,
minDays: number
): Promise<{ eligible: boolean; evidence: DepositRecord[] }> {
// Always-true predicates (the Capability Smoke Test: deposit_count with a
// minimum of 0, or any amount-based predicate with a minimum of 0) need no
// deposit history at all — mirrors the same short-circuit in
// ProvaApp.tsx's clientEvaluatePredicate (both call the same
// isAlwaysTruePredicate in predicateMath.ts) so /api/pass doesn't burn an
// RPC round trip re-deriving an answer that's already known.
if (isAlwaysTruePredicate(predicateType, minAmount)) {
return { eligible: true, evidence: [] };
}
switch (predicateType) {
case "balance_threshold":
return evaluateBalanceThreshold(userAddress, tokenAddress, minAmount);
case "deposit_count":
return evaluateDepositCount(userAddress, tokenAddress, minAmount);
case "held_since":
default:
return evaluateHeldSinceDays(userAddress, tokenAddress, minAmount, minDays);
}
}