game-true402 0.1.0__tar.gz
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.
- game_true402-0.1.0/.gitignore +5 -0
- game_true402-0.1.0/PKG-INFO +107 -0
- game_true402-0.1.0/README.md +91 -0
- game_true402-0.1.0/game_true402/__init__.py +36 -0
- game_true402-0.1.0/game_true402/functions.py +160 -0
- game_true402-0.1.0/game_true402/x402.py +169 -0
- game_true402-0.1.0/pyproject.toml +26 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: game-true402
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: true402 safety stalls as Virtuals Protocol GAME functions — pay-per-call on-chain rug/honeypot & address safety for Base AI agents over x402 (USDC, no account, no API key).
|
|
5
|
+
Project-URL: Homepage, https://true402.dev
|
|
6
|
+
Project-URL: Source, https://github.com/true402/game-true402
|
|
7
|
+
Project-URL: OpenAPI, https://true402.dev/openapi.json
|
|
8
|
+
Author-email: true402 <contact@true402.dev>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: agent-tools,ai-agent,base,crypto,defi,game-by-virtuals,game-sdk,honeypot,rug-check,token-safety,virtuals,web3,x402
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: eth-account>=0.11
|
|
13
|
+
Requires-Dist: game-sdk>=0.1
|
|
14
|
+
Requires-Dist: requests>=2.28
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# game-true402
|
|
18
|
+
|
|
19
|
+
<!-- meta description: Give a Virtuals Protocol GAME agent an on-chain rug, honeypot and address-safety gate — pay per call over x402, USDC on Base, no account and no API key. -->
|
|
20
|
+
|
|
21
|
+
<p><strong>true402 safety stalls as <a href="https://docs.game.virtuals.io/">Virtuals Protocol GAME</a> functions.</strong> Give a G.A.M.E agent a pay-per-call, on-chain rug/honeypot gate for Base tokens over <a href="https://x402.org">x402</a> — USDC on Base, no account, no API key. The wallet is the identity, and the safety stalls have a free daily trial, so the functions work out of the box with no wallet configured.</p>
|
|
22
|
+
|
|
23
|
+
## §01 · Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install game-true402
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
<p>This pulls the canonical Python GAME SDK (<code>game_sdk</code>) plus the x402 payer (<code>requests</code>, <code>eth-account</code>). You also need a GAME API key from <a href="https://console.game.virtuals.io/">console.game.virtuals.io</a>.</p>
|
|
30
|
+
|
|
31
|
+
## §02 · Use
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import os
|
|
35
|
+
from game_sdk.game.worker import Worker
|
|
36
|
+
from game_true402 import true402_functions
|
|
37
|
+
|
|
38
|
+
# Reads PAYER_PRIVATE_KEY from the env (a Base wallet holding a little USDC).
|
|
39
|
+
# Omit the key to rely on the free daily trial for the safety stalls.
|
|
40
|
+
worker = Worker(
|
|
41
|
+
api_key=os.environ["GAME_API_KEY"],
|
|
42
|
+
description="A cautious on-chain trader that vets tokens before buying.",
|
|
43
|
+
instruction="Always run check_token_report before acting on a token.",
|
|
44
|
+
get_state_fn=lambda function_result, current_state: (current_state or {}),
|
|
45
|
+
action_space=true402_functions(),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
worker.run("Is token 0x… safe to buy on Base?")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
<p>To compose the functions into a full multi-worker agent instead, register the same list on a <code>WorkerConfig.action_space</code> and pass the worker to an <code>Agent(workers=[…])</code>, then call <code>agent.compile()</code> and <code>agent.run()</code>.</p>
|
|
52
|
+
|
|
53
|
+
## §03 · The functions
|
|
54
|
+
|
|
55
|
+
<p>The agent gets four functions. Each pays its stall over x402 and returns the standard GAME <code>(FunctionResultStatus, message, info)</code> tuple; the <code>info</code> dict is the full stall JSON, fed into your <code>get_state_fn</code> on the next step.</p>
|
|
56
|
+
|
|
57
|
+
<ul>
|
|
58
|
+
<li><code>check_token_report</code> — <strong>primary pre-trade gate.</strong> Composite <strong>avoid / caution / ok</strong> verdict from a real on-chain buy/sell honeypot simulation (proves sellability, not just a static scan) plus liquidity, ownership/mint and recent rug activity. Call before buying. ~$0.01.</li>
|
|
59
|
+
<li><code>check_token_safety</code> — structural safety score 0–100 and flags (honeypot sim, liquidity, mint/ownership). Lighter than the report. ~$0.005.</li>
|
|
60
|
+
<li><code>check_address_safety</code> — screen any address before you send to / approve / call it: EOA-vs-contract, ETH+USDC balance, activity, ownership, upgradeable-proxy (EIP-1967) detection. ~$0.005.</li>
|
|
61
|
+
<li><code>check_deployer</code> — deployer wallet reputation (age, contracts shipped, fresh-throwaway flag) to catch serial ruggers a structural scan can't see. ~$0.008.</li>
|
|
62
|
+
</ul>
|
|
63
|
+
|
|
64
|
+
## §04 · Configuration
|
|
65
|
+
|
|
66
|
+
<p><code>true402_functions()</code> reads the environment, or you can pass an explicit <code>PayOpts</code>:</p>
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from game_true402 import true402_functions, PayOpts
|
|
70
|
+
|
|
71
|
+
action_space = true402_functions(PayOpts(
|
|
72
|
+
payer_private_key="0x…", # a Base wallet with a little USDC (gas is sponsored; USDC only)
|
|
73
|
+
max_amount_usd=0.10, # hard per-call ceiling — refuses to sign a 402 demanding more
|
|
74
|
+
))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
<ul>
|
|
78
|
+
<li><code>PAYER_PRIVATE_KEY</code> — Base wallet key that signs x402 payments (needs USDC, not ETH). Unset means free-trial only.</li>
|
|
79
|
+
<li><code>TRUE402_BASE_URL</code> — defaults to <code>https://true402.dev/api</code>; override to point at a self-hosted instance.</li>
|
|
80
|
+
<li><code>BASE_RPC_URL</code> — defaults to <code>https://mainnet.base.org</code>; used only for a balance pre-check.</li>
|
|
81
|
+
</ul>
|
|
82
|
+
|
|
83
|
+
## §05 · Safety
|
|
84
|
+
|
|
85
|
+
<p>The payer <strong>refuses to sign</strong> anything that isn't USDC-on-Base within <code>max_amount_usd</code> (default $0.10), so a rogue or compromised endpoint can't make your agent authorize an unexpected asset, network, or amount. The private key signs locally and never leaves the process. This is the same verified x402 payer used by the CrewAI and LangChain integrations — signatures recover to the payer address.</p>
|
|
86
|
+
|
|
87
|
+
## §06 · FAQ
|
|
88
|
+
|
|
89
|
+
<ol>
|
|
90
|
+
<li><p><strong>Do I need a true402 account or API key?</strong> No. Payment is the auth: the agent POSTs, gets an HTTP 402 with payment terms, signs an EIP-3009 USDC authorization, and retries. The wallet is the identity.</p></li>
|
|
91
|
+
<li><p><strong>Does it work without a funded wallet?</strong> Yes, up to a point. The safety stalls have a free daily trial, so the functions return real results with no <code>PAYER_PRIVATE_KEY</code> set — until the trial is exhausted, then they require payment.</p></li>
|
|
92
|
+
<li><p><strong>Which function should the agent call before trading?</strong> <code>check_token_report</code>. It is the composite avoid/caution/ok gate and folds in the honeypot simulation, liquidity, ownership and deployer signal.</p></li>
|
|
93
|
+
<li><p><strong>Why Python and not the Node GAME SDK?</strong> GAME's Python SDK is the canonical reference, and its executables run synchronously — a clean match for the synchronous x402 payer, with no async wrapping. A TypeScript build can wrap the same stalls via the <code>@true402.dev/langchain</code> payer.</p></li>
|
|
94
|
+
<li><p><strong>What does the executable return to the planner?</strong> A <code>(FunctionResultStatus.DONE | .FAILED, message, info)</code> tuple. The message is an agent-readable summary; the <code>info</code> dict is the full stall JSON for your <code>get_state_fn</code>.</p></li>
|
|
95
|
+
</ol>
|
|
96
|
+
|
|
97
|
+
## §07 · Links
|
|
98
|
+
|
|
99
|
+
<ul>
|
|
100
|
+
<li>Live check in your browser: <a href="https://true402.dev/check">true402.dev/check</a></li>
|
|
101
|
+
<li>API reference: <a href="https://true402.dev/docs/api">true402.dev/docs/api</a> · OpenAPI: <a href="https://true402.dev/openapi.json">true402.dev/openapi.json</a></li>
|
|
102
|
+
<li>Also available: <a href="https://www.npmjs.com/package/@true402.dev/langchain">LangChain</a> · <a href="https://pypi.org/project/crewai-true402/">CrewAI</a> · <a href="https://www.npmjs.com/package/@true402.dev/mcp-server">MCP server</a></li>
|
|
103
|
+
</ul>
|
|
104
|
+
|
|
105
|
+
## §08 · License
|
|
106
|
+
|
|
107
|
+
<p>MIT</p>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# game-true402
|
|
2
|
+
|
|
3
|
+
<!-- meta description: Give a Virtuals Protocol GAME agent an on-chain rug, honeypot and address-safety gate — pay per call over x402, USDC on Base, no account and no API key. -->
|
|
4
|
+
|
|
5
|
+
<p><strong>true402 safety stalls as <a href="https://docs.game.virtuals.io/">Virtuals Protocol GAME</a> functions.</strong> Give a G.A.M.E agent a pay-per-call, on-chain rug/honeypot gate for Base tokens over <a href="https://x402.org">x402</a> — USDC on Base, no account, no API key. The wallet is the identity, and the safety stalls have a free daily trial, so the functions work out of the box with no wallet configured.</p>
|
|
6
|
+
|
|
7
|
+
## §01 · Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install game-true402
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
<p>This pulls the canonical Python GAME SDK (<code>game_sdk</code>) plus the x402 payer (<code>requests</code>, <code>eth-account</code>). You also need a GAME API key from <a href="https://console.game.virtuals.io/">console.game.virtuals.io</a>.</p>
|
|
14
|
+
|
|
15
|
+
## §02 · Use
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import os
|
|
19
|
+
from game_sdk.game.worker import Worker
|
|
20
|
+
from game_true402 import true402_functions
|
|
21
|
+
|
|
22
|
+
# Reads PAYER_PRIVATE_KEY from the env (a Base wallet holding a little USDC).
|
|
23
|
+
# Omit the key to rely on the free daily trial for the safety stalls.
|
|
24
|
+
worker = Worker(
|
|
25
|
+
api_key=os.environ["GAME_API_KEY"],
|
|
26
|
+
description="A cautious on-chain trader that vets tokens before buying.",
|
|
27
|
+
instruction="Always run check_token_report before acting on a token.",
|
|
28
|
+
get_state_fn=lambda function_result, current_state: (current_state or {}),
|
|
29
|
+
action_space=true402_functions(),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
worker.run("Is token 0x… safe to buy on Base?")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
<p>To compose the functions into a full multi-worker agent instead, register the same list on a <code>WorkerConfig.action_space</code> and pass the worker to an <code>Agent(workers=[…])</code>, then call <code>agent.compile()</code> and <code>agent.run()</code>.</p>
|
|
36
|
+
|
|
37
|
+
## §03 · The functions
|
|
38
|
+
|
|
39
|
+
<p>The agent gets four functions. Each pays its stall over x402 and returns the standard GAME <code>(FunctionResultStatus, message, info)</code> tuple; the <code>info</code> dict is the full stall JSON, fed into your <code>get_state_fn</code> on the next step.</p>
|
|
40
|
+
|
|
41
|
+
<ul>
|
|
42
|
+
<li><code>check_token_report</code> — <strong>primary pre-trade gate.</strong> Composite <strong>avoid / caution / ok</strong> verdict from a real on-chain buy/sell honeypot simulation (proves sellability, not just a static scan) plus liquidity, ownership/mint and recent rug activity. Call before buying. ~$0.01.</li>
|
|
43
|
+
<li><code>check_token_safety</code> — structural safety score 0–100 and flags (honeypot sim, liquidity, mint/ownership). Lighter than the report. ~$0.005.</li>
|
|
44
|
+
<li><code>check_address_safety</code> — screen any address before you send to / approve / call it: EOA-vs-contract, ETH+USDC balance, activity, ownership, upgradeable-proxy (EIP-1967) detection. ~$0.005.</li>
|
|
45
|
+
<li><code>check_deployer</code> — deployer wallet reputation (age, contracts shipped, fresh-throwaway flag) to catch serial ruggers a structural scan can't see. ~$0.008.</li>
|
|
46
|
+
</ul>
|
|
47
|
+
|
|
48
|
+
## §04 · Configuration
|
|
49
|
+
|
|
50
|
+
<p><code>true402_functions()</code> reads the environment, or you can pass an explicit <code>PayOpts</code>:</p>
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from game_true402 import true402_functions, PayOpts
|
|
54
|
+
|
|
55
|
+
action_space = true402_functions(PayOpts(
|
|
56
|
+
payer_private_key="0x…", # a Base wallet with a little USDC (gas is sponsored; USDC only)
|
|
57
|
+
max_amount_usd=0.10, # hard per-call ceiling — refuses to sign a 402 demanding more
|
|
58
|
+
))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
<ul>
|
|
62
|
+
<li><code>PAYER_PRIVATE_KEY</code> — Base wallet key that signs x402 payments (needs USDC, not ETH). Unset means free-trial only.</li>
|
|
63
|
+
<li><code>TRUE402_BASE_URL</code> — defaults to <code>https://true402.dev/api</code>; override to point at a self-hosted instance.</li>
|
|
64
|
+
<li><code>BASE_RPC_URL</code> — defaults to <code>https://mainnet.base.org</code>; used only for a balance pre-check.</li>
|
|
65
|
+
</ul>
|
|
66
|
+
|
|
67
|
+
## §05 · Safety
|
|
68
|
+
|
|
69
|
+
<p>The payer <strong>refuses to sign</strong> anything that isn't USDC-on-Base within <code>max_amount_usd</code> (default $0.10), so a rogue or compromised endpoint can't make your agent authorize an unexpected asset, network, or amount. The private key signs locally and never leaves the process. This is the same verified x402 payer used by the CrewAI and LangChain integrations — signatures recover to the payer address.</p>
|
|
70
|
+
|
|
71
|
+
## §06 · FAQ
|
|
72
|
+
|
|
73
|
+
<ol>
|
|
74
|
+
<li><p><strong>Do I need a true402 account or API key?</strong> No. Payment is the auth: the agent POSTs, gets an HTTP 402 with payment terms, signs an EIP-3009 USDC authorization, and retries. The wallet is the identity.</p></li>
|
|
75
|
+
<li><p><strong>Does it work without a funded wallet?</strong> Yes, up to a point. The safety stalls have a free daily trial, so the functions return real results with no <code>PAYER_PRIVATE_KEY</code> set — until the trial is exhausted, then they require payment.</p></li>
|
|
76
|
+
<li><p><strong>Which function should the agent call before trading?</strong> <code>check_token_report</code>. It is the composite avoid/caution/ok gate and folds in the honeypot simulation, liquidity, ownership and deployer signal.</p></li>
|
|
77
|
+
<li><p><strong>Why Python and not the Node GAME SDK?</strong> GAME's Python SDK is the canonical reference, and its executables run synchronously — a clean match for the synchronous x402 payer, with no async wrapping. A TypeScript build can wrap the same stalls via the <code>@true402.dev/langchain</code> payer.</p></li>
|
|
78
|
+
<li><p><strong>What does the executable return to the planner?</strong> A <code>(FunctionResultStatus.DONE | .FAILED, message, info)</code> tuple. The message is an agent-readable summary; the <code>info</code> dict is the full stall JSON for your <code>get_state_fn</code>.</p></li>
|
|
79
|
+
</ol>
|
|
80
|
+
|
|
81
|
+
## §07 · Links
|
|
82
|
+
|
|
83
|
+
<ul>
|
|
84
|
+
<li>Live check in your browser: <a href="https://true402.dev/check">true402.dev/check</a></li>
|
|
85
|
+
<li>API reference: <a href="https://true402.dev/docs/api">true402.dev/docs/api</a> · OpenAPI: <a href="https://true402.dev/openapi.json">true402.dev/openapi.json</a></li>
|
|
86
|
+
<li>Also available: <a href="https://www.npmjs.com/package/@true402.dev/langchain">LangChain</a> · <a href="https://pypi.org/project/crewai-true402/">CrewAI</a> · <a href="https://www.npmjs.com/package/@true402.dev/mcp-server">MCP server</a></li>
|
|
87
|
+
</ul>
|
|
88
|
+
|
|
89
|
+
## §08 · License
|
|
90
|
+
|
|
91
|
+
<p>MIT</p>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""true402 safety stalls as Virtuals Protocol GAME (G.A.M.E) custom Functions.
|
|
2
|
+
|
|
3
|
+
Give a GAME agent a pay-per-call, on-chain rug/honeypot gate for Base tokens over x402 (USDC on Base,
|
|
4
|
+
no account, no API key — the wallet is the identity).
|
|
5
|
+
|
|
6
|
+
from game_true402 import true402_functions
|
|
7
|
+
from game_sdk.game.worker import Worker
|
|
8
|
+
|
|
9
|
+
worker = Worker(
|
|
10
|
+
api_key=GAME_API_KEY,
|
|
11
|
+
description="Vets tokens before trading.",
|
|
12
|
+
instruction="Always vet a token with check_token_report before acting.",
|
|
13
|
+
get_state_fn=lambda function_result, current_state: (current_state or {}),
|
|
14
|
+
action_space=true402_functions(), # reads PAYER_PRIVATE_KEY from the env
|
|
15
|
+
)
|
|
16
|
+
"""
|
|
17
|
+
from .functions import (
|
|
18
|
+
address_safety_function,
|
|
19
|
+
deployer_check_function,
|
|
20
|
+
token_report_function,
|
|
21
|
+
token_safety_function,
|
|
22
|
+
true402_functions,
|
|
23
|
+
)
|
|
24
|
+
from .x402 import PayOpts, pay_stall, sign_payment
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
__all__ = [
|
|
28
|
+
"true402_functions",
|
|
29
|
+
"token_report_function",
|
|
30
|
+
"token_safety_function",
|
|
31
|
+
"address_safety_function",
|
|
32
|
+
"deployer_check_function",
|
|
33
|
+
"PayOpts",
|
|
34
|
+
"pay_stall",
|
|
35
|
+
"sign_payment",
|
|
36
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""true402 safety stalls as Virtuals Protocol GAME (G.A.M.E) custom Functions.
|
|
2
|
+
|
|
3
|
+
from game_true402 import true402_functions
|
|
4
|
+
action_space = true402_functions() # reads PAYER_PRIVATE_KEY from the env
|
|
5
|
+
|
|
6
|
+
Each stall is a GAME `Function` whose `executable` pays the stall over x402 (USDC on Base) and
|
|
7
|
+
returns the standard `(FunctionResultStatus, message, info)` 3-tuple. The GAME planner passes each
|
|
8
|
+
declared `Argument` to the executable BY NAME, so the executables accept keyword args plus `**kwargs`
|
|
9
|
+
(the SDK injects extra keys). `token_report` is the primary pre-trade gate.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from typing import Callable, Optional
|
|
15
|
+
|
|
16
|
+
from game_sdk.game.custom_types import Argument, Function, FunctionResultStatus
|
|
17
|
+
|
|
18
|
+
from .x402 import PayOpts, pay_stall
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _summarize(data: object) -> str:
|
|
22
|
+
"""Best-effort human/agent-readable one-liner from a stall's JSON, without inventing fields."""
|
|
23
|
+
if not isinstance(data, dict):
|
|
24
|
+
return str(data)[:300]
|
|
25
|
+
parts = []
|
|
26
|
+
for key in ("verdict", "score", "risk", "riskLevel", "reputation", "recommendation"):
|
|
27
|
+
if key in data and data[key] is not None:
|
|
28
|
+
parts.append(f"{key}={data[key]}")
|
|
29
|
+
return ", ".join(parts) if parts else json.dumps(data)[:300]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _make_executable(path: str, input_key: str, opts_provider: Callable[[], PayOpts]):
|
|
33
|
+
"""Build a GAME executable that reads `input_key` from the passed-by-name args and pays the stall."""
|
|
34
|
+
|
|
35
|
+
def executable(**kwargs):
|
|
36
|
+
value = kwargs.get(input_key)
|
|
37
|
+
if not value or not isinstance(value, str):
|
|
38
|
+
return (
|
|
39
|
+
FunctionResultStatus.FAILED,
|
|
40
|
+
f"missing required '{input_key}' (a 0x Base address)",
|
|
41
|
+
{},
|
|
42
|
+
)
|
|
43
|
+
try:
|
|
44
|
+
data = pay_stall(path, {input_key: value}, opts_provider())
|
|
45
|
+
except Exception as exc: # noqa: BLE001 — surface any failure to the planner
|
|
46
|
+
return (
|
|
47
|
+
FunctionResultStatus.FAILED,
|
|
48
|
+
f"{path} check for {value} failed: {exc}",
|
|
49
|
+
{},
|
|
50
|
+
)
|
|
51
|
+
info = data if isinstance(data, dict) else {"result": data}
|
|
52
|
+
return (
|
|
53
|
+
FunctionResultStatus.DONE,
|
|
54
|
+
f"{path} → {_summarize(data)}",
|
|
55
|
+
info,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return executable
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def token_report_function(opts_provider: Callable[[], PayOpts]) -> Function:
|
|
62
|
+
"""PRIMARY pre-trade gate — composite avoid/caution/ok verdict for a Base ERC-20."""
|
|
63
|
+
return Function(
|
|
64
|
+
fn_name="check_token_report",
|
|
65
|
+
fn_description=(
|
|
66
|
+
"Pre-trade rug/honeypot gate for a Base ERC-20 token. Runs a real on-chain buy/sell "
|
|
67
|
+
"honeypot simulation (proves sellability, not just a static scan) plus liquidity, "
|
|
68
|
+
"ownership/mint and recent rug activity, and returns a composite avoid/caution/ok "
|
|
69
|
+
"verdict. Call this BEFORE buying any token. Pays ~$0.01 USDC on Base over x402."
|
|
70
|
+
),
|
|
71
|
+
args=[
|
|
72
|
+
Argument(
|
|
73
|
+
name="token",
|
|
74
|
+
type="string",
|
|
75
|
+
description="The Base ERC-20 token contract address to vet (0x…).",
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
executable=_make_executable("/v1/base/token-report", "token", opts_provider),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def token_safety_function(opts_provider: Callable[[], PayOpts]) -> Function:
|
|
83
|
+
"""Structural safety score 0-100 + flags (lighter than token_report)."""
|
|
84
|
+
return Function(
|
|
85
|
+
fn_name="check_token_safety",
|
|
86
|
+
fn_description=(
|
|
87
|
+
"Structural safety score (0–100) and flags for a Base ERC-20 token: honeypot simulation, "
|
|
88
|
+
"liquidity, mint/ownership/blacklist. Lighter than check_token_report. "
|
|
89
|
+
"Pays ~$0.005 USDC on Base over x402."
|
|
90
|
+
),
|
|
91
|
+
args=[
|
|
92
|
+
Argument(
|
|
93
|
+
name="token",
|
|
94
|
+
type="string",
|
|
95
|
+
description="The Base ERC-20 token contract address to score (0x…).",
|
|
96
|
+
),
|
|
97
|
+
],
|
|
98
|
+
executable=_make_executable("/v1/token-safety", "token", opts_provider),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def address_safety_function(opts_provider: Callable[[], PayOpts]) -> Function:
|
|
103
|
+
"""Screen any Base address before send / approve / call."""
|
|
104
|
+
return Function(
|
|
105
|
+
fn_name="check_address_safety",
|
|
106
|
+
fn_description=(
|
|
107
|
+
"Screen any Base address before you send to, approve, or call it: EOA-vs-contract, "
|
|
108
|
+
"ETH+USDC balance, activity, ownership and upgradeable-proxy (EIP-1967) detection. "
|
|
109
|
+
"Pays ~$0.005 USDC on Base over x402."
|
|
110
|
+
),
|
|
111
|
+
args=[
|
|
112
|
+
Argument(
|
|
113
|
+
name="address",
|
|
114
|
+
type="string",
|
|
115
|
+
description="Any Base address to screen (0x…) — an EOA or a contract.",
|
|
116
|
+
),
|
|
117
|
+
],
|
|
118
|
+
executable=_make_executable("/v1/base/address-safety", "address", opts_provider),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def deployer_check_function(opts_provider: Callable[[], PayOpts]) -> Function:
|
|
123
|
+
"""Deployer wallet reputation — catches serial ruggers a structural scan can't see."""
|
|
124
|
+
return Function(
|
|
125
|
+
fn_name="check_deployer",
|
|
126
|
+
fn_description=(
|
|
127
|
+
"Deployer reputation for a Base token: resolves who created it and that wallet's track "
|
|
128
|
+
"record (age, contracts shipped, fresh-throwaway flag) to catch serial ruggers a "
|
|
129
|
+
"structural check can't see. Pays ~$0.008 USDC on Base over x402."
|
|
130
|
+
),
|
|
131
|
+
args=[
|
|
132
|
+
Argument(
|
|
133
|
+
name="token",
|
|
134
|
+
type="string",
|
|
135
|
+
description="The Base ERC-20 token contract address whose deployer to check (0x…).",
|
|
136
|
+
),
|
|
137
|
+
],
|
|
138
|
+
executable=_make_executable("/v1/base/deployer-check", "token", opts_provider),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def true402_functions(opts: Optional[PayOpts] = None) -> list[Function]:
|
|
143
|
+
"""The four true402 safety Functions, ready to drop into a GAME Worker `action_space`.
|
|
144
|
+
|
|
145
|
+
Pass a `PayOpts`, or leave `None` to read PAYER_PRIVATE_KEY / TRUE402_BASE_URL / BASE_RPC_URL from
|
|
146
|
+
the environment. The safety stalls have a free daily trial, so the functions return real results
|
|
147
|
+
even with no wallet configured — until the trial is exhausted, then they require payment.
|
|
148
|
+
`check_token_report` is the primary pre-trade gate.
|
|
149
|
+
"""
|
|
150
|
+
resolved = opts or PayOpts.from_env()
|
|
151
|
+
|
|
152
|
+
def opts_provider() -> PayOpts:
|
|
153
|
+
return resolved
|
|
154
|
+
|
|
155
|
+
return [
|
|
156
|
+
token_report_function(opts_provider),
|
|
157
|
+
token_safety_function(opts_provider),
|
|
158
|
+
address_safety_function(opts_provider),
|
|
159
|
+
deployer_check_function(opts_provider),
|
|
160
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Pay any true402 stall over x402 and return its JSON.
|
|
2
|
+
|
|
3
|
+
The whole protocol, in one function: POST → 402 with payment terms → sign an EIP-3009 USDC
|
|
4
|
+
authorization → retry with an X-PAYMENT header → 200. No accounts, no API keys; the wallet is the
|
|
5
|
+
identity. USDC on Base, gas sponsored by the facilitator (the payer needs only USDC, not ETH).
|
|
6
|
+
|
|
7
|
+
Safety: the client REFUSES to sign anything that isn't USDC-on-Base within a caller-set cap, so a
|
|
8
|
+
rogue/compromised endpoint (or a MITM past TLS) can't make the agent authorize an unexpected
|
|
9
|
+
asset/network or an excessive amount.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import secrets
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import requests
|
|
21
|
+
from eth_account import Account
|
|
22
|
+
from eth_account.messages import encode_typed_data
|
|
23
|
+
|
|
24
|
+
BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
25
|
+
BASE_NETWORK = "eip155:8453"
|
|
26
|
+
BASE_CHAIN_ID = 8453
|
|
27
|
+
DEFAULT_BASE_URL = "https://true402.dev/api"
|
|
28
|
+
DEFAULT_RPC_URL = "https://mainnet.base.org"
|
|
29
|
+
_BALANCE_OF_SELECTOR = "0x70a08231"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class PayOpts:
|
|
34
|
+
"""Configuration for paying true402 stalls."""
|
|
35
|
+
|
|
36
|
+
payer_private_key: str
|
|
37
|
+
"""A Base wallet private key holding a little USDC (the payer)."""
|
|
38
|
+
base_url: str = DEFAULT_BASE_URL
|
|
39
|
+
rpc_url: str = DEFAULT_RPC_URL
|
|
40
|
+
max_amount_usd: float = 0.10
|
|
41
|
+
"""Hard ceiling, in USDC, on a single signed payment. The client refuses a 402 demanding more."""
|
|
42
|
+
timeout: float = 30.0
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_env(cls, **overrides: Any) -> "PayOpts":
|
|
46
|
+
"""Build from PAYER_PRIVATE_KEY / TRUE402_BASE_URL / BASE_RPC_URL env vars."""
|
|
47
|
+
key = overrides.pop("payer_private_key", None) or os.environ.get("PAYER_PRIVATE_KEY", "")
|
|
48
|
+
return cls(
|
|
49
|
+
payer_private_key=key,
|
|
50
|
+
base_url=overrides.pop("base_url", None) or os.environ.get("TRUE402_BASE_URL", DEFAULT_BASE_URL),
|
|
51
|
+
rpc_url=overrides.pop("rpc_url", None) or os.environ.get("BASE_RPC_URL", DEFAULT_RPC_URL),
|
|
52
|
+
**overrides,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _usdc_balance(rpc_url: str, holder: str, timeout: float) -> int:
|
|
57
|
+
data = _BALANCE_OF_SELECTOR + holder[2:].rjust(64, "0")
|
|
58
|
+
body = {"jsonrpc": "2.0", "id": 1, "method": "eth_call", "params": [{"to": BASE_USDC, "data": data}, "latest"]}
|
|
59
|
+
r = requests.post(rpc_url, json=body, timeout=timeout, headers={"content-type": "application/json"})
|
|
60
|
+
r.raise_for_status()
|
|
61
|
+
return int(r.json()["result"], 16)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def sign_payment(accept: dict, opts: PayOpts) -> str:
|
|
65
|
+
"""Sign an EIP-3009 authorization for the given 402 `accepts[]` entry → base64 X-PAYMENT value."""
|
|
66
|
+
key = opts.payer_private_key
|
|
67
|
+
if not key:
|
|
68
|
+
raise ValueError("no payer_private_key set — cannot pay (set PAYER_PRIVATE_KEY or PayOpts.payer_private_key)")
|
|
69
|
+
if not key.startswith("0x"):
|
|
70
|
+
key = "0x" + key
|
|
71
|
+
account = Account.from_key(key)
|
|
72
|
+
|
|
73
|
+
network = accept.get("network")
|
|
74
|
+
if network and network != BASE_NETWORK:
|
|
75
|
+
raise ValueError(f'unexpected payment network "{network}" (expected {BASE_NETWORK}) — refusing to sign')
|
|
76
|
+
asset = accept.get("asset", "")
|
|
77
|
+
if asset.lower() != BASE_USDC.lower():
|
|
78
|
+
raise ValueError(f"unexpected payment asset {asset} (expected Base USDC) — refusing to sign")
|
|
79
|
+
|
|
80
|
+
value = int(accept.get("amount") or accept.get("maxAmountRequired") or "0")
|
|
81
|
+
cap_atomic = round(opts.max_amount_usd * 1e6)
|
|
82
|
+
if value > cap_atomic:
|
|
83
|
+
raise ValueError(f"402 demands {value} USDC base units, over the ${opts.max_amount_usd} cap — refusing to sign")
|
|
84
|
+
|
|
85
|
+
held = _usdc_balance(opts.rpc_url, account.address, opts.timeout)
|
|
86
|
+
if held < value:
|
|
87
|
+
raise ValueError(f"payer {account.address} holds {held} < {value} USDC base units — fund it")
|
|
88
|
+
|
|
89
|
+
now = int(time.time())
|
|
90
|
+
valid_before = now + int(accept.get("maxTimeoutSeconds") or 120)
|
|
91
|
+
nonce = "0x" + secrets.token_hex(32)
|
|
92
|
+
extra = accept.get("extra") or {}
|
|
93
|
+
authorization = {
|
|
94
|
+
"from": account.address,
|
|
95
|
+
"to": accept["payTo"],
|
|
96
|
+
"value": value,
|
|
97
|
+
"validAfter": now - 60,
|
|
98
|
+
"validBefore": valid_before,
|
|
99
|
+
"nonce": bytes.fromhex(nonce[2:]),
|
|
100
|
+
}
|
|
101
|
+
signable = encode_typed_data(
|
|
102
|
+
domain_data={
|
|
103
|
+
"name": extra.get("name", "USD Coin"),
|
|
104
|
+
"version": extra.get("version", "2"),
|
|
105
|
+
"chainId": BASE_CHAIN_ID,
|
|
106
|
+
"verifyingContract": BASE_USDC,
|
|
107
|
+
},
|
|
108
|
+
message_types={
|
|
109
|
+
"TransferWithAuthorization": [
|
|
110
|
+
{"name": "from", "type": "address"},
|
|
111
|
+
{"name": "to", "type": "address"},
|
|
112
|
+
{"name": "value", "type": "uint256"},
|
|
113
|
+
{"name": "validAfter", "type": "uint256"},
|
|
114
|
+
{"name": "validBefore", "type": "uint256"},
|
|
115
|
+
{"name": "nonce", "type": "bytes32"},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
message_data=authorization,
|
|
119
|
+
)
|
|
120
|
+
signature = Account.sign_message(signable, key).signature.hex()
|
|
121
|
+
if not signature.startswith("0x"):
|
|
122
|
+
signature = "0x" + signature
|
|
123
|
+
|
|
124
|
+
payment = {
|
|
125
|
+
"x402Version": 2,
|
|
126
|
+
"scheme": "exact",
|
|
127
|
+
"network": network,
|
|
128
|
+
"payload": {
|
|
129
|
+
"signature": signature,
|
|
130
|
+
"authorization": {
|
|
131
|
+
"from": authorization["from"],
|
|
132
|
+
"to": authorization["to"],
|
|
133
|
+
"value": str(value),
|
|
134
|
+
"validAfter": str(authorization["validAfter"]),
|
|
135
|
+
"validBefore": str(authorization["validBefore"]),
|
|
136
|
+
"nonce": nonce,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
import base64
|
|
141
|
+
|
|
142
|
+
return base64.b64encode(json.dumps(payment).encode()).decode()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def pay_stall(path: str, payload: dict, opts: PayOpts) -> Any:
|
|
146
|
+
"""POST `payload` to a true402 stall path (e.g. '/v1/base/token-report'), paying over x402 if asked.
|
|
147
|
+
|
|
148
|
+
If the first response is 200 (a free-trial call), returns it without paying. If it's 402, signs and
|
|
149
|
+
retries. Raises on any other status or on a refused/insufficient payment.
|
|
150
|
+
"""
|
|
151
|
+
url = opts.base_url.rstrip("/") + path
|
|
152
|
+
headers = {"content-type": "application/json"}
|
|
153
|
+
first = requests.post(url, json=payload, headers=headers, timeout=opts.timeout)
|
|
154
|
+
if first.status_code == 200:
|
|
155
|
+
return first.json() # served free (free trial) — no payment needed
|
|
156
|
+
if first.status_code != 402:
|
|
157
|
+
raise RuntimeError(f"expected HTTP 402 from {path}, got {first.status_code}: {first.text[:200]}")
|
|
158
|
+
|
|
159
|
+
challenge = first.json()
|
|
160
|
+
accepts = challenge.get("accepts") or []
|
|
161
|
+
accept = next((a for a in accepts if a.get("scheme") == "exact"), None)
|
|
162
|
+
if not accept:
|
|
163
|
+
raise RuntimeError('no x402 "exact" payment requirement in the 402')
|
|
164
|
+
|
|
165
|
+
x_payment = sign_payment(accept, opts)
|
|
166
|
+
paid = requests.post(url, json=payload, headers={**headers, "X-PAYMENT": x_payment}, timeout=opts.timeout)
|
|
167
|
+
if paid.status_code != 200:
|
|
168
|
+
raise RuntimeError(f"paid request to {path} failed (HTTP {paid.status_code}): {paid.text[:200]}")
|
|
169
|
+
return paid.json()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "game-true402"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "true402 safety stalls as Virtuals Protocol GAME functions — pay-per-call on-chain rug/honeypot & address safety for Base AI agents over x402 (USDC, no account, no API key)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
keywords = ["virtuals", "game-sdk", "game-by-virtuals", "x402", "base", "rug-check", "honeypot", "token-safety", "crypto", "ai-agent", "agent-tools", "defi", "web3"]
|
|
13
|
+
authors = [{ name = "true402", email = "contact@true402.dev" }]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"game_sdk>=0.1",
|
|
16
|
+
"requests>=2.28",
|
|
17
|
+
"eth-account>=0.11",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://true402.dev"
|
|
22
|
+
Source = "https://github.com/true402/game-true402"
|
|
23
|
+
"OpenAPI" = "https://true402.dev/openapi.json"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["game_true402"]
|