Non-transferable auto-compounding power token for the ALEX DeFi protocol — SIP-010 compliant with permanently disabled transfers
| Contract | SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.token-apower |
| Protocol | ALEX — leading DeFi platform on Stacks |
| Source | Verified on-chain via Hiro API (/v2/contracts/source) |
| Clarity Version | Clarity 1 (pre-Clarity 4 — no as-contract? asset allowances) |
| Block Height | 45,455 (early mainnet deployment) |
| Lines of Code | 218 |
| Audit Date | 2026-02-25 |
| Auditor | cocoa007.btc |
| Confidence | 🟢 HIGH — simple token contract, fully reviewed, all findings verified against on-chain source |
APower is a non-transferable SIP-010 fungible token used in the ALEX protocol ecosystem. It implements the standard SIP-010 trait but permanently disables transfers — the transfer function always returns an error. Tokens can only be minted and burned by the contract owner or approved contracts.
The contract also provides a fixed-point math layer (8-decimal precision) with convenience wrappers like mint-fixed, burn-fixed, and mint-fixed-many for batch operations.
At deployment, ownership is transferred to .executor-dao and .alex-reserve-pool is added as an approved contract, indicating this token is managed by ALEX's DAO governance.
transfer function is intentionally disabledLocation: set-decimals, pow-decimals, fixed-to-decimals, decimals-to-fixed
Description: The token-decimals variable can be changed by the owner at any time. Since the fixed-point conversion functions (fixed-to-decimals, decimals-to-fixed) dynamically read this variable, changing decimals after tokens have been minted causes all existing balances to be reinterpreted incorrectly.
(define-public (set-decimals (new-decimals uint))
(begin
(try! (check-is-owner))
(ok (var-set token-decimals new-decimals))
)
)
(define-private (pow-decimals)
(pow u10 (unwrap-panic (get-decimals))) ;; uses mutable var!
)
Impact: If decimals changes from 8 to 6, a user with 100 APower (stored as 10,000,000,000 raw units) would appear to have 10,000 APower through the fixed-point view. The mint-fixed and burn-fixed wrappers would also mint/burn incorrect raw amounts. Any protocol integrating with the fixed-point functions would break silently.
Recommendation: Make token-decimals a constant instead of a variable, or add a guard that only allows setting decimals before any tokens are minted (when total supply is 0).
Location: burn function
Description: The burn function allows any approved contract or owner to burn tokens from any sender address without requiring the sender's authorization. There is no check that sender == tx-sender or that the sender has approved the burn.
(define-public (burn (amount uint) (sender principal))
(begin
(asserts! (or (is-ok (check-is-approved)) (is-ok (check-is-owner))) ERR-NOT-AUTHORIZED)
(ft-burn? apower amount sender) ;; burns from arbitrary sender
)
)
Impact: Any approved contract (currently .alex-reserve-pool) or the DAO owner can burn any user's APower balance without their consent. While this may be intentional for a protocol-managed token, it means users must fully trust all approved contracts. A compromised or malicious approved contract could wipe all user balances.
Recommendation: If unconditional burn is intended, document this trust assumption clearly. Otherwise, add (asserts! (is-eq sender tx-sender) ERR-NOT-AUTHORIZED) or require sender to be an approved contract as well. Note: Clarity's post-conditions can protect against unexpected burns at the transaction level.
Location: transfer function
Description: The transfer function always returns ERR-TRANSFER-FAILED regardless of parameters. The function doesn't even check its arguments — it ignores amount, sender, recipient, and memo entirely.
(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
ERR-TRANSFER-FAILED
)
Impact: Any integration (DEX, wallet, or protocol) that attempts SIP-010 transfers will silently fail. While non-transferability is the design intent, implementing the full SIP-010 trait creates a false expectation of transferability. The transfer-fixed wrapper also always fails.
Recommendation: This is by design. Consider adding a comment in the source documenting the intentional non-transferability. Integrators should check for this pattern.
Location: check-is-approved, check-is-owner
Description: Authorization checks use tx-sender instead of contract-caller. In Clarity, tx-sender is the original transaction signer and does not change through contract-to-contract calls. This means if an approved contract (A) calls another contract (B), and B calls token-apower.mint, the tx-sender check will see A (if A is the tx originator) — potentially allowing unintended transitive access.
(define-private (check-is-approved)
(ok (asserts! (default-to false (map-get? approved-contracts tx-sender)) ERR-NOT-AUTHORIZED))
)
Impact: Low in practice since APower is non-transferable and protocol-managed. But if an approved contract has a public function that can be called by anyone, and that function triggers a chain leading to APower mint/burn, the tx-sender will be the approved contract's deployer principal — which may not be in the approved-contracts map. The real risk is if the contract-owner principal is used as tx-sender in a multi-step call.
Recommendation: Consider using contract-caller for the approved-contracts check to limit authorization to direct callers only. This is standard practice for Clarity access control.
Location: (define-fungible-token apower)
Description: The token is defined without a maximum supply parameter. Clarity's define-fungible-token accepts an optional max supply — omitting it means supply is unlimited.
Impact: The owner or any approved contract can mint unlimited tokens. For a protocol-managed auto-compounding token, this may be intentional, but it means there is no on-chain supply ceiling enforced at the language level.
Recommendation: If a maximum supply is intended, add it to the token definition. Otherwise, this is acceptable for a protocol-managed token where supply is controlled by governance.
Location: mint-fixed-many
Description: The mint-fixed-many function performs an authorization check, then calls mint-fixed-many-iter → mint-fixed → mint, which performs the same authorization check again for each item in the list.
(define-public (mint-fixed-many (recipients (list 200 {amount: uint, recipient: principal})))
(begin
(asserts! (or (is-ok (check-is-approved)) (is-ok (check-is-owner))) ERR-NOT-AUTHORIZED)
(ok (map mint-fixed-many-iter recipients)) ;; each iter calls mint → re-checks auth
)
)
Impact: Wastes runtime costs by checking authorization 201 times (1 + 200 iterations) instead of once. No security impact — it's just inefficient.
Recommendation: Create an internal mint-unchecked private function for use by mint-fixed-many-iter after the outer auth check. This saves gas on batch operations.
The contract follows a common ALEX pattern:
.executor-dao)The fixed-point layer uses ONE_8 = 10^8 as the base unit. The conversion functions multiply/divide between raw and fixed representations. This is sound as long as decimals remains at 8 (the default), aligning 1:1 with the fixed-point base — but fragile if decimals is ever changed (see M-01).
APower is a straightforward non-transferable token with a clean implementation. The main concerns are the mutable decimals (M-01) which could break fixed-point math, and the unchecked burn target (M-02) which requires trust in all approved contracts. Neither is critical given the token's non-transferable nature and DAO governance, but both represent avoidable risk.
The contract has been deployed since early mainnet (block 45,455) and is managed by ALEX's executor-dao. Its simplicity is a strength — there are no complex interactions or reentrancy vectors.
Overall risk: Low-Medium. The trust model is centralized (DAO + approved contracts), which is appropriate for a protocol-internal accounting token.