Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ resolver = "2"
[workspace.dependencies]
scanner = { path = "scanner", version = "0.1" }
x402 = { path = "x402", version = "0.1" }
eip8004 = { git = "https://github.com/zpaynow/8004" }
alloy = "1.0"
anyhow = "1.0"
async-trait = "0.1"
Expand Down
18 changes: 16 additions & 2 deletions api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::{net::SocketAddr, sync::Arc};
use tokio::{net::TcpListener, sync::mpsc::UnboundedSender};
use tower_http::cors::{Any, CorsLayer};
use tracing::level_filters::LevelFilter;
use x402::{EvmScheme, Facilitator};
use x402::{Evm8004Registry, EvmScheme, Facilitator};

#[derive(Parser)]
#[command(version, about, long_about = None)]
Expand Down Expand Up @@ -62,6 +62,14 @@ struct Command {
/// Scanner chains configure file path
#[arg(long, env = "SCANNER_CONFIG", default_value = "config.toml")]
scanner_config: String,

/// EIP-8004 registry agent id
#[arg(long, env = "AGENT_ID")]
agent_id: Option<i64>,

/// EIP-8004 registry agent identity contract
#[arg(long, env = "AGENT_IDENTITY")]
agent_identity: Option<String>,
}

#[derive(Clone)]
Expand Down Expand Up @@ -136,11 +144,17 @@ async fn main() {
.unwrap();

// building x402 facilitator
let agent = match (args.agent_id, args.agent_identity) {
(Some(agent_id), Some(identity)) => Some(Evm8004Registry { agent_id, identity }),
_ => None,
};
let mut facilitator = Facilitator::new();
for c in x402_assets {
match c.ctype {
ChainType::Evm => {
let mut scheme = EvmScheme::new(&c.rpc, &c.network, &c.signer).unwrap();
let mut scheme = EvmScheme::new(&c.rpc, &c.network, &c.signer, agent.clone())
.await
.unwrap();
for asset in c.assets {
scheme
.asset(&asset.address, &asset.name, &asset.version)
Expand Down
1 change: 1 addition & 0 deletions x402/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024"

[dependencies]
eip8004.workspace = true
alloy.workspace = true
anyhow.workspace = true
async-trait.workspace = true
Expand Down
10 changes: 8 additions & 2 deletions x402/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ impl ClientFacilitator {
pub fn build<'a>(
&self,
prs: &'a [PaymentRequirements],
feedback_index: Option<u64>,
) -> Result<(PaymentPayload, &'a PaymentRequirements)> {
for pr in prs.iter() {
let identity = format!("{}-{}", pr.scheme, pr.network);
if self.infos.contains_key(&identity) {
let payload = self.build_with_scheme(pr)?;
let payload = self.build_with_scheme(pr, feedback_index)?;
return Ok((payload, pr));
}
}
Expand All @@ -110,7 +111,11 @@ impl ClientFacilitator {
}

/// Build the payment payload by a paymentRequirements
pub fn build_with_scheme(&self, pr: &PaymentRequirements) -> Result<PaymentPayload> {
pub fn build_with_scheme(
&self,
pr: &PaymentRequirements,
feedback_index: Option<u64>,
) -> Result<PaymentPayload> {
let identity = format!("{}-{}", pr.scheme, pr.network);

if let Some(info) = self.infos.get(&identity) {
Expand All @@ -125,6 +130,7 @@ impl ClientFacilitator {
payload: SchemePayload {
signature,
authorization,
feedback_index,
},
})
} else {
Expand Down
1 change: 1 addition & 0 deletions x402/src/facilitator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ impl Facilitator {
transaction: "".to_owned(),
network: req.payment_payload.network.clone(),
payer: req.payment_payload.payload.authorization.from.clone(),
feedback_auth: None,
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion x402/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
mod scheme;
pub use scheme::evm::EvmScheme;
pub use scheme::evm::{Evm8004Registry, EvmAsset, EvmScheme};
pub use scheme::sol::SolScheme;

pub mod client;
pub mod facilitator;
pub use facilitator::Facilitator;

use async_trait::async_trait;
use eip8004::FeedbackAuth;
use serde::{Deserialize, Serialize};
use serde_json::Value;

Expand Down Expand Up @@ -75,6 +76,10 @@ pub struct SchemePayload {
pub signature: String,
/// EIP-3009 authorization parameters
pub authorization: Authorization,
/// The index in client feedback for 8004 Reputation,
/// If has this field, and service has register agent info,
/// The `SettlementResponse` will has `feedback_auth` field.
pub feedback_index: Option<u64>,
}

/// EIP-3009 authorization parameters
Expand Down Expand Up @@ -126,6 +131,7 @@ impl VerifyResponse {
transaction: tx.to_owned(),
network: network.to_owned(),
payer: self.payer,
feedback_auth: None,
}
}
}
Expand All @@ -144,6 +150,8 @@ pub struct SettlementResponse {
pub network: String,
/// Address of the payer's wallet
pub payer: String,
/// The feedback authorized signature for 8004 Reputation
pub feedback_auth: Option<FeedbackAuth>,
}

/// List supported payment schemes.
Expand Down Expand Up @@ -338,6 +346,7 @@ impl Error {
transaction: "".to_owned(),
network: req.network.clone(),
payer: req.payload.authorization.from.clone(),
feedback_auth: None,
}
}
}
Expand Down
85 changes: 75 additions & 10 deletions x402/src/scheme/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
VerifyRequest, VerifyResponse,
};
use alloy::{
primitives::{Address, B256, U256},
primitives::{Address, B256, Bytes, U256},
providers::{Provider, ProviderBuilder},
signers::{Signature, SignerSync, local::PrivateKeySigner},
sol,
Expand All @@ -12,6 +12,7 @@ use alloy::{
};
use anyhow::Result;
use async_trait::async_trait;
use eip8004::{FeedbackAuth, FeedbackOnchainAuth};
use serde_json::{Value, json};
use std::collections::HashMap;
use std::str::FromStr;
Expand Down Expand Up @@ -60,6 +61,7 @@ impl TransferWithAuthorization {
}
}

/// EIP-3009 based assets/tokens
pub struct EvmAsset {
name: String,
version: String,
Expand All @@ -68,21 +70,58 @@ pub struct EvmAsset {
extra: Value,
}

/// EIP-8004 agent registry infomation
#[derive(Clone, Debug)]
pub struct Evm8004Registry {
pub agent_id: i64,
pub identity: String,
}

struct InnerEvm8004Registry {
pub agent_id: U256,
pub identity_registry: Address,
}

/// Evm-based scheme
pub struct EvmScheme {
chain_id: u64,
scheme: String,
network: String,
rpc: Url,
signer: PrivateKeySigner,
assets: HashMap<Address, EvmAsset>,
agent: Option<InnerEvm8004Registry>,
}

impl EvmScheme {
pub fn new(url: &str, network: &str, signer: &str) -> Result<Self> {
let rpc = url.parse()?;
/// Build the evm scheme with EIP-8004 agent
pub async fn new(
url: &str,
network: &str,
signer: &str,
agent: Option<Evm8004Registry>,
) -> Result<Self> {
let rpc: Url = url.parse()?;
let signer = signer.parse()?;

let provider = ProviderBuilder::new().connect_http(rpc.clone());
let chain_id = provider.get_chain_id().await?;

let agent = if let Some(agent) = agent {
let identity_registry: Address = agent.identity.parse()?;
Some(InnerEvm8004Registry {
agent_id: U256::from(agent.agent_id),
identity_registry,
})
} else {
None
};

Ok(Self {
chain_id,
rpc,
signer,
agent,
scheme: SCHEME.to_owned(),
network: network.to_owned(),
assets: HashMap::new(),
Expand All @@ -105,9 +144,6 @@ impl EvmScheme {
// Create provider and contract instance
let provider = ProviderBuilder::new().connect_http(self.rpc.clone());

// Get chain ID for EIP-712 domain
let chain_id = provider.get_chain_id().await?;

// Verify the contract has the required EIP-3009 functions by calling view functions
let contract = Eip3009Token::new(token_address, &provider);
let decimal = contract.decimals().call().await?;
Expand All @@ -123,7 +159,7 @@ impl EvmScheme {
let domain = create_eip712_domain(
name.to_string(),
version.to_string(),
chain_id,
self.chain_id,
token_address,
);

Expand Down Expand Up @@ -251,7 +287,10 @@ impl EvmScheme {
Ok(())
}

async fn handle_settle(&self, req: &VerifyRequest) -> Result<String, Error> {
async fn handle_settle(
&self,
req: &VerifyRequest,
) -> Result<(String, Option<FeedbackAuth>), Error> {
// Get the token address and parse authorization
let token: Address = req
.payment_requirements
Expand Down Expand Up @@ -322,8 +361,33 @@ impl EvmScheme {
.await
.map_err(|_| Error::InvalidTransactionState)?;

let feedback_auth = match (&self.agent, req.payment_payload.payload.feedback_index) {
(Some(agent), Some(index)) => {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|_| Error::UnexpectedVerifyError)?
.as_secs();
let expiry = now + 86400; // default is 1 Day

let mut feedback = FeedbackOnchainAuth {
agentId: agent.agent_id,
identityRegistry: agent.identity_registry,
clientAddress: from,
indexLimit: index,
expiry: U256::from(expiry),
chainId: U256::from(self.chain_id),
signerAddress: self.signer.address(),
signature: Bytes::default(),
};
let _ = feedback.sign(&self.signer).await;

Some(feedback.to_feedback_auth())
}
_ => None,
};

// Return the transaction hash
Ok(format!("{:?}", receipt.transaction_hash))
Ok((format!("{:?}", receipt.transaction_hash), feedback_auth))
}
}

Expand Down Expand Up @@ -410,12 +474,13 @@ impl PaymentScheme for EvmScheme {
/// parameters provided in the payment payload.
async fn settle(&self, req: &VerifyRequest) -> SettlementResponse {
match self.handle_settle(req).await {
Ok(tx_hash) => SettlementResponse {
Ok((tx_hash, feedback_auth)) => SettlementResponse {
success: true,
error_reason: None,
transaction: tx_hash,
network: req.payment_payload.network.clone(),
payer: req.payment_payload.payload.authorization.from.clone(),
feedback_auth,
},
Err(error) => error.settle(&req.payment_payload),
}
Expand Down