Skip to content

Commit fc34f5b

Browse files
werner-stacksgitbook-bot
authored andcommitted
GITBOOK: Document signer-calldata, complete the error table, quarantine PoX-4 comparisons
1 parent 1b58c9f commit fc34f5b

1 file changed

Lines changed: 63 additions & 30 deletions

File tree

‎docs/operate/deploy-a-signer-manager-contract.md‎

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ Four roles are involved and they do not have to be the same key. Only the staker
2929

3030
### What a signer is in PoX-5
3131

32-
* A signer is no longer a `{ btcRewardAddress, signerKey }` tuple in a reward set. It is a **signer-manager contract**, identified in all contract state by its principal, with one associated signer key.
33-
* Every staker (STX-only or bond participant) delegates to a signer-manager. There is no separate "solo" signer set: a solo staker is just a signer-manager with one member.
34-
* The pox-5 contract hooks into the manager during staking actions via `validate-stake!`, replacing PoX-4's per-transaction signer signatures that scoped each call by amount, period, and pox-addr.
32+
* A signer is a **signer-manager contract**, identified in all contract state by its principal, with one associated signer key.
33+
* Every staker (STX-only or bond participant) delegates to a signer-manager. A solo staker is a signer-manager with one member, so there is no separate "solo" signer set.
34+
* The pox-5 contract hooks into the manager during staking actions via `validate-stake!`.
3535
* Most operators do not need to write a contract at all. The reference signer-manager covers the common case: it accepts delegations, registers the signer key once, and handles reward accounting and onward distribution. Write your own only for custom needs: allowlisting members, different fee logic, custom sBTC routing.
3636

3737
{% hint style="warning" %}
38-
**"Immutable" applies to the code, not the terms.** A deployed contract's Clarity source can never change, but the reference manager's _parameters_ can. An admin can call `update-fees` at any time, up to 99.99%, and `update-admin` lets any existing admin add another. Stakers should read a manager's admin set and current fee, not assume its economics are fixed.
38+
**"Immutable" applies to the code, not the terms.** A deployed contract's Clarity source can never change, but the reference manager's _parameters_ can. An admin can call `update-fees` at any time, up to 99.99%, and `update-admin` lets any existing admin add or remove an admin, including itself. Stakers should read a manager's admin set and current fee rather than assume its economics are fixed.
3939
{% endhint %}
4040

