frisk-screen 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frisk/__init__.py +22 -0
- frisk/client.py +134 -0
- frisk/data/README.md +9 -0
- frisk/data/blocklist_seed.json +1 -0
- frisk/lite.py +115 -0
- frisk/py.typed +0 -0
- frisk/types.py +67 -0
- frisk_screen-0.0.1.dist-info/METADATA +114 -0
- frisk_screen-0.0.1.dist-info/RECORD +11 -0
- frisk_screen-0.0.1.dist-info/WHEEL +4 -0
- frisk_screen-0.0.1.dist-info/licenses/LICENSE +21 -0
frisk/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Pre-transaction risk screening for autonomous AI agents."""
|
|
2
|
+
|
|
3
|
+
from .client import Client, ScreenError
|
|
4
|
+
from .types import (
|
|
5
|
+
Confidence,
|
|
6
|
+
Policy,
|
|
7
|
+
ScreenRequest,
|
|
8
|
+
ScreenResult,
|
|
9
|
+
Verdict,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Client",
|
|
14
|
+
"ScreenError",
|
|
15
|
+
"Confidence",
|
|
16
|
+
"Policy",
|
|
17
|
+
"ScreenRequest",
|
|
18
|
+
"ScreenResult",
|
|
19
|
+
"Verdict",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
__version__ = "0.0.1"
|
frisk/client.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Client entry point for screening agent transactions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import urllib.error
|
|
7
|
+
import urllib.request
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
from .lite import evaluate_lite
|
|
12
|
+
from .types import Confidence, Policy, ScreenRequest, ScreenResult, Verdict
|
|
13
|
+
|
|
14
|
+
DEFAULT_BASE_URL = "https://api.tryfrisk.dev"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ScreenError(RuntimeError):
|
|
18
|
+
"""Raised when a hosted screening request cannot be completed."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _normalize_base_url(raw: str) -> str:
|
|
22
|
+
"""Validate and normalize a base URL.
|
|
23
|
+
|
|
24
|
+
HTTPS is required so the API key in the Authorization header is never sent
|
|
25
|
+
in plaintext; http is allowed only for localhost to support local
|
|
26
|
+
development and tests.
|
|
27
|
+
"""
|
|
28
|
+
trimmed = raw.rstrip("/")
|
|
29
|
+
parsed = urlparse(trimmed)
|
|
30
|
+
is_localhost = parsed.hostname in ("localhost", "127.0.0.1", "::1")
|
|
31
|
+
if parsed.scheme == "https" or (parsed.scheme == "http" and is_localhost):
|
|
32
|
+
return trimmed
|
|
33
|
+
raise ValueError("base_url must use HTTPS (http is allowed only for localhost)")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Client:
|
|
37
|
+
"""Screen agent transactions before they execute.
|
|
38
|
+
|
|
39
|
+
Without an API key the client runs in offline ``lite`` mode. Supply an
|
|
40
|
+
API key to use the hosted service, which adds reputation-graph, model,
|
|
41
|
+
and threat-feed signals and returns higher-confidence verdicts.
|
|
42
|
+
|
|
43
|
+
Example::
|
|
44
|
+
|
|
45
|
+
client = Client() # lite mode
|
|
46
|
+
result = client.screen("0xabc...", amount=2.5, asset="USDC")
|
|
47
|
+
if not result.allowed:
|
|
48
|
+
... # hold the payment for review
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
api_key: Optional[str] = None,
|
|
54
|
+
*,
|
|
55
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
56
|
+
timeout: float = 5.0,
|
|
57
|
+
) -> None:
|
|
58
|
+
self._api_key = api_key
|
|
59
|
+
self._base_url = _normalize_base_url(base_url)
|
|
60
|
+
self._timeout = timeout
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def mode(self) -> str:
|
|
64
|
+
return "hosted" if self._api_key else "lite"
|
|
65
|
+
|
|
66
|
+
def screen(
|
|
67
|
+
self,
|
|
68
|
+
counterparty: str,
|
|
69
|
+
*,
|
|
70
|
+
endpoint: Optional[str] = None,
|
|
71
|
+
amount: Optional[float] = None,
|
|
72
|
+
asset: Optional[str] = None,
|
|
73
|
+
policy: Optional[Policy] = None,
|
|
74
|
+
observed_pay_to: Optional[str] = None,
|
|
75
|
+
strictness: Optional[float] = None,
|
|
76
|
+
) -> ScreenResult:
|
|
77
|
+
request = ScreenRequest(
|
|
78
|
+
counterparty=counterparty,
|
|
79
|
+
endpoint=endpoint,
|
|
80
|
+
amount=amount,
|
|
81
|
+
asset=asset,
|
|
82
|
+
policy=policy,
|
|
83
|
+
observed_pay_to=observed_pay_to,
|
|
84
|
+
strictness=strictness,
|
|
85
|
+
)
|
|
86
|
+
if self._api_key:
|
|
87
|
+
return self._screen_hosted(request)
|
|
88
|
+
return evaluate_lite(request)
|
|
89
|
+
|
|
90
|
+
def _screen_hosted(self, request: ScreenRequest) -> ScreenResult:
|
|
91
|
+
payload = {
|
|
92
|
+
"counterparty": request.counterparty,
|
|
93
|
+
"endpoint": request.endpoint,
|
|
94
|
+
"amount": request.amount,
|
|
95
|
+
"asset": request.asset,
|
|
96
|
+
"observed_pay_to": request.observed_pay_to,
|
|
97
|
+
"strictness": request.strictness,
|
|
98
|
+
}
|
|
99
|
+
if request.policy is not None:
|
|
100
|
+
payload["policy"] = {
|
|
101
|
+
"max_per_call": request.policy.max_per_call,
|
|
102
|
+
"allowed_assets": request.policy.allowed_assets,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
http_request = urllib.request.Request(
|
|
106
|
+
f"{self._base_url}/v1/screen",
|
|
107
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
108
|
+
headers={
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
111
|
+
},
|
|
112
|
+
method="POST",
|
|
113
|
+
)
|
|
114
|
+
try:
|
|
115
|
+
with urllib.request.urlopen(
|
|
116
|
+
http_request, timeout=self._timeout
|
|
117
|
+
) as response:
|
|
118
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
119
|
+
except urllib.error.HTTPError as exc:
|
|
120
|
+
raise ScreenError(f"hosted screening failed: HTTP {exc.code}") from exc
|
|
121
|
+
except urllib.error.URLError as exc:
|
|
122
|
+
raise ScreenError(f"hosted screening unreachable: {exc.reason}") from exc
|
|
123
|
+
return _result_from_payload(data)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _result_from_payload(data: dict) -> ScreenResult:
|
|
127
|
+
return ScreenResult(
|
|
128
|
+
verdict=Verdict(data["verdict"]),
|
|
129
|
+
trust_score=int(data["trust_score"]),
|
|
130
|
+
confidence=Confidence(data.get("confidence", "low")),
|
|
131
|
+
reasons=list(data.get("reasons", [])),
|
|
132
|
+
policy_hits=list(data.get("policy_hits", [])),
|
|
133
|
+
source="hosted",
|
|
134
|
+
)
|
frisk/data/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Seed blocklist
|
|
2
|
+
|
|
3
|
+
`blocklist_seed.json` is a small, offline subset of known-bad counterparties
|
|
4
|
+
shipped with the package so that lite mode can flag the most obvious cases
|
|
5
|
+
without a network call.
|
|
6
|
+
|
|
7
|
+
It is **not** the authoritative list. The live, continuously updated
|
|
8
|
+
blocklist and reputation data are served by the hosted API. Entries here are
|
|
9
|
+
only added when independently verifiable from public sources.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
frisk/lite.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Offline ("lite") screening.
|
|
2
|
+
|
|
3
|
+
The lite engine runs entirely on the caller's machine using only public,
|
|
4
|
+
locally available signals. It is intentionally weaker than the hosted
|
|
5
|
+
service and always reports LOW confidence: it has no reputation history, no
|
|
6
|
+
threat feed, and no trained models. Supply an API key to :class:`Client` to
|
|
7
|
+
use the hosted service for graph-, model-, and threat-feed-backed verdicts.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from typing import Iterable, List, Optional, Set
|
|
15
|
+
|
|
16
|
+
from .types import Confidence, ScreenRequest, ScreenResult, Verdict
|
|
17
|
+
|
|
18
|
+
_SEED_BLOCKLIST_PATH = os.path.join(
|
|
19
|
+
os.path.dirname(__file__), "data", "blocklist_seed.json"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_BASELINE_SCORE = 70 # lite cannot see reputation history, so it starts neutral
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load_seed_blocklist() -> Set[str]:
|
|
26
|
+
try:
|
|
27
|
+
with open(_SEED_BLOCKLIST_PATH, "r", encoding="utf-8") as handle:
|
|
28
|
+
return {str(entry).lower() for entry in json.load(handle)}
|
|
29
|
+
except (OSError, ValueError):
|
|
30
|
+
return set()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
_SEED_BLOCKLIST = _load_seed_blocklist()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def evaluate_lite(
|
|
37
|
+
request: ScreenRequest,
|
|
38
|
+
blocklist: Optional[Iterable[str]] = None,
|
|
39
|
+
) -> ScreenResult:
|
|
40
|
+
"""Score a request using offline signals only."""
|
|
41
|
+
block = (
|
|
42
|
+
_SEED_BLOCKLIST
|
|
43
|
+
if blocklist is None
|
|
44
|
+
else {str(entry).lower() for entry in blocklist}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
score = _BASELINE_SCORE
|
|
48
|
+
reasons: List[str] = []
|
|
49
|
+
counterparty = (request.counterparty or "").lower()
|
|
50
|
+
|
|
51
|
+
# 1. Seed blocklist. The live, continuously updated list is hosted-only.
|
|
52
|
+
if counterparty in block:
|
|
53
|
+
score = 0
|
|
54
|
+
reasons.append("counterparty on local seed blocklist")
|
|
55
|
+
|
|
56
|
+
# 2. Address sanity.
|
|
57
|
+
if not _looks_like_address(counterparty):
|
|
58
|
+
score -= 15
|
|
59
|
+
reasons.append("counterparty is not a well-formed address")
|
|
60
|
+
|
|
61
|
+
# 3. Dynamic payTo swap (a documented x402 attack vector).
|
|
62
|
+
if request.observed_pay_to and request.observed_pay_to.lower() != counterparty:
|
|
63
|
+
score -= 30
|
|
64
|
+
reasons.append("payTo differs from the expected counterparty")
|
|
65
|
+
|
|
66
|
+
# 4. Insecure endpoint.
|
|
67
|
+
if request.endpoint and not request.endpoint.lower().startswith("https://"):
|
|
68
|
+
score -= 20
|
|
69
|
+
reasons.append("endpoint is not served over HTTPS")
|
|
70
|
+
|
|
71
|
+
# 5. Spending policy (deterministic, fully offline).
|
|
72
|
+
policy_hits = _check_policy(request)
|
|
73
|
+
if policy_hits:
|
|
74
|
+
score -= 25
|
|
75
|
+
|
|
76
|
+
score = max(0, min(100, score))
|
|
77
|
+
return ScreenResult(
|
|
78
|
+
verdict=_verdict_for(score, policy_hits),
|
|
79
|
+
trust_score=score,
|
|
80
|
+
confidence=Confidence.LOW,
|
|
81
|
+
reasons=reasons,
|
|
82
|
+
policy_hits=policy_hits,
|
|
83
|
+
source="lite",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _check_policy(request: ScreenRequest) -> List[str]:
|
|
88
|
+
hits: List[str] = []
|
|
89
|
+
policy = request.policy
|
|
90
|
+
if policy is None:
|
|
91
|
+
return hits
|
|
92
|
+
if policy.max_per_call is not None and request.amount is not None:
|
|
93
|
+
if request.amount > policy.max_per_call:
|
|
94
|
+
hits.append(
|
|
95
|
+
f"amount {request.amount} exceeds max_per_call {policy.max_per_call}"
|
|
96
|
+
)
|
|
97
|
+
if policy.allowed_assets is not None and request.asset is not None:
|
|
98
|
+
if request.asset not in policy.allowed_assets:
|
|
99
|
+
hits.append(f"asset {request.asset} not in allowed_assets")
|
|
100
|
+
return hits
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _verdict_for(score: int, policy_hits: List[str]) -> Verdict:
|
|
104
|
+
if score < 20:
|
|
105
|
+
return Verdict.BLOCK
|
|
106
|
+
if policy_hits or score < 65:
|
|
107
|
+
return Verdict.REVIEW
|
|
108
|
+
return Verdict.ALLOW
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _looks_like_address(value: str) -> bool:
|
|
112
|
+
if value.startswith("0x") and len(value) == 42:
|
|
113
|
+
return all(character in "0123456789abcdef" for character in value[2:])
|
|
114
|
+
# Non-EVM identifiers (e.g. Solana base58) — length heuristic only.
|
|
115
|
+
return 32 <= len(value) <= 60
|
frisk/py.typed
ADDED
|
File without changes
|
frisk/types.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Core data types for the screening SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Verdict(str, Enum):
|
|
11
|
+
"""Recommended action for a screened transaction."""
|
|
12
|
+
|
|
13
|
+
ALLOW = "allow"
|
|
14
|
+
REVIEW = "review"
|
|
15
|
+
BLOCK = "block"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Confidence(str, Enum):
|
|
19
|
+
"""How much weight to place on the verdict.
|
|
20
|
+
|
|
21
|
+
Offline (lite) screening always reports ``LOW``; the hosted service
|
|
22
|
+
raises confidence as graph, model, and threat-feed coverage improve.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
LOW = "low"
|
|
26
|
+
MEDIUM = "medium"
|
|
27
|
+
HIGH = "high"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class Policy:
|
|
32
|
+
"""Caller-supplied spending policy. Every field is optional."""
|
|
33
|
+
|
|
34
|
+
max_per_call: Optional[float] = None
|
|
35
|
+
allowed_assets: Optional[List[str]] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class ScreenRequest:
|
|
40
|
+
"""A transaction an agent is about to perform."""
|
|
41
|
+
|
|
42
|
+
counterparty: str
|
|
43
|
+
endpoint: Optional[str] = None
|
|
44
|
+
amount: Optional[float] = None
|
|
45
|
+
asset: Optional[str] = None
|
|
46
|
+
policy: Optional[Policy] = None
|
|
47
|
+
# The payTo address the endpoint actually returned this request, if
|
|
48
|
+
# observed. Used to detect dynamic-payTo swaps.
|
|
49
|
+
observed_pay_to: Optional[str] = None
|
|
50
|
+
# Screening "temperature" in [0, 1]: 0 = permissive, 1 = paranoid. Default 0.3.
|
|
51
|
+
strictness: Optional[float] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class ScreenResult:
|
|
56
|
+
"""The outcome of screening a transaction."""
|
|
57
|
+
|
|
58
|
+
verdict: Verdict
|
|
59
|
+
trust_score: int # 0-100
|
|
60
|
+
confidence: Confidence
|
|
61
|
+
reasons: List[str] = field(default_factory=list)
|
|
62
|
+
policy_hits: List[str] = field(default_factory=list)
|
|
63
|
+
source: str = "lite" # "lite" | "hosted"
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def allowed(self) -> bool:
|
|
67
|
+
return self.verdict is Verdict.ALLOW
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: frisk-screen
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Pre-transaction risk screening for autonomous AI agents.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Jiangw2718i/frisk
|
|
6
|
+
Project-URL: Source, https://github.com/Jiangw2718i/frisk
|
|
7
|
+
Author: Frisk contributors
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Frisk contributors
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: agent-payments,ai-agents,mcp,risk,trust,x402
|
|
31
|
+
Classifier: Development Status :: 3 - Alpha
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Classifier: Topic :: Security
|
|
39
|
+
Requires-Python: >=3.10
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# Frisk
|
|
43
|
+
|
|
44
|
+
Pre-transaction risk screening for autonomous AI agents.
|
|
45
|
+
|
|
46
|
+
Before your agent pays an x402 seller or calls an unfamiliar tool, ask Frisk
|
|
47
|
+
whether the counterparty is trustworthy and whether the transaction fits your
|
|
48
|
+
policy. Frisk returns a verdict — `allow`, `review`, or `block` — with a
|
|
49
|
+
trust score and human-readable reasons. It is advisory: your agent stays in
|
|
50
|
+
control of the decision.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install frisk-screen
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from frisk import Client, Policy
|
|
62
|
+
|
|
63
|
+
client = Client() # lite mode, no key required
|
|
64
|
+
|
|
65
|
+
result = client.screen(
|
|
66
|
+
"0x9a3f1b2c3d4e5f60718293a4b5c6d7e8f9a0bc12",
|
|
67
|
+
endpoint="https://api.seller.x402/quote",
|
|
68
|
+
amount=2.50,
|
|
69
|
+
asset="USDC",
|
|
70
|
+
policy=Policy(max_per_call=5.00),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if not result.allowed:
|
|
74
|
+
print(result.verdict, result.trust_score, result.reasons)
|
|
75
|
+
# hold the payment for review
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Lite mode vs. hosted
|
|
79
|
+
|
|
80
|
+
| | Lite (default) | Hosted (API key) |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| Runs | Locally, offline | Frisk API |
|
|
83
|
+
| Signals | Public, structural checks only | Reputation graph, trained models, live threat feed |
|
|
84
|
+
| Confidence | Always `low` | Rises with coverage |
|
|
85
|
+
| Cost | Free | Usage-based |
|
|
86
|
+
|
|
87
|
+
Lite mode catches obvious problems — malformed counterparties, `payTo` swaps,
|
|
88
|
+
insecure endpoints, policy violations, and a small seed blocklist. For
|
|
89
|
+
reputation history and continuously updated threat intelligence, pass an API
|
|
90
|
+
key:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
client = Client(api_key="...")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Design
|
|
97
|
+
|
|
98
|
+
- **Zero runtime dependencies.** The SDK uses only the standard library.
|
|
99
|
+
- **Advisory, not in-path.** Frisk never holds your funds or blocks a
|
|
100
|
+
payment itself; it returns a verdict and your code decides.
|
|
101
|
+
- **Typed.** Ships with type hints (`py.typed`).
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
This project uses [uv](https://docs.astral.sh/uv/).
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv sync
|
|
109
|
+
uv run pytest
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT — see [LICENSE](LICENSE). Security disclosures: see [SECURITY.md](SECURITY.md).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
frisk/__init__.py,sha256=1nUC7qJnx94kgNtIZBo5pjRRlKQYQPoNPGptLCxr2vk,364
|
|
2
|
+
frisk/client.py,sha256=OGKIn1FbZeIh7agcLckJ1hY09PAjur72gejC58neaGg,4452
|
|
3
|
+
frisk/lite.py,sha256=bxzBUHu8xaSvk5ETtLqaI4tc3ZToe6jirm7g3BB53t8,3755
|
|
4
|
+
frisk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
frisk/types.py,sha256=fT7mRimOafZsNMVyGDTGh4tjHF-vcVxFYdG3vA_aJJA,1787
|
|
6
|
+
frisk/data/README.md,sha256=4s0nzKA6G9sSilxCodUIJdQ4MnL37lvPPbFsrnW5y3s,403
|
|
7
|
+
frisk/data/blocklist_seed.json,sha256=N1F-Xz3GaBn2H1p7uKzhkhKCQV8QVR0t76XD6wmFtXA,3
|
|
8
|
+
frisk_screen-0.0.1.dist-info/METADATA,sha256=-X3A26kF4ScWqAHXx-XZxWLtrUrslqymQe1EjFG32b0,3954
|
|
9
|
+
frisk_screen-0.0.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
10
|
+
frisk_screen-0.0.1.dist-info/licenses/LICENSE,sha256=2te30sP0itpLLXhFtzNRs-9KbkBhgNuQsw_oW2R-Dvc,1075
|
|
11
|
+
frisk_screen-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Frisk contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|