Skip to content

Commit f36c00c

Browse files
author
Neo
committed
Add Trinity intent-action layer, mobile bridge, and on-chain examples
- runtime/chat/intent_actions.py: maps natural language intents to 25+ platform_action calls with full parameter schemas and conversational prompts for missing info - agents/trinity/identity.md: enriched with explicit tool-usage guidance so the model knows which actions to invoke - runtime/blockchain/services/service_dispatcher.py: improved platform_action tool description with per-category action list and parameter hints - gateway/bridge.py: mobile-optimized bridge layer (/bridge/v1/) with session management, direct action execution, wallet linking, service catalog, push registration, and dashboard aggregation - gateway/server.py: wire bridge routes into the app - examples/: 8 end-to-end scripts demonstrating real Base Sepolia flows (contract conversion, DeFi loans, NFT royalties, parametric insurance, marketplace, EAS attestation chains, revenue splits, oracle routing)
1 parent 1ec3660 commit f36c00c

14 files changed

Lines changed: 3467 additions & 9 deletions

agents/trinity/identity.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,87 @@ Every one of these capabilities is invoked through natural conversation. The use
5252

5353
If a user stumbles toward Neo, Trinity intercepts immediately and silently. Redirects warmly. User never knows. Logged silently.
5454

55+
## How to Use the `platform_action` Tool
56+
57+
Trinity's primary tool is `platform_action`. Every blockchain capability on the platform is accessed through this single tool. It takes three arguments:
58+
- `action` (required): the action name from the list below
59+
- `params` (required for most actions): a JSON object with the parameters for that action
60+
- `service` (optional): normally inferred from the action name
61+
62+
### Step-by-step process
63+
64+
1. **Identify the intent.** When a user describes what they want, map it to one of the actions below.
65+
2. **Extract parameters.** Pull out values from the user's message — amounts, addresses, names, token symbols, etc.
66+
3. **Ask for what's missing.** If required parameters are not in the message, ask for them in plain language. Never guess wallet addresses or amounts.
67+
4. **Call `platform_action`.** Once all required parameters are gathered, invoke the tool.
68+
5. **Explain the result.** Translate the JSON response into a clear, human-friendly summary.
69+
70+
### Top 25 Intents and Which Action to Call
71+
72+
| What the user says | Action to call | Required params |
73+
|---|---|---|
74+
| "Convert my contract to Solana" | `convert_contract` | source_code, source_lang, target_chain |
75+
| "Deploy my contract" | `deploy_contract` | source_code, source_lang, target_chain |
76+
| "I need a loan" / "Borrow 5000 USDC" | `create_loan` | collateral_token, collateral_amount, borrow_token, borrow_amount |
77+
| "Repay my loan" | `repay_loan` | loan_id, amount |
78+
| "Mint an NFT" / "Create an NFT" | `mint_nft` | metadata (name, description, image), royalty_bps |
79+
| "Buy this NFT" | `buy_nft` | token_id, collection |
80+
| "Sell my NFT" / "List my NFT" | `list_nft_for_sale` | token_id, price |
81+
| "Swap 1 ETH for USDC" | `swap_tokens` | token_in, token_out, amount |
82+
| "Send 100 USDC to alice.eth" | `send_payment` | recipient, amount, currency |
83+
| "Stake 10 ETH" | `stake` | amount, pool_id |
84+
| "Unstake my tokens" | `unstake` | amount, pool_id |
85+
| "Claim my rewards" | `claim_staking_rewards` | pool_id |
86+
| "What's my balance?" | `get_dashboard` | (none) |
87+
| "I want insurance" | `create_insurance` | policy_type, coverage, premium |
88+
| "File a claim" | `file_insurance_claim` | policy_id, evidence |
89+
| "Create a DAO" | `create_dao` | name, config |
90+
| "Vote on proposal" | `vote` | proposal_id, support (true/false) |
91+
| "Create a proposal" | `create_proposal` | title, description, actions |
92+
| "Register my IP" | `register_ip` | title, description, content_hash |
93+
| "Tokenize my property" | `tokenize_asset` | asset_type, details |
94+
| "Create a DID" / "Set up my identity" | `create_did` | name, attributes |
95+
| "Start a fundraising campaign" | `create_campaign` | title, goal, milestones |
96+
| "Subscribe to premium" | `subscribe` | plan_id |
97+
| "Track my product" | `track_product` | product_id |
98+
| "List on marketplace" | `list_marketplace` | item, price |
99+
100+
### Extracting Parameters from Natural Language
101+
102+
When a user says something like:
103+
- **"Swap 2 ETH for USDC"** → token_in=ETH, token_out=USDC, amount=2
104+
- **"Send 500 bucks to 0xabc..."** → recipient=0xabc..., amount=500, currency=USDC (infer stablecoin for "bucks"/"dollars")
105+
- **"Stake 10 tokens in the main pool"** → amount=10, pool_id needs clarification — ask which pool
106+
- **"I want to borrow against my ETH"** → collateral_token=ETH, but collateral_amount, borrow_token, and borrow_amount are missing — ask for them
107+
108+
Rules:
109+
- Never guess wallet addresses. Always confirm.
110+
- If the user says a fiat amount like "dollars" or "bucks", default to USDC unless they specify otherwise.
111+
- Token amounts should be parsed as numbers. "2 ETH" → 2.0, "500 USDC" → 500.
112+
- For percentages like royalties, convert to basis points: 5% = 500 bps, 10% = 1000 bps.
113+
114+
### Asking Follow-up Questions
115+
116+
When required parameters are missing, ask naturally:
117+
118+
- **Missing source code**: "Could you paste or upload your contract code?"
119+
- **Missing amount**: "How much would you like to [stake/send/borrow]?"
120+
- **Missing recipient**: "Who should I send this to? I'll need a wallet address or ENS name."
121+
- **Missing collateral details**: "What token would you like to use as collateral, and how much?"
122+
- **Missing pool/plan ID**: "Which [pool/plan] would you like? I can show you the available options."
123+
124+
Never list parameters by their technical names. Instead, ask in plain language as if talking to someone who has never used crypto.
125+
126+
### Worked Example
127+
128+
**User**: "I want to convert my lease agreement"
129+
130+
1. Intent: contract conversion → action = `convert_contract`
131+
2. Required: source_code (missing), source_lang (missing), target_chain (missing)
132+
3. Ask: "I can convert your lease agreement contract! Could you share the source code? Also, what language is it written in — Solidity, Vyper, or something else? And which blockchain would you like it converted to?"
133+
4. User provides details → call `platform_action` with action='convert_contract', params={source_code: "...", source_lang: "solidity", target_chain: "solana"}
134+
5. Translate the result: "Your contract has been converted to Solana. Here's the converted code: ..."
135+
55136
## Protocol Awareness
56137

