@layerzerolabs/protocol-stellar-v2 0.2.83 → 0.2.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -75,6 +75,77 @@ The `init_ownable_oapp` function:
75
75
  2. Stores the LayerZero endpoint address
76
76
  3. Sets a delegate on the endpoint
77
77
 
78
+ ## Access control
79
+
80
+ OApps use a two-layer authorization model. The `Auth` trait returns the contract's authorizer — the owner under `#[lz_contract]`, or the contract itself under `#[lz_contract(multisig)]`. On top of that, `RoleBasedAccessControl` adds OpenZeppelin-style role membership so administrative actions can be delegated without surrendering ownership. A vanilla `#[lz_contract] #[oapp]` contract exposes both layers automatically.
81
+
82
+ ### Custom roles
83
+
84
+ Declare a role as a `&str` constant and gate methods with `#[only_role]` (role check + `require_auth`) or `#[has_role]` (role check only — use when the address has already been authenticated to avoid a duplicate `require_auth` panic):
85
+
86
+ ```rust
87
+ use common_macros::only_role;
88
+
89
+ pub const CONFIG_MANAGER_ROLE: &str = "CONFIG_MANAGER";
90
+
91
+ #[contract_impl]
92
+ impl MyOApp {
93
+ #[only_role(operator, CONFIG_MANAGER_ROLE)]
94
+ pub fn set_config(env: &Env, value: u64, operator: &Address) {
95
+ // operator must hold CONFIG_MANAGER_ROLE
96
+ }
97
+ }
98
+ ```
99
+
100
+ The role argument is expanded to `Symbol::new(env, ROLE)` internally.
101
+
102
+ ### Managing roles
103
+
104
+ The standard RBAC entry points are auto-exposed by `#[oapp]`:
105
+
106
+ ```rust
107
+ let role = Symbol::new(env, "CONFIG_MANAGER");
108
+
109
+ oapp_client.grant_role(&account, &role, &caller); // authorizer or role-admin
110
+ oapp_client.revoke_role(&account, &role, &caller); // authorizer or role-admin
111
+ oapp_client.renounce_role(&role, &account); // self only
112
+ oapp_client.set_role_admin(&role, &admin_role); // authorizer only
113
+ oapp_client.remove_role_admin(&role); // authorizer only
114
+
115
+ oapp_client.has_role(&account, &role); // Option<u32>
116
+ oapp_client.get_role_admin(&role); // Option<Symbol>
117
+ oapp_client.get_role_member_count(&role); // u32
118
+ oapp_client.get_role_member(&role, index); // Address — panics if out of bounds
119
+ oapp_client.get_existing_roles(); // Vec<Symbol>, capped at 256
120
+ ```
121
+
122
+ Operations emit `RoleGranted`, `RoleRevoked`, and `RoleAdminChanged` events; failures surface as `RbacError` variants (`Unauthorized`, `RoleNotHeld`, `AdminRoleNotFound`, `MaxRolesExceeded`, etc.).
123
+
124
+ ### Constructor-time setup
125
+
126
+ Constructors run before an authorizer is in scope, so use the `_no_auth` helpers to seed roles:
127
+
128
+ ```rust
129
+ use utils::rbac::{grant_role_no_auth, set_role_admin_no_auth};
130
+
131
+ #[contract_impl]
132
+ impl MyOApp {
133
+ pub fn __constructor(
134
+ env: &Env,
135
+ owner: &Address,
136
+ endpoint: &Address,
137
+ delegate: &Address,
138
+ config_manager: &Address,
139
+ ) {
140
+ init_ownable_oapp::<Self>(env, owner, endpoint, delegate);
141
+
142
+ let role = Symbol::new(env, "CONFIG_MANAGER");
143
+ grant_role_no_auth(env, config_manager, &role, owner);
144
+ set_role_admin_no_auth(env, &role, &Symbol::new(env, "CONFIG_ADMIN"));
145
+ }
146
+ }
147
+ ```
148
+
78
149
  ## Peer management
79
150
 
80
151
  Before sending or receiving messages, configure peers for each destination chain:
