Skip to content

Commit e9f7e4f

Browse files
Bradley Lubetkinclaude
authored andcommitted
feat(python): Add Nory x402 payment plugin
Adds NoryX402Plugin for AI agents to make payments using the x402 HTTP protocol. New kernel functions: - get_payment_requirements: Get payment requirements for a resource - verify_payment: Verify signed payment transactions - settle_payment: Submit payments for on-chain settlement - lookup_transaction: Check transaction status - health_check: Check service health Features: - Supports Solana and 7 EVM chains - Sub-400ms payment settlement - Optional API key authentication - Async HTTP requests with aiohttp 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b0d621f commit e9f7e4f

File tree

2 files changed

+192
-0
lines changed

2 files changed

+192
-0
lines changed

python/semantic_kernel/core_plugins/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
)
66
from semantic_kernel.core_plugins.http_plugin import HttpPlugin
77
from semantic_kernel.core_plugins.math_plugin import MathPlugin
8+
from semantic_kernel.core_plugins.nory_x402_plugin import NoryX402Plugin
89
from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin import (
910
SessionsPythonTool,
1011
)
@@ -17,6 +18,7 @@
1718
"ConversationSummaryPlugin",
1819
"HttpPlugin",
1920
"MathPlugin",
21+
"NoryX402Plugin",
2022
"SessionsPythonTool",
2123
"TextMemoryPlugin",
2224
"TextPlugin",
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
"""Nory x402 Payment Plugin for Semantic Kernel.
4+
5+
A plugin that enables AI agents to make payments using the x402 HTTP protocol.
6+
Supports Solana and 7 EVM chains with sub-400ms settlement.
7+
"""
8+
9+
from typing import Annotated
10+
11+
import aiohttp
12+
13+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
14+
from semantic_kernel.kernel_pydantic import KernelBaseModel
15+
16+
NORY_API_BASE = "https://noryx402.com"
17+
18+
19+
class NoryX402Plugin(KernelBaseModel):
20+
"""A plugin that provides x402 payment functionality via Nory.
21+
22+
Enables AI agents to make payments using the x402 HTTP payment protocol.
23+
Supports Solana and 7 EVM chains (Base, Polygon, Arbitrum, Optimism,
24+
Avalanche, Sei, IoTeX) with sub-400ms settlement times.
25+
26+
Usage:
27+
kernel.add_plugin(NoryX402Plugin(), "nory")
28+
29+
# With API key for authenticated endpoints:
30+
kernel.add_plugin(NoryX402Plugin(api_key="your-api-key"), "nory")
31+
32+
Examples:
33+
{{nory.get_payment_requirements "/api/premium/data" "0.10"}}
34+
{{nory.settle_payment $payload}}
35+
{{nory.health_check}}
36+
"""
37+
38+
api_key: str | None = None
39+
"""Nory API key for authenticated endpoints. Optional for public endpoints."""
40+
41+
def _get_headers(self, with_json: bool = False) -> dict[str, str]:
42+
"""Get request headers with optional auth."""
43+
headers: dict[str, str] = {}
44+
if with_json:
45+
headers["Content-Type"] = "application/json"
46+
if self.api_key:
47+
headers["Authorization"] = f"Bearer {self.api_key}"
48+
return headers
49+
50+
@kernel_function(
51+
name="get_payment_requirements",
52+
description="Get x402 payment requirements for accessing a paid resource. Use this when you encounter an HTTP 402 Payment Required response.",
53+
)
54+
async def get_payment_requirements(
55+
self,
56+
resource: Annotated[str, "The resource path requiring payment (e.g., /api/premium/data)"],
57+
amount: Annotated[str, "Amount in human-readable format (e.g., '0.10' for $0.10 USDC)"],
58+
network: Annotated[
59+
str | None,
60+
"Preferred blockchain network (solana-mainnet, base-mainnet, polygon-mainnet, etc.)",
61+
] = None,
62+
) -> str:
63+
"""Get payment requirements for a resource.
64+
65+
Returns amount, supported networks, and wallet address needed to make a payment.
66+
67+
Args:
68+
resource: The resource path requiring payment.
69+
amount: Amount in human-readable format.
70+
network: Preferred blockchain network (optional).
71+
72+
Returns:
73+
JSON string with payment requirements.
74+
"""
75+
params = {"resource": resource, "amount": amount}
76+
if network:
77+
params["network"] = network
78+
79+
async with aiohttp.ClientSession() as session:
80+
async with session.get(
81+
f"{NORY_API_BASE}/api/x402/requirements",
82+
params=params,
83+
headers=self._get_headers(),
84+
raise_for_status=True,
85+
) as response:
86+
return await response.text()
87+
88+
@kernel_function(
89+
name="verify_payment",
90+
description="Verify a signed payment transaction before settlement. Use this to validate that a payment is correct before submitting to blockchain.",
91+
)
92+
async def verify_payment(
93+
self,
94+
payload: Annotated[str, "Base64-encoded payment payload containing signed transaction"],
95+
) -> str:
96+
"""Verify a signed payment transaction.
97+
98+
Validates that a payment transaction is correct before submitting
99+
it to the blockchain.
100+
101+
Args:
102+
payload: Base64-encoded payment payload.
103+
104+
Returns:
105+
JSON string with verification result including validity and payer info.
106+
"""
107+
async with aiohttp.ClientSession() as session:
108+
async with session.post(
109+
f"{NORY_API_BASE}/api/x402/verify",
110+
json={"payload": payload},
111+
headers=self._get_headers(with_json=True),
112+
raise_for_status=True,
113+
) as response:
114+
return await response.text()
115+
116+
@kernel_function(
117+
name="settle_payment",
118+
description="Settle a payment on-chain. Submits a verified payment to the blockchain with ~400ms settlement time.",
119+
)
120+
async def settle_payment(
121+
self,
122+
payload: Annotated[str, "Base64-encoded payment payload"],
123+
) -> str:
124+
"""Settle a payment on-chain.
125+
126+
Submits a verified payment transaction to the blockchain.
127+
Settlement typically completes in under 400ms.
128+
129+
Args:
130+
payload: Base64-encoded payment payload.
131+
132+
Returns:
133+
JSON string with settlement result including transaction ID.
134+
"""
135+
async with aiohttp.ClientSession() as session:
136+
async with session.post(
137+
f"{NORY_API_BASE}/api/x402/settle",
138+
json={"payload": payload},
139+
headers=self._get_headers(with_json=True),
140+
raise_for_status=True,
141+
) as response:
142+
return await response.text()
143+
144+
@kernel_function(
145+
name="lookup_transaction",
146+
description="Look up the status of a previously submitted payment transaction.",
147+
)
148+
async def lookup_transaction(
149+
self,
150+
transaction_id: Annotated[str, "Transaction ID or signature"],
151+
network: Annotated[str, "Network where the transaction was submitted"],
152+
) -> str:
153+
"""Look up transaction status.
154+
155+
Check the status of a previously submitted payment.
156+
157+
Args:
158+
transaction_id: Transaction ID or signature.
159+
network: Network where the transaction was submitted.
160+
161+
Returns:
162+
JSON string with transaction details including status and confirmations.
163+
"""
164+
async with aiohttp.ClientSession() as session:
165+
async with session.get(
166+
f"{NORY_API_BASE}/api/x402/transactions/{transaction_id}",
167+
params={"network": network},
168+
headers=self._get_headers(),
169+
raise_for_status=True,
170+
) as response:
171+
return await response.text()
172+
173+
@kernel_function(
174+
name="health_check",
175+
description="Check Nory service health and see supported networks.",
176+
)
177+
async def health_check(self) -> str:
178+
"""Check Nory service health.
179+
180+
Verify the payment service is operational and see supported networks.
181+
182+
Returns:
183+
JSON string with health status and supported networks.
184+
"""
185+
async with aiohttp.ClientSession() as session:
186+
async with session.get(
187+
f"{NORY_API_BASE}/api/x402/health",
188+
raise_for_status=True,
189+
) as response:
190+
return await response.text()

0 commit comments

Comments
 (0)