@@ -124,3 +124,83 @@ pub async fn setup() -> FrameworkResult<SetupGuard> {
124124 teardown_called : false ,
125125 } )
126126}
127+
128+ /// Multi-identity counterpart of [`setup`]. Builds a fresh test
129+ /// wallet, funds `n` distinct platform addresses from the bank, and
130+ /// registers an identity at DIP-9 indices `0..n` on each.
131+ ///
132+ /// Returns a [`MultiIdentitySetupGuard`] wrapping the original
133+ /// [`SetupGuard`] plus the `Vec<RegisteredIdentity>` so test
134+ /// authors can drive multi-identity flows (DP-002 contact requests,
135+ /// ID-003 transfers) without re-deriving the registration boilerplate.
136+ ///
137+ /// Funding policy: every identity is registered with `funding_per`
138+ /// credits charged to a freshly-derived address, so each call costs
139+ /// `n * (funding_per + register_fee)` credits from the bank. Tests
140+ /// with tight balance windows should pass conservative values —
141+ /// `30_000_000` per identity is the reference; the bank's
142+ /// `min_bank_credits` floor must cover `n * funding_per` plus
143+ /// per-tx fees.
144+ pub async fn setup_with_n_identities (
145+ n : u32 ,
146+ funding_per : dpp:: fee:: Credits ,
147+ ) -> FrameworkResult < MultiIdentitySetupGuard > {
148+ use std:: time:: Duration ;
149+
150+ use super :: framework:: wait:: wait_for_balance;
151+
152+ let base = setup ( ) . await ?;
153+ let mut identities = Vec :: with_capacity ( n as usize ) ;
154+
155+ // Each identity gets a distinct funding address so the bank's
156+ // FUNDING_MUTEX serialises funding without contending on the
157+ // same destination. We fund + observe before registration so
158+ // `register_from_addresses` finds the credits already
159+ // committed to platform.
160+ for identity_index in 0 ..n {
161+ let funding_addr = base. test_wallet . next_unused_address ( ) . await ?;
162+ base. ctx
163+ . bank ( )
164+ . fund_address ( & funding_addr, funding_per)
165+ . await ?;
166+ wait_for_balance (
167+ & base. test_wallet ,
168+ & funding_addr,
169+ funding_per,
170+ Duration :: from_secs ( 60 ) ,
171+ )
172+ . await ?;
173+
174+ let registered = base
175+ . test_wallet
176+ . register_identity_from_addresses ( funding_addr, funding_per, identity_index)
177+ . await ?;
178+ identities. push ( registered) ;
179+ }
180+
181+ Ok ( MultiIdentitySetupGuard { base, identities } )
182+ }
183+
184+ /// Guard returned by [`setup_with_n_identities`]. Wraps the base
185+ /// [`SetupGuard`] plus the freshly-registered identities.
186+ ///
187+ /// Calling [`MultiIdentitySetupGuard::teardown`] consumes the
188+ /// guard and forwards to the inner [`SetupGuard::teardown`] —
189+ /// identity-side cleanup is implicit (their funds drain back to
190+ /// the bank during the wallet sweep).
191+ pub struct MultiIdentitySetupGuard {
192+ /// Inner single-wallet guard. Holds the [`E2eContext`] and the
193+ /// shared [`wallet_factory::TestWallet`] every identity is
194+ /// derived from.
195+ pub base : SetupGuard ,
196+ /// Identities registered during setup, ordered by DIP-9 index
197+ /// `0..n`.
198+ pub identities : Vec < wallet_factory:: RegisteredIdentity > ,
199+ }
200+
201+ impl MultiIdentitySetupGuard {
202+ /// Forward to the inner [`SetupGuard::teardown`].
203+ pub async fn teardown ( self ) -> FrameworkResult < ( ) > {
204+ self . base . teardown ( ) . await
205+ }
206+ }
0 commit comments