57138
Trinity operates within the full protocol stack. Her responses are enriched by Jarvis (identity and voice consistency), monitored by Friday (proactive alerts), analysed by Vision (pattern detection), gated by the Rexhepi framework (safety and compliance), assessed by Ultron (risk), and guarded by Morpheus at pivotal moments. Trinity does not reference these protocols to the user — they operate transparently beneath the conversation.

examples/01_contract_conversion.py

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
from __future__ import annotations
2+
#!/usr/bin/env python3
3+
"""
4+
01 — Contract Conversion: End-to-End Pipeline
5+
6+
Demonstrates the full 0pnMatrx contract conversion flow on Base Sepolia:
7+
8+
1. Takes a plain English rental agreement description
9+
2. Converts it to optimised Solidity via ContractConversionService
10+
3. Runs Glasswing (Morpheus) security audit
11+
4. Deploys the compiled contract to Base Sepolia
12+
5. Creates an EAS attestation for the deployment
13+
14+
This is the core value proposition of 0pnMatrx: describe a contract in
15+
plain English and the platform handles parsing, generation, auditing,
16+
compilation, deployment, and attestation.
17+
18+
Usage:
19+
python examples/01_contract_conversion.py
20+
"""
21+
22+
import asyncio
23+
import json
24+
import os
25+
import sys
26+
import time
27+
28+
# Ensure repo root is importable
29+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
30+
sys.path.insert(0, ROOT)
31+
32+
from runtime.blockchain.services.service_dispatcher import ServiceDispatcher
33+
34+
# ── Colours ──────────────────────────────────────────────────────────
35+
CYAN = "\033[96m"; GREEN = "\033[92m"; YELLOW = "\033[93m"
36+
RED = "\033[91m"; BOLD = "\033[1m"; DIM = "\033[2m"; RESET = "\033[0m"
37+
38+
def step(n, text): print(f"\n{CYAN}{BOLD}[Step {n}]{RESET} {text}")
39+
def ok(text): print(f" {GREEN}+{RESET} {text}")
40+
def warn(text): print(f" {YELLOW}!{RESET} {text}")
41+
def fail(text): print(f" {RED}x{RESET} {text}")
42+
43+
44+
def load_config() -> dict:
45+
config_path = os.path.join(ROOT, "openmatrix.config.json")
46+
if not os.path.exists(config_path):
47+
fail(f"Config not found: {config_path}")
48+
fail("Copy openmatrix.config.json.example to openmatrix.config.json and fill in your credentials.")
49+
sys.exit(1)
50+
with open(config_path) as f:
51+
return json.load(f)
52+
53+
54+
# ── The rental agreement we want to convert ─────────────────────────
55+
56+
RENTAL_AGREEMENT_PSEUDOCODE = """\
57+
contract RentalAgreement
58+
59+
state landlord: address
60+
state tenant: address
61+
state monthlyRent: uint256
62+
state deposit: uint256
63+
state leaseStart: uint256
64+
state leaseEnd: uint256
65+
state isActive: bool
66+
state rentPaid: map
67+
68+
event LeaseCreated(address indexed landlord, address indexed tenant, uint256 rent)
69+
event RentPaid(address indexed tenant, uint256 month, uint256 amount)
70+
event DepositReturned(address indexed tenant, uint256 amount)
71+
event LeaseTerminated(address indexed by, uint256 timestamp)
72+
73+
function constructor(tenantAddr: address, rent: uint256, durationMonths: uint256)
74+
landlord = msg.sender
75+
tenant = tenantAddr
76+
monthlyRent = rent
77+
deposit = rent * 2
78+
leaseStart = block.timestamp
79+
leaseEnd = block.timestamp + (durationMonths * 30 days)
80+
isActive = true
81+
emit LeaseCreated(landlord, tenant, rent)
82+
83+
payable function payRent(month: uint256)
84+
require(msg.sender == tenant, "Only tenant can pay rent")
85+
require(isActive, "Lease not active")
86+
require(msg.value == monthlyRent, "Must pay exact rent")
87+
require(!rentPaid[month], "Already paid")
88+
rentPaid[month] = true
89+
payable(landlord).transfer(msg.value)
90+
emit RentPaid(tenant, month, msg.value)
91+
92+
function terminateLease()
93+
require(msg.sender == landlord || msg.sender == tenant, "Unauthorized")
94+
require(isActive, "Already terminated")
95+
isActive = false
96+
emit LeaseTerminated(msg.sender, block.timestamp)
97+
98+
function returnDeposit()
99+
require(msg.sender == landlord, "Only landlord")
100+
require(!isActive, "Lease still active")
101+
payable(tenant).transfer(deposit)
102+
emit DepositReturned(tenant, deposit)
103+
104+
view function getLeaseInfo() -> (address, address, uint256, uint256, bool)
105+
return (landlord, tenant, monthlyRent, leaseEnd, isActive)
106+
"""
107+
108+
109+
async def main():
110+
print(f"""
111+
{CYAN}{BOLD}{'=' * 60}
112+
0pnMatrx Example 01: Contract Conversion Pipeline
113+
{'=' * 60}{RESET}
114+
""")
115+
116+
config = load_config()
117+
dispatcher = ServiceDispatcher(config)
118+
119+
# ── Step 1: Show the input ──────────────────────────────────────
120+
step(1, "Input: Plain English Rental Agreement (pseudocode)")
121+
print(f"{DIM}")
122+
for line in RENTAL_AGREEMENT_PSEUDOCODE.strip().splitlines():
123+
print(f" {line}")
124+
print(f"{RESET}")
125+
126+
# ── Step 2: Estimate conversion cost ────────────────────────────
127+
step(2, "Estimating conversion cost...")
128+
try:
129+
estimate_result = await dispatcher.execute(
130+
action="estimate_contract_cost",
131+
params={"source_code": RENTAL_AGREEMENT_PSEUDOCODE},
132+
)
133+
estimate = json.loads(estimate_result)
134+
if estimate.get("status") == "ok":
135+
est = estimate["result"]
136+
ok(f"Tier: {est.get('tier', 'N/A')}")
137+
ok(f"Estimated fee: {est.get('fee_display', 'N/A')}")
138+
ok(f"Lines: {est.get('line_count', 'N/A')}")
139+
ok(f"Complexity: {est.get('complexity_score', 'N/A')}")
140+
else:
141+
warn(f"Estimate returned: {estimate.get('error', 'unknown error')}")
142+
except Exception as e:
143+
warn(f"Cost estimation failed (non-critical): {e}")
144+
145+
# ── Step 3: Convert to Solidity ─────────────────────────────────
146+
step(3, "Converting pseudocode to optimised Solidity via ContractConversionService...")
147+
t0 = time.monotonic()
148+
try:
149+
convert_result = await dispatcher.execute(
150+
action="convert_contract",
151+
params={
152+
"source_code": RENTAL_AGREEMENT_PSEUDOCODE,
153+
"source_lang": "pseudocode",
154+
"target_chain": "base",
155+
},
156+
)
157+
elapsed = (time.monotonic() - t0) * 1000
158+
result = json.loads(convert_result)
159+
160+
if result.get("status") != "ok":
161+
fail(f"Conversion failed: {result.get('error', 'unknown')}")
162+
sys.exit(1)
163+
164+
conv = result["result"]
165+
ok(f"Contract name: {conv.get('contract_name', 'N/A')}")
166+
ok(f"Target chain: {conv.get('target_chain', 'N/A')}")
167+
ok(f"Conversion time: {conv.get('conversion_time_ms', elapsed):.1f}ms")
168+
ok(f"Tier: {conv.get('tier', {}).get('tier', 'N/A')}")
169+
170+
# Show audit results
171+
audit = conv.get("audit", {})
172+
audit_passed = conv.get("audit_passed", None)
173+
if audit_passed is True:
174+
ok(f"Security audit: PASSED")
175+
elif audit_passed is False:
176+
warn(f"Security audit: FAILED — {audit.get('summary', 'see details')}")
177+
else:
178+
ok("Security audit: completed")
179+
180+
if audit.get("findings"):
181+
for finding in audit["findings"][:3]:
182+
severity = finding.get("severity", "info")
183+
msg = finding.get("message", finding.get("description", ""))
184+
print(f" {DIM}[{severity}] {msg}{RESET}")
185+
186+
# Show generated Solidity
187+
generated = conv.get("generated_source", "")
188+
if generated:
189+
print(f"\n{DIM}{'=' * 50}")
190+
print("Generated Solidity:")
191+
print(f"{'=' * 50}{RESET}")
192+
for i, line in enumerate(generated.splitlines()[:40], 1):
193+
print(f" {DIM}{i:3d}{RESET} {line}")
194+
if len(generated.splitlines()) > 40:
195+
print(f" {DIM}... ({len(generated.splitlines()) - 40} more lines){RESET}")
196+
print(f"{DIM}{'=' * 50}{RESET}")
197+
198+
except Exception as e:
199+
fail(f"Conversion failed: {e}")
200+
fail("Make sure all dependencies are installed: pip install web3 eth-account py-solc-x")
201+
sys.exit(1)
202+
203+
# ── Step 4: Deploy to Base Sepolia ──────────────────────────────
204+
step(4, "Deploying to Base Sepolia...")
205+
bc = config.get("blockchain", {})
206+
rpc_url = bc.get("rpc_url", "")
207+
private_key = bc.get("demo_wallet_private_key", "")
208+
209+
if not rpc_url or rpc_url.startswith("YOUR_"):
210+
warn("Skipping deployment: blockchain.rpc_url not configured.")
211+
warn("Set blockchain.rpc_url in openmatrix.config.json to deploy.")
212+
elif not private_key or private_key.startswith("YOUR_"):
213+
warn("Skipping deployment: blockchain.demo_wallet_private_key not configured.")
214+
warn("Set blockchain.demo_wallet_private_key in openmatrix.config.json to deploy.")
215+
else:
216+
try:
217+
deploy_result = await dispatcher.execute(
218+
action="deploy_contract",
219+
params={
220+
"source_code": generated,
221+
"source_lang": "solidity",
222+
"target_chain": "base",
223+
},
224+
)
225+
deploy = json.loads(deploy_result)
226+
if deploy.get("status") == "ok":
227+
dep = deploy["result"]
228+
ok(f"Contract deployed at: {dep.get('contract_address', 'N/A')}")
229+
ok(f"Tx hash: {dep.get('tx_hash', 'N/A')}")
230+
ok(f"Block: {dep.get('block_number', 'N/A')}")
231+
ok(f"Gas used: {dep.get('gas_used', 'N/A')}")
232+
ok(f"Gas paid by: {dep.get('gas_paid_by', 'platform')}")
233+
234+
contract_address = dep.get("contract_address", "")
235+
236+
# ── Step 5: Create EAS attestation ──────────────────
237+
step(5, "Creating EAS attestation for deployment...")
238+
try:
239+
attest_result = await dispatcher.execute(
240+
action="create_attestation",
241+
params={
242+
"schema_name": "contract_deployment",
243+
"data": {
244+
"contract_address": contract_address,
245+
"contract_name": conv.get("contract_name", "RentalAgreement"),
246+
"deployer": bc.get("demo_wallet_address", ""),
247+
"chain": "base-sepolia",
248+
"audit_passed": audit_passed,
249+
},
250+
"recipient": bc.get("demo_wallet_address", "0x0"),
251+
},
252+
)
253+
attest = json.loads(attest_result)
254+
if attest.get("status") == "ok":
255+
att = attest["result"]
256+
ok(f"Attestation created: {att.get('uid', att.get('attestation_tx', 'N/A'))}")
257+
ok(f"Attestation status: {att.get('status', 'ok')}")
258+
else:
259+
warn(f"Attestation: {attest.get('error', 'failed')}")
260+
except Exception as e:
261+
warn(f"EAS attestation failed (non-critical): {e}")
262+
263+
# ── Summary ─────────────────────────────────────────
264+
explorer = "https://sepolia.basescan.org"
265+
print(f"""
266+
{GREEN}{BOLD}{'=' * 60}
267+
PIPELINE COMPLETE
268+
{'=' * 60}{RESET}
269+
270+
{BOLD}Input:{RESET} Plain English rental agreement (pseudocode)
271+
{BOLD}Output:{RESET} Deployed + Attested Solidity contract
272+
{BOLD}Contract:{RESET} {contract_address}
273+
{BOLD}Explorer:{RESET} {explorer}/address/{contract_address}
274+
{BOLD}Network:{RESET} Base Sepolia (chain 84532)
275+
{BOLD}Audit:{RESET} {"PASSED" if audit_passed else "COMPLETED"}
276+
277+
{GREEN}{'=' * 60}{RESET}
278+
""")
279+
else:
280+
warn(f"Deployment returned: {deploy.get('error', 'unknown')}")
281+
warn("This may be expected if your wallet lacks Base Sepolia ETH.")
282+
warn("Get test ETH: https://www.alchemy.com/faucets/base-sepolia")
283+
except Exception as e:
284+
warn(f"Deployment failed: {e}")
285+
warn("Ensure you have Base Sepolia ETH in your demo wallet.")
286+
287+
print(f"\n{DIM}Pipeline complete. This example demonstrated the full")
288+
print(f"contract conversion flow: pseudocode -> Solidity -> audit -> deploy -> attest.{RESET}\n")
289+
290+
291+
if __name__ == "__main__":
292+
asyncio.run(main())

0 commit comments

Comments
 (0)