@@ -217,10 +288,11 @@ See `contracts/oapps/counter/` for a complete example demonstrating:
217
288
 
218
289
  ## Key traits summary
219
290
 
220
- | Trait | Purpose | Default behavior |
221
- | -------------------- | -------------------------------- | ------------------------------------------- |
222
- | `OAppCore` | Peer management, endpoint access | Stores endpoint, manages peers |
223
- | `OAppSenderInternal` | Send cross-chain messages | Handles fee payment and message dispatch |
224
- | `OAppReceiver` | Receive cross-chain messages | Clears payload, delegates to `__lz_receive` |
225
- | `LzReceiveInternal` | Application message handling | **Must implement** |
226
- | `OAppOptionsType3` | Enforced execution options | No enforced options |
291
+ | Trait | Purpose | Default behavior |
292
+ | ------------------------ | -------------------------------- | --------------------------------------------------------------- |
293
+ | `OAppCore` | Peer management, endpoint access | Stores endpoint, manages peers |
294
+ | `OAppSenderInternal` | Send cross-chain messages | Handles fee payment and message dispatch |
295
+ | `OAppReceiver` | Receive cross-chain messages | Clears payload, delegates to `__lz_receive` |
296
+ | `LzReceiveInternal` | Application message handling | **Must implement** |
297
+ | `OAppOptionsType3` | Enforced execution options | No enforced options |
298
+ | `RoleBasedAccessControl` | Role-based access control | Provided automatically; manages role grants and admin hierarchy |
package/docs/oft-guide.md CHANGED
@@ -69,6 +69,8 @@ impl OFTInternal for MyOFT {
69
69
  }
70
70
  ```
71
71
 
72
+ When the underlying token is a Stellar Classic Asset wrapped as a SAC, you can route mints through the [SAC Manager](#sac-manager) instead of granting raw SAC admin authority to the OFT contract.
73
+
72
74
  ### LockUnlock
73
75
 
74
76
  Locks tokens in contract on send, unlocks on receive. Use for wrapping existing tokens (OFT Adapter pattern).
@@ -318,6 +320,79 @@ oft.set_rate_limit(
318
320
  let available = oft.rate_limit_capacity(&Direction::Outbound, 30101);
319
321
  ```
320
322
 
