Skip to content
This repository was archived by the owner on Feb 28, 2025. It is now read-only.

Commit 401925d

Browse files
committed
Add nonce JSON-RPC endpoint
1 parent d01fe2b commit 401925d

File tree

9 files changed

+285
-13
lines changed

9 files changed

+285
-13
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib-xps/src/rpc/api.rs

+166-6
Original file line numberDiff line numberDiff line change
@@ -517,14 +517,90 @@ pub trait Xps {
517517
#[method(name = "status")]
518518
async fn status(&self) -> Result<String, ErrorObjectOwned>;
519519

520+
/// ### Documentation for JSON RPC Endpoint: `xps_walletAddress`
521+
/// ---
522+
/// #### Endpoint Name: `xps_walletAddress`
523+
/// #### Description:
524+
/// The `xps_walletAddress` endpoint retrieves the current Wallet Address of the internal wallet managed by the server. This endpoint is essential for applications that need to display or monitor the wallet, especially in the context of cryptocurrency transactions or account management.
525+
/// #### Request:
526+
/// - **Method:** `POST`
527+
/// - **URL:** `/rpc/v1/walletAddress`
528+
/// - **Headers:**
529+
/// - `Content-Type: application/json`
530+
/// - **Body:**
531+
/// - **JSON Object:**
532+
/// - `jsonrpc`: `"2.0"`
533+
/// - `method`: `"xps_walletAddress"`
534+
/// - `params`: Array (optional parameters as required)
535+
/// - `id`: Request identifier (integer or string)
536+
/// **Example Request Body:**
537+
/// ```json
538+
/// {
539+
/// "jsonrpc": "2.0",
540+
/// "method": "xps_walletAddress",
541+
/// "params": [],
542+
/// "id": 1
543+
/// }
544+
/// ```
545+
/// #### Response:
546+
/// - **Success Status Code:** `200 OK`
547+
/// - **Error Status Codes:**
548+
/// - `400 Bad Request` - Invalid request format or parameters.
549+
/// - `500 Internal Server Error` - Server or wallet-related error.
550+
/// **Success Response Body:**
551+
/// ```json
552+
/// {
553+
/// "jsonrpc": "2.0",
554+
/// "result": "0x0000000000000000000000000000000000000000",
555+
/// "id": 1
556+
/// }
557+
/// ```
558+
/// **Error Response Body:**
559+
/// ```json
560+
/// {
561+
/// "jsonrpc": "2.0",
562+
/// "error": {
563+
/// "code": -32602,
564+
/// "message": "Invalid parameters"
565+
/// },
566+
/// "id": 1
567+
/// }
568+
/// ```
569+
/// #### Error Handling:
570+
/// - **Invalid Parameters:** Check if the request body is properly formatted and includes valid parameters.
571+
/// - **Wallet or Server Errors:** Ensure that the server and wallet are operational. Consult server logs for detailed error information.
572+
/// #### Security Considerations:
573+
/// - **Authentication and Authorization:** Implement robust authentication and authorization checks to ensure only authorized users can access wallet balance information.
574+
/// - **Secure Communication:** Utilize HTTPS to encrypt data in transit and prevent eavesdropping.
575+
/// #### Usage Example:
576+
/// ```javascript
577+
/// const requestBody = {
578+
/// jsonrpc: "2.0",
579+
/// method: "xps_walletAddress",
580+
/// params: [],
581+
/// id: 1
582+
/// };
583+
/// fetch('https://server.example.com/rpc/v1/walletAddress', {
584+
/// method: 'POST',
585+
/// headers: {
586+
/// 'Content-Type': 'application/json'
587+
/// },
588+
/// body: JSON.stringify(requestBody)
589+
/// })
590+
/// .then(response => response.json())
591+
/// .then(data => console.log('Wallet Balance:', data.result))
592+
/// .catch(error => console.error('Error:', error));
593+
/// ```
594+
/// </div>
595+
/// ```
520596
#[method(name = "walletAddress")]
521597
async fn wallet_address(&self) -> Result<Address, ErrorObjectOwned>;
522598

523-
/// ### Documentation for JSON RPC Endpoint: `balance`
599+
/// ### Documentation for JSON RPC Endpoint: `xps_balance`
524600
/// ---
525-
/// #### Endpoint Name: `balance`
601+
/// #### Endpoint Name: `xps_balance`
526602
/// #### Description:
527-
/// The `balance` endpoint retrieves the current balance of the internal wallet managed by the server. This endpoint is essential for applications that need to display or monitor the wallet's balance, especially in the context of cryptocurrency transactions or account management.
603+
/// The `xps_balance` endpoint retrieves the current balance of the internal wallet managed by the server. This endpoint is essential for applications that need to display or monitor the wallet's balance, especially in the context of cryptocurrency transactions or account management.
528604
/// #### Request:
529605
/// - **Method:** `POST`
530606
/// - **URL:** `/rpc/v1/balance`
@@ -533,14 +609,14 @@ pub trait Xps {
533609
/// - **Body:**
534610
/// - **JSON Object:**
535611
/// - `jsonrpc`: `"2.0"`
536-
/// - `method`: `"balance"`
612+
/// - `method`: `"xps_balance"`
537613
/// - `params`: Array (optional parameters as required)
538614
/// - `id`: Request identifier (integer or string)
539615
/// **Example Request Body:**
540616
/// ```json
541617
/// {
542618
/// "jsonrpc": "2.0",
543-
/// "method": "balance",
619+
/// "method": "xps_balance",
544620
/// "params": [],
545621
/// "id": 1
546622
/// }
@@ -582,7 +658,7 @@ pub trait Xps {
582658
/// ```javascript
583659
/// const requestBody = {
584660
/// jsonrpc: "2.0",
585-
/// method: "balance",
661+
/// method: "xps_balance",
586662
/// params: [],
587663
/// id: 1
588664
/// };
@@ -601,4 +677,88 @@ pub trait Xps {
601677
/// ```
602678
#[method(name = "balance")]
603679
async fn balance(&self) -> Result<WalletBalance, ErrorObjectOwned>;
680+
681+
/// ### Documentation for JSON RPC Endpoint: `xps_nonce`
682+
/// ---
683+
/// #### Endpoint Name: `nonce`
684+
/// #### Description:
685+
/// The `xps_nonce` endpoint retrieves the nonce for `address` in the [`DIDRegistry`]. This is
686+
/// needed for signing payloads for DID Registry meta transactions.
687+
688+
/// #### Request:
689+
/// - **Method:** `POST`
690+
/// - **URL:** `/rpc/v1/nonce`
691+
/// - **Headers:**
692+
/// - `Content-Type: application/json`
693+
/// - **Body:**
694+
/// - **JSON Object:**
695+
/// - `jsonrpc`: `"2.0"`
696+
/// - `method`: `"xps_nonce"`
697+
/// - `params`: Array (optional parameters as required)
698+
/// - `id`: Request identifier (integer or string)
699+
/// **Example Request Body:**
700+
/// ```json
701+
/// {
702+
/// "jsonrpc": "2.0",
703+
/// "method": "xps_nonce",
704+
/// "params": ["0x0000000000000000000000000000000000000000"],
705+
/// "id": 1
706+
/// }
707+
/// ```
708+
/// #### Response:
709+
/// - **Success Status Code:** `200 OK`
710+
/// - **Error Status Codes:**
711+
/// - `400 Bad Request` - Invalid request format or parameters.
712+
/// - `500 Internal Server Error` - Server or wallet-related error.
713+
/// **Success Response Body:**
714+
/// ```json
715+
/// {
716+
/// "jsonrpc": "2.0",
717+
/// "result": {
718+
/// "nonce": 0
719+
/// },
720+
/// "id": 1
721+
/// }
722+
/// ```
723+
/// **Error Response Body:**
724+
/// ```json
725+
/// {
726+
/// "jsonrpc": "2.0",
727+
/// "error": {
728+
/// "code": -32602,
729+
/// "message": "Invalid parameters"
730+
/// },
731+
/// "id": 1
732+
/// }
733+
/// ```
734+
/// #### Error Handling:
735+
/// - **Invalid Parameters:** Check if the request body is properly formatted and includes valid parameters.
736+
/// - **Wallet or Server Errors:** Ensure that the server and wallet are operational. Consult server logs for detailed error information.
737+
/// #### Security Considerations:
738+
/// - **Authentication and Authorization:** Implement robust authentication and authorization checks to ensure only authorized users can access wallet balance information.
739+
/// - **Secure Communication:** Utilize HTTPS to encrypt data in transit and prevent eavesdropping.
740+
/// #### Usage Example:
741+
/// ```javascript
742+
/// const requestBody = {
743+
/// jsonrpc: "2.0",
744+
/// method: "xps_nonce",
745+
/// params: ["0x0000000000000000000000000000000000000000"],
746+
/// id: 1
747+
/// };
748+
/// fetch('https://server.example.com/rpc/v1/nonce', {
749+
/// method: 'POST',
750+
/// headers: {
751+
/// 'Content-Type': 'application/json'
752+
/// },
753+
/// body: JSON.stringify(requestBody)
754+
/// })
755+
/// .then(response => response.json())
756+
/// .then(data => console.log('Wallet Balance:', data.result))
757+
/// .catch(error => console.error('Error:', error));
758+
/// ```
759+
/// </div>
760+
/// ```
761+
762+
#[method(name = "nonce")]
763+
async fn nonce(&self, did: String) -> Result<U256, ErrorObjectOwned>;
604764
}

lib-xps/src/rpc/methods.rs

+10
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ impl<P: Middleware + 'static> XpsServer for XpsMethods<P> {
145145
.map_err(RpcError::from)?;
146146
Ok(result)
147147
}
148+
149+
async fn nonce(&self, did: String) -> Result<U256, ErrorObjectOwned> {
150+
log::debug!("xps_nonce called");
151+
let result = self
152+
.contact_operations
153+
.nonce(did)
154+
.await
155+
.map_err(RpcError::from)?;
156+
Ok(result)
157+
}
148158
}
149159

150160
/// Error types for DID Registry JSON-RPC

lib-xps/src/util.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//! Internal Utility functions for use in crate
2-
32
#[cfg(test)]
43
use std::sync::Once;
54
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};