4141
### The `signer-manager` trait
@@ -68,6 +68,20 @@ Your contract must also gate `validate-stake!` so only pox-5 can call it. The re
6868
)
6969
```
7070

71+
#### `signer-calldata` is yours to define
72+
73+
pox-5 treats `signer-calldata` as opaque bytes. It forwards the buffer to `validate-stake!` and discards it, storing nothing and parsing nothing. Any meaning it carries is a convention your contract defines, and any value that must survive to claim time is state your contract has to persist itself.
74+
75+
The reference manager uses it to let a staker elect a native BTC payout. It deserializes the buffer with `from-consensus-buff?` into `{ pox-addr: { version, hashbytes }, max-fee }`, validates the address with `check-pox-addr`, and stores it in a `pox-addrs` map keyed by the staker principal:
76+
77+
```clarity
78+
(map-set pox-addrs staker pox-addr)
79+
```
80+
81+
At claim time `claim-staker-rewards` reads that entry back. With a record it calls `sbtc-withdrawal::initiate-withdrawal-request`, and without one it transfers sBTC directly.
82+
83+
Two consequences worth building around if you write your own manager. `validate-stake!` fires again on every `stake-update`, so a second call overwrites whatever the first stored, giving stakers rotation for free. And in the reference manager, calldata of `none` on a later call **deletes** the stored entry rather than preserving it, so a staker who re-stakes without resupplying their address reverts to sBTC payouts with no error.
84+
7185
### Deploy your contract
7286

7387
Deploy whichever signer-manager you have reviewed. The [reference implementation](https://github.com/stx-labs/signer-sidekick/blob/11f8ff79e309db14357c4adfbbe31e1aeb7cd17e/contracts/reference-manager/generated/mainnet/signer-manager.clar) is the recommended starting point today, and more will follow, including [one with a lower fee ceiling](https://explorer.hiro.so/txid/SPMPMA1V6P430M8C91QS1G9XJ95S59JS1TZFZ4Q4.fastpool-max500-signer-manager?chain=mainnet\&tab=sourceCode) (`MAX_FEE_BIPS u500`).
@@ -88,23 +102,10 @@ The reference manager's deployer becomes its first admin automatically (`(map-se
88102
Ledger Stacks App versions through `0.26.17` cannot sign a deployment payload that carries an explicit Clarity version, so leave any "force Clarity 6 payload" option switched off. With the version omitted, the node defaults the contract to Clarity 6, which it does since Epoch 4.0 activated. The Leather extension omits the version for you from version `6.107.0` onward. Stacks App `0.27.x` is expected to remove the restriction.
89103
{% endhint %}
90104

91-
### Register the signer key: `register-signer`
92-
93-
After deployment, the signer-manager contract itself calls `pox-5::register-signer` to bind itself to a signer key.
94-
95-
```clarity
96-
;; Only the signer contract itself can register itself
97-
(asserts! (is-eq contract-caller signer)
98-
ERR_UNAUTHORIZED_SIGNER_REGISTRATION
99-
)
100-
```
101-
102-
`contract-caller` must equal the signer-manager. It cannot be forwarded through an intermediary contract, and it cannot be sent by an EOA on the manager's behalf. Violating this aborts with `ERR_UNAUTHORIZED_SIGNER_REGISTRATION (u26)`.
103-
104-
Registration also re-checks the grant first, so the grant must already be in place. [Generate a Signer Signature](stacking-stx/generate-signer-signature.md) has the `stacks-signer` command that produces it. Read the registered key back on-chain with `get-signer-info`, or via `fetchSignerInfo` in the SDK.
105-
106105
### Grant the signer-manager permission: `grant-signer-key`
107106

107+
The grant comes first. Registration re-checks it, so a manager cannot register a key it has not already been granted. [Generate a Signer Signature](stacking-stx/generate-signer-signature.md) has the `stacks-signer` command that produces one.
108+
108109
{% stepper %}
109110
{% step %}
110111
#### Build the SIP-018 grant message
@@ -126,13 +127,13 @@ Registration also re-checks the grant first, so the grant must already be in pla
126127
}
127128
```
128129

129-
The grant binds a signer key to a signer-manager and **nothing else**: no `max-amount`, `period`, `reward-cycle`, or `pox-addr`. The on-chain grant map is keyed only on `{ signer-key, signer-manager }`; replay protection adds `auth-id` in a separate map. This is the substantive break from PoX-4's per-transaction scoped signatures.
130+
The grant binds a signer key to a signer-manager and **nothing else**: no `max-amount`, `period`, `reward-cycle`, or `pox-addr`. The on-chain grant map is keyed only on `{ signer-key, signer-manager }`; replay protection adds `auth-id` in a separate map.
130131
{% endstep %}
131132

132133
{% step %}
133134
#### Sign it off-chain, then submit from the manager
134135

135-
The signer-key holder signs the hash off-chain with `signSignerGrant`. The **signer-manager contract** then submits `grant-signer-key`, which enforces the same caller rule as registration:
136+
The signer-key holder signs the hash off-chain with `signSignerGrant`. The **signer-manager contract** then submits `grant-signer-key`, and `contract-caller` must be the manager itself:
136137

137138
```clarity
138139
;; Only the signer contract itself can call this function to grant a signer key
@@ -199,6 +200,21 @@ Generate the signature on the signer host. Never paste a signer private key or s
199200
Builders in `@stacks/bitcoin-staking` attach no post-conditions and default to `Deny` mode. Any transaction you build that moves an asset needs explicit post-conditions.
200201
{% endhint %}
201202

203+
### Register the signer key: `register-signer`
204+
205+
With the grant in place, the signer-manager contract calls `pox-5::register-signer` to bind itself to the signer key.
206+
207+
```clarity
208+
;; Only the signer contract itself can register itself
209+
(asserts! (is-eq contract-caller signer)
210+
ERR_UNAUTHORIZED_SIGNER_REGISTRATION
211+
)
212+
```
213+
214+
Same caller rule as the grant: `contract-caller` must equal the signer-manager. It cannot be forwarded through an intermediary contract, and it cannot be sent by an EOA on the manager's behalf. Violating this aborts with `ERR_UNAUTHORIZED_SIGNER_REGISTRATION (u26)`.
215+
216+
Read the registered key back on-chain with `get-signer-info`, or via `fetchSignerInfo` in the SDK.
217+
202218
### Revoke a grant: `revoke-signer-grant`
203219

204220
Takes `signer-manager` first, then `signer-key`. It must be sent **directly** by the Stacks principal derived from the signer key. `contract-caller` is the authorization, so it cannot be forwarded through an intermediary contract, and no SIP-018 message is needed:
@@ -237,14 +253,21 @@ From pox-5:
237253
| `u26` | `ERR_UNAUTHORIZED_SIGNER_REGISTRATION` | `register-signer` or `grant-signer-key` invoked with `contract-caller != signer-manager`. |
238254
| `u49` | `ERR_REENTRANT_CALL` | Reentrant call into pox-5 while a `signer-manager-validate-stake` call was in flight. |
239255

