Skip to main content

forest/rpc/methods/
msig.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::rpc::error::ServerError;
5use crate::rpc::types::ApiTipsetKey;
6use crate::rpc::types::*;
7use crate::rpc::{ApiPaths, Ctx, Permission, RpcMethod};
8use crate::shim::actors::MultisigActorStateLoad as _;
9use crate::shim::actors::multisig;
10use crate::shim::{address::Address, econ::TokenAmount};
11use enumflags2::BitFlags;
12use num_bigint::BigInt;
13
14pub enum MsigGetAvailableBalance {}
15
16impl RpcMethod<2> for MsigGetAvailableBalance {
17    const NAME: &'static str = "Filecoin.MsigGetAvailableBalance";
18    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
19    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
20    const PERMISSION: Permission = Permission::Read;
21    const DESCRIPTION: &'static str = "Returns the spendable balance of the given multisig (total balance minus the locked amount) at the given tipset.";
22
23    type Params = (Address, ApiTipsetKey);
24    type Ok = TokenAmount;
25
26    async fn handle(
27        ctx: Ctx,
28        (address, ApiTipsetKey(tsk)): Self::Params,
29        _: &http::Extensions,
30    ) -> Result<Self::Ok, ServerError> {
31        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
32        let height = ts.epoch();
33        let actor = ctx
34            .state_manager
35            .get_required_actor(&address, *ts.parent_state())?;
36        let actor_balance = TokenAmount::from(&actor.balance);
37        let ms = multisig::State::load(ctx.db(), actor.code, actor.state)?;
38        let locked_balance = ms.locked_balance(height)?;
39        let avail_balance = &actor_balance - locked_balance;
40        Ok(avail_balance)
41    }
42}
43
44pub enum MsigGetPending {}
45
46impl RpcMethod<2> for MsigGetPending {
47    const NAME: &'static str = "Filecoin.MsigGetPending";
48    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
49    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
50    const PERMISSION: Permission = Permission::Read;
51    const DESCRIPTION: &'static str =
52        "Returns the transactions awaiting approval in the given multisig at the given tipset.";
53
54    type Params = (Address, ApiTipsetKey);
55    type Ok = Vec<Transaction>;
56
57    async fn handle(
58        ctx: Ctx,
59        (address, ApiTipsetKey(tsk)): Self::Params,
60        _: &http::Extensions,
61    ) -> Result<Self::Ok, ServerError> {
62        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
63        let ms: multisig::State = ctx
64            .state_manager
65            .get_actor_state_from_address(&ts, &address)?;
66        let txns = ms
67            .get_pending_txn(ctx.db())?
68            .into_iter()
69            .map(|txn| Transaction {
70                id: txn.id,
71                to: txn.to,
72                value: txn.value,
73                method: txn.method,
74                params: txn.params,
75                approved: txn.approved,
76            })
77            .collect();
78        Ok(txns)
79    }
80}
81
82pub enum MsigGetVested {}
83impl RpcMethod<3> for MsigGetVested {
84    const NAME: &'static str = "Filecoin.MsigGetVested";
85    const PARAM_NAMES: [&'static str; 3] = ["address", "startTipsetKey", "endTipsetKey"];
86    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
87    const PERMISSION: Permission = Permission::Read;
88    const DESCRIPTION: &'static str =
89        "Returns the amount that vested in the given multisig between the start and end tipsets.";
90
91    type Params = (Address, ApiTipsetKey, ApiTipsetKey);
92    type Ok = BigInt;
93
94    async fn handle(
95        ctx: Ctx,
96        (addr, ApiTipsetKey(start_tsk), ApiTipsetKey(end_tsk)): Self::Params,
97        _: &http::Extensions,
98    ) -> Result<Self::Ok, ServerError> {
99        let start_ts = ctx
100            .chain_store()
101            .load_required_tipset_or_heaviest(&start_tsk)?;
102        let end_ts = ctx
103            .chain_store()
104            .load_required_tipset_or_heaviest(&end_tsk)?;
105
106        match start_ts.epoch().cmp(&end_ts.epoch()) {
107            std::cmp::Ordering::Greater => Err(ServerError::internal_error(
108                "start tipset is after end tipset",
109                None,
110            )),
111            std::cmp::Ordering::Equal => Ok(BigInt::from(0)),
112            std::cmp::Ordering::Less => {
113                let ms: multisig::State = ctx
114                    .state_manager
115                    .get_actor_state_from_address(&end_ts, &addr)?;
116                let start_lb = ms.locked_balance(start_ts.epoch())?;
117                let end_lb = ms.locked_balance(end_ts.epoch())?;
118                Ok(start_lb.atto() - end_lb.atto())
119            }
120        }
121    }
122}
123
124pub enum MsigGetVestingSchedule {}
125impl RpcMethod<2> for MsigGetVestingSchedule {
126    const NAME: &'static str = "Filecoin.MsigGetVestingSchedule";
127    const PARAM_NAMES: [&'static str; 2] = ["address", "tsk"];
128    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
129    const PERMISSION: Permission = Permission::Read;
130    const DESCRIPTION: &'static str =
131        "Returns the vesting schedule of the given multisig at the given tipset.";
132
133    type Params = (Address, ApiTipsetKey);
134    type Ok = MsigVesting;
135
136    async fn handle(
137        ctx: Ctx,
138        (addr, ApiTipsetKey(tsk)): Self::Params,
139        _: &http::Extensions,
140    ) -> Result<Self::Ok, ServerError> {
141        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
142        let ms: multisig::State = ctx.state_manager.get_actor_state_from_address(&ts, &addr)?;
143        Ok(ms.get_vesting_schedule()?)
144    }
145}