Convert string to principal
Parse string addresses into principal types using c32 decoding in Clarity
(define-read-only (string-to-principal? (input (string-ascii 82)))(let (;; Find the dot separator for contract addresses(dot (default-to (len input) (index-of? input ".")));; Extract address part (skip first char which is version)(addr (unwrap! (slice? input u1 dot) ERR_INVALID_LENGTH));; Decode c32 characters to numbers(addressc32 (map unwrap-panic-uint (filter is-some-uint (map c32-index addr))));; Validate all characters are valid c32(isValidChars (asserts! (is-eq (len addr) (len addressc32)) ERR_INVALID_CHAR));; Extract version and decode address data(version (unwrap-panic (element-at? addressc32 u0)))(decoded (decode-address addressc32));; Verify checksum(checksum (verify-checksum decoded version)));; Construct principal with or without contract name(match (slice? input (+ u1 dot) (len input)) contract(principal-construct? (to-byte version) (get-address-bytes decoded) contract)(principal-construct? (to-byte version) (get-address-bytes decoded)))));; Example usage(string-to-principal? "SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7");; Returns (some SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7)(string-to-principal? "SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7.my-contract");; Returns (some SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7.my-contract)
Use cases
- Parsing user input addresses in contracts
- Converting stored string addresses to principals
- Validating address formats before use
- Building dynamic contract calls with string inputs
Key concepts
Stacks addresses use c32 encoding:
- c32 alphabet:
0123456789ABCDEFGHJKMNPQRSTVWXYZ
(no I, L, O, U) - Checksum: Last 4 bytes verify address integrity
- Version byte: First character indicates address type
- Contract addresses: Include
.contract-name
suffix
Complete implementation
;; Constants(define-constant C32_CHARS "0123456789ABCDEFGHJKMNPQRSTVWXYZ")(define-constant ALL_HEX 0x00010203...FF) ;; All hex bytes;; Error codes(define-constant ERR_INVALID_CHAR (err u100))(define-constant ERR_INVALID_LENGTH (err u101))(define-constant ERR_INVALID_CHECKSUM (err u105));; Helper to get c32 character index(define-read-only (c32-index (c (string-ascii 1)))(index-of? C32_CHARS c));; Decode c32 to bytes(define-read-only (decode (i uint) (out (list 26 uint)))(let ((cb (unwrap-panic (element-at? out u1)))(carry (+ (unwrap-panic (element-at? out u0)) (bit-shift-left i cb)))(carryBits (+ u5 cb)))(unwrap-panic (as-max-len? (concat(if (>= carryBits u8)(list (bit-shift-right carry u8) (- carryBits u8) (bit-and carry (- (bit-shift-left u1 u8) u1)))(list carry carryBits))(default-to (list) (slice? out u2 (len out)))) u26))))
Address version types
Version | Type | Example |
---|---|---|
S | Standard (mainnet) | SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7 |
T | Standard (testnet) | ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7 |
M | Multisig (mainnet) | SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7 |
N | Multisig (testnet) | SN2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7 |
Validation example
;; Validate address format before conversion(define-read-only (validate-address-string (address (string-ascii 82)))(let (;; Check minimum length(len-check (asserts! (>= (len address) u28) (err u1)));; Check starts with valid version(version-check (asserts!(is-some (index-of? "STMNP" (unwrap-panic (slice? address u0 u1))))(err u2)));; Try to convert(converted (string-to-principal? address)))(match convertedprincipal (ok principal)(err u3))))
Batch conversion
;; Convert list of string addresses(define-read-only (convert-address-list (addresses (list 10 (string-ascii 82))))(map string-to-principal? addresses));; Filter valid addresses(define-read-only (get-valid-addresses (addresses (list 10 (string-ascii 82))))(filter is-some (map string-to-principal? addresses)))
Security note
Always validate string addresses before converting to principals. Invalid addresses will cause the conversion to fail, potentially blocking contract execution.