document contract APIs

This commit is contained in:
wiesche
2026-09-08 09:08:55 +02:00
parent 9ac678291a
commit 05aaf260bd
5 changed files with 377 additions and 13 deletions
+54
View File
@@ -475,6 +475,32 @@ where
// Below is a foreign wrapper around owner calls to 'new' and 'sign' which are only executed
// if this is a receiving contract. This preserves the ability to receive on a foreign interface.
/// Start a receiving contract through the foreign API.
///
/// This is the first step of an RSR flow. The caller must confirm the request
/// with the user before continuing.
///
/// # Arguments
/// * `args` - Contract setup and expiry options. `net_change` must be positive.
///
/// # Returns
/// The new contract slate.
///
/// # Example
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config);
/// use libwallet::contract::types::ContractNewArgsAPI;
///
/// let api_foreign = Foreign::new(
/// wallet,
/// std::path::PathBuf::from("grin-wallet.toml"),
/// None,
/// None,
/// false,
/// );
/// let mut args = ContractNewArgsAPI::default();
/// args.setup_args.net_change = Some(1_000_000_000);
/// let result = api_foreign.contract_new(&args);
/// ```
pub fn contract_new(&self, args: &ContractNewArgsAPI) -> Result<Slate, Error> {
let mut w_lock = self.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
@@ -489,6 +515,34 @@ where
}
/// Sign the receiving side of a contract through the foreign API.
///
/// The caller must show the incoming slate and ask the user before signing.
/// The slate must come from an existing receiving contract.
///
/// # Arguments
/// * `slate` - The incoming contract slate.
/// * `args` - This wallet's setup and expected balance change.
///
/// # Returns
/// The updated contract slate.
///
/// # Example
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env_foreign!(wallet, wallet_config);
/// use libwallet::contract::types::ContractSetupArgsAPI;
///
/// let api_foreign = Foreign::new(
/// wallet,
/// std::path::PathBuf::from("grin-wallet.toml"),
/// None,
/// None,
/// false,
/// );
/// let slate = Slate::blank(2, false);
/// let mut args = ContractSetupArgsAPI::default();
/// args.net_change = Some(1_000_000_000);
/// let result = api_foreign.contract_sign(&slate, &args);
/// ```
pub fn contract_sign(
&self,
slate: &Slate,
+1 -1
View File
@@ -54,7 +54,7 @@ pub use crate::tor_config::ConfigPath;
pub use crate::foreign_rpc::foreign_rpc as foreign_rpc_client;
pub use crate::foreign_rpc::run_doctest_foreign;
pub use crate::owner_rpc::run_doctest_owner;
pub use crate::owner_rpc::{run_doctest_owner, run_doctest_owner_with};
pub use types::{
ECDHPubkey, Ed25519SecretKey, EncryptedRequest, EncryptedResponse, EncryptionErrorResponse,
+53
View File
@@ -888,6 +888,24 @@ where
}
/// Initiate a new contract. Also performs the initial setup on the slate.
///
/// # Arguments
/// * `keychain_mask` - Wallet secret mask, if one is used.
/// * `args` - Contract setup and expiry options.
///
/// # Returns
/// The new contract slate.
///
/// # Example
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
/// use libwallet::contract::types::ContractNewArgsAPI;
///
/// let api_owner = Owner::new(wallet, None, std::path::PathBuf::from("grin-wallet.toml"));
/// let mut new_args = ContractNewArgsAPI::default();
/// new_args.setup_args.net_change = Some(-1_000_000_000);
/// let result = api_owner.contract_new(None, &new_args);
/// ```
pub fn contract_new(
&self,
keychain_mask: Option<&SecretKey>,
@@ -899,6 +917,14 @@ where
}
/// Summarise a contract slate, including its participants, signatures and net change.
///
/// # Example
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
/// let api_owner = Owner::new(wallet, None, std::path::PathBuf::from("grin-wallet.toml"));
/// let slate = Slate::blank(2, false);
/// let result = api_owner.contract_view(None, &slate);
/// ```
pub fn contract_view(
&self,
keychain_mask: Option<&SecretKey>,
@@ -910,6 +936,17 @@ where
}
/// Sign a contract, running setup first if it has not been done yet.
/// The slate must come from an existing contract.
///
/// # Example
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
/// use libwallet::contract::types::ContractSetupArgsAPI;
///
/// let api_owner = Owner::new(wallet, None, std::path::PathBuf::from("grin-wallet.toml"));
/// let slate = Slate::blank(2, false);
/// let result = api_owner.contract_sign(None, &slate, &ContractSetupArgsAPI::default());
/// ```
pub fn contract_sign(
&self,
keychain_mask: Option<&SecretKey>,
@@ -933,6 +970,22 @@ where
}
/// Revoke a contract by double-spending one of its locked inputs.
/// The transaction id must identify an existing contract.
///
/// # Example
/// ```
/// # grin_wallet_api::doctest_helper_setup_doc_env!(wallet, wallet_config);
/// use libwallet::contract::types::ContractRevokeArgsAPI;
///
/// let api_owner = Owner::new(wallet, None, std::path::PathBuf::from("grin-wallet.toml"));
/// let result = api_owner.contract_revoke(
/// None,
/// &ContractRevokeArgsAPI {
/// tx_id: 1,
/// src_acct_name: None,
/// },
/// );
/// ```
pub fn contract_revoke(
&self,
keychain_mask: Option<&SecretKey>,
+269 -11
View File
@@ -572,16 +572,136 @@ pub trait OwnerRpc {
fn init_send_tx(&self, token: Token, args: InitTxArgs) -> Result<VersionedSlate, Error>;
/**
TODO: Full docs once API has stabilised
*/
Networked version of [Owner::contract_new](struct.Owner.html#method.contract_new).
# Json rpc example
```
# use grin_wallet_api::run_doctest_owner;
# use serde_json::{self, Value};
# use tempfile::tempdir;
# let request = r#"
{
"jsonrpc": "2.0",
"method": "contract_new",
"params": {
"token": "d202964900000000d302964900000000d402964900000000d502964900000000",
"args": {
"setup_args": {
"src_acct_name": null,
"net_change": -1000000000,
"num_participants": 2,
"fee_rate": null,
"add_outputs": false,
"selection_args": {
"minimum_confirmations": 1,
"use_inputs": "any",
"make_outputs": null
},
"proof_args": {
"suppress_proof": true,
"proof_type": "Invoice",
"memo": null,
"sender_address": null
}
},
"ttl_blocks": null
}
},
"id": 1
}
# "#;
# let dir = tempdir().unwrap();
# let request: Value = serde_json::from_str(request).unwrap();
# let response = run_doctest_owner(
# request,
# dir.path().to_str().unwrap(),
# 4,
# false,
# false,
# false,
# false,
# ).unwrap().unwrap();
# assert_eq!(response["result"]["Ok"]["sta"], "S1", "{response}");
```
*/
fn contract_new(&self, token: Token, args: ContractNewArgsAPI)
-> Result<VersionedSlate, Error>;
/**
TODO: Full docs once API has stabilised
*/
Networked version of [Owner::contract_sign](struct.Owner.html#method.contract_sign).
Pass the slate returned by `contract_new` or an earlier `contract_sign` call as `slate`.
This example signs a one-party self-spend.
# Json rpc example
```
# use easy_jsonrpc_mw::Handler;
# use grin_wallet_api::run_doctest_owner_with;
# use grin_wallet_libwallet::contract::types::{
# ContractNewArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs,
# };
# use serde_json::json;
# use tempfile::tempdir;
# let token = "d202964900000000d302964900000000d402964900000000d502964900000000";
# let setup_args = ContractNewArgsAPI {
# setup_args: ContractSetupArgsAPI {
# net_change: Some(0),
# num_participants: 1,
# selection_args: OutputSelectionArgs {
# minimum_confirmations: Some(1),
# ..Default::default()
# },
# ..Default::default()
# },
# ..Default::default()
# };
# let setup_request = json!({
# "jsonrpc": "2.0",
# "method": "contract_new",
# "params": { "token": token, "args": setup_args },
# "id": 1
# });
# let dir = tempdir().unwrap();
# let response = run_doctest_owner_with(
# dir.path().to_str().unwrap(), 4, false, false, false, false, false,
# move |api| {
# let setup = api.handle_request(setup_request).as_option().unwrap();
# let slate = setup["result"]["Ok"].clone();
let request = json!({
"jsonrpc": "2.0",
"method": "contract_sign",
"params": {
"token": token,
"slate": slate,
"args": {
"src_acct_name": null,
"net_change": null,
"num_participants": 1,
"fee_rate": null,
"add_outputs": false,
"selection_args": {
"minimum_confirmations": 1,
"use_inputs": "any",
"make_outputs": null
},
"proof_args": {
"suppress_proof": true,
"proof_type": "Invoice",
"memo": null,
"sender_address": null
}
}
},
"id": 2
});
# api.handle_request(request).as_option()
# },
# ).unwrap().unwrap();
# assert_eq!(response["result"]["Ok"]["sta"], "S2", "{response}");
```
*/
fn contract_sign(
&self,
token: Token,
@@ -590,15 +710,124 @@ pub trait OwnerRpc {
) -> Result<VersionedSlate, Error>;
/**
TODO: Full docs once API has stabilised
*/
Networked version of [Owner::contract_view](struct.Owner.html#method.contract_view).
# Json rpc example
```
# grin_wallet_api::doctest_helper_json_rpc_owner_assert_response!(
# r#"
{
"jsonrpc": "2.0",
"method": "contract_view",
"params": {
"token": "d202964900000000d302964900000000d402964900000000d502964900000000",
"slate": {
"ver": "4:3",
"id": "0436430c-2b02-624c-2032-570501212b00",
"sta": "S1",
"sigs": [],
"coms": []
}
},
"id": 1
}
# "#,
# r#"
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"Ok": {
"agreed_net_change": null,
"balance_change": null,
"is_executed": false,
"num_participants": 2,
"num_sigs": 0,
"own_commitment_status": "clean",
"own_fee": null,
"suggested_net_change": 0
}
}
}
# "#,
# 0, false, false, false, false);
```
*/
fn contract_view(&self, token: Token, slate: VersionedSlate) -> Result<ContractView, Error>;
/**
TODO: Full docs once API has stabilised
*/
Networked version of [Owner::contract_revoke](struct.Owner.html#method.contract_revoke).
Returns a replacement self-spend when the wallet contributed an input.
# Json rpc example
```
# use easy_jsonrpc_mw::Handler;
# use grin_wallet_api::run_doctest_owner_with;
# use grin_wallet_libwallet::contract::types::{
# ContractNewArgsAPI, ContractSetupArgsAPI, OutputSelectionArgs,
# };
# use serde_json::json;
# use tempfile::tempdir;
# let token = "d202964900000000d302964900000000d402964900000000d502964900000000";
# let setup_args = ContractNewArgsAPI {
# setup_args: ContractSetupArgsAPI {
# net_change: Some(-1_000_000_000),
# add_outputs: true,
# selection_args: OutputSelectionArgs {
# minimum_confirmations: Some(1),
# ..Default::default()
# },
# ..Default::default()
# },
# ..Default::default()
# };
# let setup_request = json!({
# "jsonrpc": "2.0",
# "method": "contract_new",
# "params": { "token": token, "args": setup_args },
# "id": 1
# });
# let dir = tempdir().unwrap();
# let response = run_doctest_owner_with(
# dir.path().to_str().unwrap(), 4, false, false, false, false, false,
# move |api| {
# let setup = api.handle_request(setup_request).as_option().unwrap();
# let slate_id = setup["result"]["Ok"]["id"].clone();
# let txs = api.handle_request(json!({
# "jsonrpc": "2.0",
# "method": "retrieve_txs",
# "params": {
# "token": token,
# "refresh_from_node": false,
# "tx_id": null,
# "tx_slate_id": slate_id
# },
# "id": 2
# })).as_option().unwrap();
# let tx_id = txs["result"]["Ok"][1][0]["id"].clone();
let request = json!({
"jsonrpc": "2.0",
"method": "contract_revoke",
"params": {
"token": token,
"args": {
"tx_id": tx_id,
"src_acct_name": null
}
},
"id": 3
});
# api.handle_request(request).as_option()
# },
# ).unwrap().unwrap();
# let replacement = &response["result"]["Ok"];
# assert!(!replacement.is_null(), "{response}");
# assert_eq!(replacement["sta"], "S2", "{response}");
```
*/
fn contract_revoke(
&self,
token: Token,
@@ -2637,6 +2866,35 @@ pub fn run_doctest_owner(
payment_proof: bool,
) -> Result<Option<serde_json::Value>, String> {
use easy_jsonrpc_mw::Handler;
let close_wallet = request["method"] == "set_top_level_directory";
run_doctest_owner_with(
test_dir,
blocks_to_mine,
perform_tx,
lock_tx,
finalize_tx,
payment_proof,
close_wallet,
move |owner_api| owner_api.handle_request(request).as_option(),
)
}
/// Set up an owner doctest and run several requests against the same wallet
#[doc(hidden)]
pub fn run_doctest_owner_with<F>(
test_dir: &str,
blocks_to_mine: u64,
perform_tx: bool,
lock_tx: bool,
finalize_tx: bool,
payment_proof: bool,
close_wallet: bool,
run: F,
) -> Result<Option<serde_json::Value>, String>
where
F: FnOnce(&(dyn OwnerRpc + 'static)) -> Option<serde_json::Value>,
{
use grin_keychain::ExtKeychain;
use grin_wallet_config::initial_setup_wallet;
use grin_wallet_impls::test_framework::{self, LocalWalletClient, WalletProxy};
@@ -2832,11 +3090,11 @@ pub fn run_doctest_owner(
let mut api_owner = Owner::new(wallet1, None, config.config_file_path);
api_owner.doctest_mode = true;
if request["method"] == "set_top_level_directory" {
if close_wallet {
api_owner.close_wallet(None).unwrap();
}
let owner_api = &api_owner as &dyn OwnerRpc;
let res = owner_api.handle_request(request).as_option();
let owner_api = &api_owner as &(dyn OwnerRpc + 'static);
let res = run(owner_api);
let _ = fs::remove_dir_all(test_dir);
Ok(res)
}
-1
View File
@@ -46,7 +46,6 @@ Separating side effects until the 'save_step' part would make these functions mu
outside contracts and is versioned with the slate format.
- Add contract history, lookup and transport.
- Decide whether the foreign JSON-RPC API should expose `new` and `sign`.
- Add full API and RPC examples once the contract interface is stable.
- Separate side effects out from the computation, as described above.
- Store transactions in LMDB so they can be committed with the wallet state.