> **v0 - Unaudited**
> This is an initial version of this package that has not yet been formally audited.
> A fully audited version will be published as a subsequent release.
> Use in production at your own risk.
# `ownable` - Ownership pattern for realms
Provides an `Ownable` object that gates privileged operations behind a single owner address. Embed it in a realm (or any struct) to restrict actions like configuration changes, withdrawals, or upgrades.
## Usage
```go
package myrealm
import (
"chain/runtime"
"gno.land/p/nt/ownable/v0"
)
// The owner address is chosen explicitly at construction. A common
// choice is the deployer, captured in init after confirming it is a
// real user call.
var owner *ownable.Ownable
func init() {
caller := runtime.PreviousRealm()
if !caller.IsUserCall() {
panic("must be deployed by a user")
}
owner = ownable.NewWithAddress(caller.Address())
}
// SetFee is gated: only the current owner may call it.
func SetFee(cur realm, newFee int64) {
if !cur.IsCurrent() {
panic("spoofed realm")
}
owner.AssertOwnedBy(cur.Previous().Address())
fee = newFee
}
// Hand the realm over. TransferOwnership itself verifies the caller is owner.
func TransferOwner(cur realm, to address) error {
return owner.TransferOwnership(0, cur, to)
}
```
There is no auth-mode flag. The single `NewWithAddress` constructor replaced the
old `New` / `NewWithOrigin` / `NewWithAddressByPrevious` sugar: the realm now picks
the owner address explicitly rather than baking a runtime walk into the struct.
## API
```go
type Ownable struct{ /* unexported */ }
const OwnershipTransferEvent = "OwnershipTransfer"
var (
ErrUnauthorized = errors.New("ownable: caller is not owner")
ErrInvalidAddress = errors.New("ownable: new owner address is invalid")
)
// NewWithAddress is the only constructor: the realm picks the owner
// address explicitly (e.g. cur.Previous().Address() after checking
// cur.Previous().IsUserCall() in init).
func NewWithAddress(addr address) *Ownable
// Queries (caller supplies the address to check).
func (o *Ownable) Owner() address // "" if o is nil or ownership was dropped
func (o *Ownable) OwnedBy(addr address) bool // true if addr is the current owner
func (o *Ownable) AssertOwnedBy(addr address) // panics with ErrUnauthorized if addr is not the owner
// Authority mutation (thread the caller's own cur; pass 0 as the first arg).
func (o *Ownable) TransferOwnership(_ int, rlm realm, newOwner address) error
func (o *Ownable) DropOwnership(_ int, rlm realm) error // sets owner to "" — irreversible
```
## Notes
- Authority-mutating methods assert `rlm.IsCurrent()` and identify the caller as `rlm.Previous().Address()`, which must equal the current owner. The principal is therefore unforgeable: an attacker cannot supply an arbitrary caller address. Pass `0` as the placeholder first arg and your own `cur` as `rlm`.
- Read helpers (`OwnedBy`, `AssertOwnedBy`) take a bare address; the caller extracts it, guarding with `cur.IsCurrent()` before reading `cur.Previous().Address()`.
- `TransferOwnership` rejects an invalid `newOwner` with `ErrInvalidAddress`. Both mutators emit `OwnershipTransferEvent` with `from` and `to` fields.
- `DropOwnership` is permanent: `owner` becomes `""`, so every owner-gated action becomes unreachable.
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.