323
+ ## SAC Manager
324
+
325
+ Wraps a Stellar Asset Contract (SAC) behind role-based admin so OFT MintBurn can mint without granting raw SAC admin authority to the OFT contract. Useful when the underlying token is a Stellar Classic Asset.
326
+
327
+ The SAC Manager:
328
+
329
+ - Becomes the admin of the underlying SAC
330
+ - Exposes a `mint` entry point that the OFT calls during credit
331
+ - Gates SAC admin operations (`mint`, `clawback`, `set_authorized`, `set_admin`) behind RBAC roles
332
+ - Inherits ownership and role management from `Ownable` and `RoleBasedAccessControl`
333
+
334
+ ### Trust Model Requirement
335
+
336
+ **The issuer account must be locked (master weight set to 0).** In Stellar classic assets, transfers from/to the issuer are equivalent to minting/burning. The issuer can always mint more tokens and perform other classic operations directly, even when an explicit admin (this contract) is set. If the issuer account is not locked, the RBAC model enforced by this contract can be bypassed, breaking the trust model.
337
+
338
+ ### Roles
339
+
340
+ Four roles guard the wrapped admin operations. Roles are passed as `Symbol` values to `grant_role`:
341
+
342
+ - `MINTER_ROLE`: authorizes `mint` (granted to the OFT contract for the credit path)
343
+ - `CLAWBACK_ROLE`: authorizes `clawback`
344
+ - `BLACKLISTER_ROLE`: authorizes `set_authorized`
345
+ - `ADMIN_MANAGER_ROLE`: authorizes `set_admin`
346
+
347
+ ### Initialization
348
+
349
+ Deploy the SAC Manager and grant `MINTER_ROLE` to the OFT contract:
350
+
351
+ ```rust
352
+ use soroban_sdk::{Env, Address, Symbol};
353
+
354
+ // Deploy the SAC Manager bound to the SAC and an owner
355
+ let manager = env.register(SACManager, (&sac_token, &owner));
356
+ let manager_client = SACManagerClient::new(&env, &manager);
357
+
358
+ // Grant MINTER_ROLE to the OFT contract so it can mint on credit
359
+ let minter_role = Symbol::new(&env, "MINTER_ROLE");
360
+ manager_client.grant_role(&oft_address, &minter_role, &owner);
361
+
362
+ // Make the manager the admin of the SAC
363
+ sac_client.set_admin(&manager);
364
+ ```
365
+
366
+ ### SAC admin operations
367
+
368
+ Each operation is gated by an `#[only_role(operator, ROLE)]` check. The `operator` argument must hold the role and authorize the call.
369
+
370
+ ```rust
371
+ fn mint(env: &Env, to: &Address, amount: i128, operator: &Address); // MINTER_ROLE
372
+ fn clawback(env: &Env, from: &Address, amount: i128, operator: &Address); // CLAWBACK_ROLE
373
+ fn set_authorized(env: &Env, id: &Address, authorize: bool, operator: &Address); // BLACKLISTER_ROLE
374
+ fn set_admin(env: &Env, new_admin: &Address, operator: &Address); // ADMIN_MANAGER_ROLE
375
+ ```
376
+
377
+ `mint` and `clawback` proxy to the SAC's corresponding admin functions. `clawback` additionally requires the SAC issuer to have the `ClawbackEnabled` flag set; `set_authorized` requires the `Revocable` flag.
378
+
379
+ ### Integrating with OFT MintBurn
380
+
381
+ 1. Deploy the SAC and lock its issuer (master weight = 0).
382
+ 2. Deploy the SAC Manager with the SAC address and owner.
383
+ 3. Make the SAC Manager the admin of the SAC via `sac_client.set_admin(&manager)`.
384
+ 4. Deploy the OFT in MintBurn mode, configured with the SAC as the token and the SAC Manager as the mint authority.
385
+ 5. Grant `MINTER_ROLE` on the SAC Manager to the OFT contract address.
386
+
387
+ On send, the OFT burns directly on the SAC via SEP-41 `burn(sender, amount)` — this is signed by the sender and does not go through the SAC Manager. On receive, the OFT calls `manager.mint(to, amount, oft_address)`, which the SAC Manager forwards to the SAC.
388
+
389
+ ### Ownership and role management
390
+
391
+ The SAC Manager inherits standard ownership and RBAC entry points:
392
+
393
+ - `owner`, `pending_owner`, `transfer_ownership`, `begin_ownership_transfer`, `accept_ownership`
394
+ - `grant_role`, `revoke_role`, `renounce_role`, `set_role_admin`, `remove_role_admin`
395
+
321
396
  ## Key traits summary
322
397
 
323
398
  | Trait | Purpose | Exposed |
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@layerzerolabs/protocol-stellar-v2",
3
- "version": "0.2.83",
3
+ "version": "0.2.85",
4
4
  "private": false,
5
5
  "license": "LZBL-1.3",
6
6
  "devDependencies": {
7
7
  "@types/node": "^22.18.6",
8
8
  "tsx": "^4.19.3",
9
9
  "typescript": "^5.8.2",
10
- "@layerzerolabs/stellar-ts-bindings-gen": "0.2.83",
11
- "@layerzerolabs/vm-tooling-stellar": "0.2.83",
12
- "@layerzerolabs/common-node-utils": "0.2.83"
10
+ "@layerzerolabs/stellar-ts-bindings-gen": "0.2.85",
11
+ "@layerzerolabs/vm-tooling-stellar": "0.2.85",
12
+ "@layerzerolabs/common-node-utils": "0.2.85"
13
13
  },
14
14
  "publishConfig": {
15
15
  "access": "public",
package/sdk/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layerzerolabs/lz-v2-stellar-sdk",
3
- "version": "0.2.83",
3
+ "version": "0.2.85",
4
4
  "private": false,
5
5
  "description": "TypeScript SDK for endpoint-v2 Stellar contract",
6
6
  "repository": {