lib-xps/tests/integration_test.rs

+13
Original file line numberDiff line numberDiff line change
@@ -582,3 +582,16 @@ async fn test_did_deactivation() -> Result<(), Error> {
582582
})
583583
.await
584584
}
585+
586+
#[tokio::test]
587+
async fn test_nonce() -> Result<(), Error> {
588+
with_xps_client(None, None, |client, _, _, anvil| async move {
589+
let me: LocalWallet = anvil.keys()[3].clone().into();
590+
591+
let nonce = client.nonce(hex::encode(me.address())).await?;
592+
assert_eq!(U256::from(0), nonce);
593+
594+
Ok(())
595+
})
596+
.await
597+
}

registry/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ xps-types.workspace = true
1515
lib-didethresolver.workspace = true
1616
rustc-hex.workspace = true
1717
thiserror.workspace = true
18+
19+
[dev-dependencies]
20+
tracing-subscriber.workspace = true
21+
ctor.workspace = true

registry/src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ pub enum ContactOperationError<M: Middleware> {
2424
DIDDeactivated,
2525
#[error("Type failed to convert")]
2626
Type(#[from] lib_didethresolver::error::TypeError),
27+
#[error("Error parsing hex bytes: {0}")]
28+
Bytes(#[from] ethers::types::ParseBytesError),
2729
}

registry/src/lib.rs

+69-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
pub mod error;
2+
#[cfg(test)]
3+
mod test;
24

35
use std::str::FromStr;
46

57
use error::ContactOperationError;
6-
use ethers::types::{H160, U256};
7-
use ethers::{core::types::Signature, providers::Middleware, types::Address};
8-
use lib_didethresolver::types::VerificationMethodProperties;
9-
use lib_didethresolver::Resolver;
10-
use lib_didethresolver::{did_registry::DIDRegistry, types::XmtpAttribute};
8+
use ethers::{
9+
providers::Middleware,
10+
types::{Address, Bytes, Signature, H160, U256},
11+
};
12+
use lib_didethresolver::{
13+
did_registry::DIDRegistry,
14+
types::{VerificationMethodProperties, XmtpAttribute},
15+
Resolver,
16+
};
1117
use xps_types::{GrantInstallationResult, KeyPackageResult, Status};
1218

1319
pub struct ContactOperations<Middleware> {
@@ -29,7 +35,8 @@ where
2935
fn resolve_did_address(&self, did: String) -> Result<H160, ContactOperationError<M>> {
3036
// for now, we will just assume the DID is a valid ethereum wallet address
3137
// TODO: Parse or resolve the actual DID
32-
let address = Address::from_str(&did)?;
38+
39+
let address = Address::from_slice(Bytes::from_str(did.as_ref())?.to_vec().as_slice());
3340
Ok(address)
3441
}
3542

@@ -165,4 +172,60 @@ where
165172

166173
Ok(())
167174
}
175+
176+
pub async fn nonce(&self, did: String) -> Result<U256, ContactOperationError<M>> {
177+
let address = self.resolve_did_address(did)?;
178+
let nonce = self.registry.nonce(address).await?;
179+
Ok(nonce)
180+
}
181+
}
182+
183+
#[cfg(test)]
184+
mod tests {
185+
use super::*;
186+
use ethers::{
187+
abi::AbiEncode,
188+
providers::{MockProvider, Provider},
189+
};
190+
use lib_didethresolver::did_registry::NonceReturn;
191+
192+
impl ContactOperations<Provider<MockProvider>> {
193+
pub fn mocked() -> (Self, MockProvider) {
194+
let (mock_provider, mock) = Provider::mocked();
195+
let registry = DIDRegistry::new(H160::zero(), mock_provider.into());
196+
197+
(ContactOperations::new(registry), mock)
198+
}
199+
}
200+
201+
#[test]
202+
fn test_resolve_address_from_hexstr() {
203+
let addr = "0x0000000000000000000000000000000000000000";
204+
let (ops, _) = ContactOperations::mocked();
205+
assert_eq!(
206+
ops.resolve_did_address(addr.to_string()).unwrap(),
207+
H160::zero()
208+
);
209+
210+
let addr = "0000000000000000000000000000000000000000";
211+
assert_eq!(
212+
ops.resolve_did_address(addr.to_string()).unwrap(),
213+
H160::zero()
214+
);
215+
}
216+
217+
#[tokio::test]
218+
async fn test_nonce() {
219+
let (ops, mock) = ContactOperations::mocked();
220+
221+
mock.push::<String, String>(NonceReturn(U256::from(212)).encode_hex())
222+
.unwrap();
223+
224+
let nonce = ops
225+
.nonce("0x1111111111111111111111111111111111111111".to_string())
226+
.await
227+
.unwrap();
228+
229+
assert_eq!(nonce, U256::from(212));
230+
}
168231
}

registry/src/test.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! Internal Utility functions for use in crate
2+
use std::sync::Once;
3+
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
4+
5+
static INIT: Once = Once::new();
6+
7+
#[ctor::ctor]
8+
fn __init_test_logging() {
9+
INIT.call_once(|| {
10+
let fmt = fmt::layer().compact();
11+
Registry::default().with(env()).with(fmt).init()
12+
})
13+
}
14+
15+
/// Try to get the logging environment from the `RUST_LOG` environment variable.
16+
/// If it is not set, use the default of `info`.
17+
fn env() -> EnvFilter {
18+
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))
19+
}

0 commit comments

Comments
 (0)