240-
The reference signer-manager has its own separate error namespace, which you will hit during setup rather than staking:
241-
242-
| Code | Constant | Meaning |
243-
| ------- | ------------------------- | ------------------------------------------------------------------- |
244-
| `u1002` | `ERR_UNAUTHORIZED_ADMIN` | An admin-only call from a non-admin, or proxied through a contract. |
245-
| `u1005` | `ERR_INVALID_FEES_BIPS` | Fee outside the allowed range. |
246-
| `u1006` | `ERR_UNAUTHORIZED_CALLER` | `validate-stake!` invoked by something other than pox-5. |
247-
| `u1007` | `ERR_INSUFFICIENT_FEES` | Attempted to withdraw more fees than have accrued. |
256+
The reference signer-manager has its own separate error namespace, which you will hit during setup and distribution rather than staking:
257+
258+
| Code | Constant | Meaning |
259+
| ------- | -------------------------------- | ----------------------------------------------------------------------------------- |
260+
| `u1001` | `ERR_NO_CLAIMABLE_REWARDS` | Nothing to claim, or a staker's share does not cover the maximum L1 fee they set. |
261+
| `u1002` | `ERR_UNAUTHORIZED_ADMIN` | An admin-only call from a non-admin, or proxied through a contract. |
262+
| `u1003` | `ERR_INVALID_CALLDATA` | `signer-calldata` did not deserialize into the expected pox-addr and max-fee tuple. |
263+
| `u1004` | `ERR_INVALID_POX_ADDR` | Address version above `u6`, or hashbytes the wrong length for that version. |
264+
| `u1005` | `ERR_INVALID_FEES_BIPS` | Fee outside the allowed range. |
265+
| `u1006` | `ERR_UNAUTHORIZED_CALLER` | `validate-stake!` invoked by something other than pox-5. |
266+
| `u1007` | `ERR_INSUFFICIENT_FEES` | Attempted to withdraw more fees than have accrued. |
267+
| `u1008` | `ERR_UNKNOWN_WITHDRAWAL_REQUEST` | Settlement or reclaim referenced a withdrawal request the manager has no record of. |
268+
| `u1009` | `ERR_WITHDRAWAL_NOT_REJECTED` | Reclaim attempted on a withdrawal the sBTC registry has not rejected. |
269+
| `u1010` | `ERR_NO_REFUNDS` | Nothing available to sweep. |
270+
| `u1011` | `ERR_WITHDRAWAL_NOT_ACCEPTED` | Settlement attempted on a withdrawal the sBTC registry has not accepted. |
248271

249272
### Signer fees
250273

@@ -257,7 +280,7 @@ In the reference manager:
257280

258281
Both require a direct call from an admin EOA: `authorize-admin` asserts `(is-eq contract-caller tx-sender)`, so admin actions cannot be proxied through another contract.
259282

260-
The fee is a percentage of sBTC rewards, deducted before payout, including before an sBTC-to-L1 BTC withdrawal. It applies to both bond participants and STX-only stakers. A dedicated **Take a signer fee** guide covers the operator workflow in detail.
283+
The fee is a percentage of sBTC rewards, deducted before payout, including before an sBTC-to-L1 BTC withdrawal. It applies to both bond participants and STX-only stakers. [Take a Signer Fee](take-a-signer-fee.md) covers the operator workflow in detail.
261284

262285
### Use stacks-cli to deploy the signer manager contract
263286

@@ -402,3 +425,13 @@ Admin calls cannot be proxied: `authorize-admin` asserts `contract-caller` equal
402425
#### Why there is no Clarity version flag
403426

404427
`publish` emits payload type `0x01` with no version byte, and the node applies its current default, which is Clarity 6 since Epoch 4.0.
428+
429+
### What changed from PoX-4
430+
431+
If you operated under PoX-4, three things are different.
432+
433+
A signer used to be a `{ btcRewardAddress, signerKey }` tuple in a reward set. It is now a contract principal with one associated signer key.
434+
435+
PoX-4 scoped every staking call with a per-transaction signer signature carrying an amount, a period, and a pox-addr. PoX-5 replaces all of that with the one-time grant above, which carries a signer-manager and an `auth-id` and nothing else. See [Generate a Signer Signature](stacking-stx/generate-signer-signature.md).
436+
437+
Reward routing has moved out of the protocol. `register-for-bond` carries no `pox-addr` argument, and pox-5 pays sBTC to the manager rather than BTC to an address in a reward set. Electing a native BTC payout is now a signer-manager concern, handled through `signer-calldata`.

0 commit comments

Comments
 (0)