
Casper Carbon 구축하기: 검증 가능한 탄소 시장을 위한 자율형 AI 에이전트 (Autonomous AI Agents)
요약
Casper Network를 활용하여 탄소 배출권을 검증하고 감독하는 자율형 AI 에이전트 시스템 구축 방법을 다룹니다. 에이전트가 온체인 행위자로서 권한을 갖고, 계약을 통해 권력 분립이 강제되는 검증 가능한 AI 아키텍처를 소개합니다.
핵심 포인트
- AI 에이전트를 일급 온체인 행위자로 설정하여 신뢰 문제 해결
- 계약(Contract)을 통해 에이전트의 권한과 권력 분립을 강제
- Odra 스토리지 레이아웃 역공학을 통한 신뢰 없는 상태 읽기 구현
- 해시 커밋된 추론을 통해 누구나 감사 가능한 검증 가능한 AI 패턴 적용
Casper Network에서 실제 탄소 배출권(carbon credits)을 검증, 감독 및 마켓 메이킹(market-make)하는, 서로를 신뢰하지 않는 AI 에이전트 군집을 구축한 방법 — 인덱서(indexer) 없이 전체 스택이 작동하도록 Odra의 스토리지 레이아웃(storage layout)을 역공학(reverse-engineering)하는 과정을 포함합니다.
탄소 시장은 신뢰의 문제를 안고 있습니다. 단일 상쇄 프로젝트(offset project)를 검증하는 데는 2~5년의 인간 감사(auditing)가 소요되며, 프로젝트 이후에 아무것도 모니터링하지 않기 때문에 사기가 인증 과정을 통과하는 일이 일상적으로 발생합니다. 또한 발행(issuance), 가격 책정(pricing), 폐기(retirement)로 이어지는 전체 라이프사이클이 폐쇄적인 등록부 데이터베이스(private registry databases)에 존재합니다.
Casper Carbon은 다른 아키텍처를 시도한 저의 결과물입니다: AI 에이전트를 일급 온체인 행위자(first-class on-chain actors)로 설정하는 것입니다. 단순히 "LLM이 보고서를 작성했다"는 수준이 아니라, 에이전트가 자체적인 Casper 계정, 계약(contract)에 의해 강제되는 권한, 온체인 평판(on-chain reputations), 그리고 누구나 브라우저에서 감사할 수 있는 해시 커밋된 추론(hash-committed reasoning)을 갖도록 했습니다.
이 포스트에서는 계약 설계, 코드가 아닌 체인에 의해 강제되는 에이전트 권력 분립(separation-of-powers), 에이전트와 대시보드 모두가 신뢰 없이(trustlessly) 상태를 읽을 수 있게 해주는 Odra 스토리지 레이아웃 역공학, 그리고 "검증 가능한 AI (verifiable AI)" 패턴과 같은 흥미로운 엔지니어링 과정을 살펴봅니다.
Repo: github.com/harishkotra/casper-carbon
아키텍처 (The architecture)
세 개의 에이전트, 네 개의 계약, 하나의 대시보드. 핵심적인 설계 결정은 다음과 같습니다: 에이전트가 아니라 계약이 권위의 원천(source of authority)이라는 점입니다.
체인에 의해 강제되는 권력 분립 (Separation of powers, enforced by the chain)
AgentRegistry 계약은 각 에이전트의 Address를 정확히 하나의 유형화된 ID로 매핑합니다:
#[odra::odra_type]
pub enum AgentType { Verifier, Marker, Compliance }
...
프로젝트 등록부의 모든 상태 변경 엔트리 포인트(state-changing entry point)는 작업을 수행하기 전에 **교차 계약 권한 확인 (cross-contract authorization check)**을 수행합니다:
pub fn verify_project(&mut self, project_id: u32, score: u8,
credit_supply: U256, reasoning_hash: String) {
let caller = self.env().caller();
...
그 결과: 검증자(verifier)는 Verifier로 등록된 secp256k1 계정 하에서 실행되며, 컴플라이언스(compliance)는 Compliance로 등록된 별도의 ed25519 계정 하에서 실행됩니다. 검증자 키가 탈취되더라도 물리적으로 프로젝트를 슬래싱(slash)할 수 없으며, 컴플라이언스 키가 탈취되더라도 프로젝트를 검증할 수 없습니다. 에이전트 측의 코드가 이를 강제하는 것이 아니라, 체인(chain)이 이를 강제합니다.
실제 사례: 제가 처음 작성한 시드 스크립트(seed script)는 처음에 동일한 계정을 Verifier로 등록한 뒤, 이어서 Compliance로 등록했습니다. 두 번째 등록이 첫 번째 등록을 조용히 덮어썼고(주소당 하나의 매핑 엔트리), 모든 verify_project 배포는 Unauthorized 오류와 함께 되돌아갔습니다(revert). 해결책은 에이전트 유형당 하나의 신원(identity)을 부여하는 것이었으며, 이는 단순한 번거로움을 프로젝트의 핵심 보안 속성으로 탈바꿈시켰습니다.
인덱서(indexer) 없이 Odra 상태 읽기
다음은 대부분의 Casper dApp들이 인덱서(indexer)나 이벤트 스트림(event stream)을 통해 해결하는 부분입니다. 저는 에이전트와 대시보드 모두가 글로벌 상태(global state)로부터 직접, 신뢰가 필요 없는 방식(trustlessly)으로 컨트랙트 상태를 읽기를 원했습니다.
문제점: Odra는 컨트랙트의 필드들을 이름이 지정된 키(named keys)로 노출하지 않습니다. 컨트랙트를 쿼리하면 저장소(storage)와 관련된 이름이 지정된 키를 정확히 하나만 발견하게 됩니다. 바로 state라고 불리는 딕셔너리(dictionary)입니다. 모듈 내의 모든 Var와 Mapping은 파생된 키(derived keys) 아래 이 안에 존재합니다.
odra-core의 contract_env.rs와 odra-macros IR을 파헤쳐 본 결과, 레이아웃은 다음과 같습니다:
- 각 필드는 구조체 선언 순서에 따라 **1부터 시작하는 인덱스(1-based index)**를 가집니다 (
매크로 내의 idx as u8 + 1). - 딕셔너리 키는
hex(blake2b256(index_as_u32_be ++ mapping_key_bytes))입니다.Var의 경우 인덱스 바이트만 사용하며,Mapping의 경우 인덱스 뒤에 bytesrepr로 직렬화된 맵 키(map key)가 붙습니다.
TypeScript로 재구현하면 다음과 같습니다:
import { blake2b } from "@noble/hashes/blake2.js";
function odraKey(fieldIndex: number, mapKeyBytes?: Uint8Array): string {
...
해당 키는 컨트랙트의 state URef에 대해 표준 state_get_dictionary_item RPC 호출을 수행하는 데 사용됩니다. 반환되는 값은 List<U8> CLValue로, 이는 사용자의 Rust 구조체를 Casper의 bytesrepr 방식으로 인코딩한 원시 데이터입니다. 이를 디코딩한다는 것은 casper-types가 직렬화(serialization)한 방식 그대로 바이트를 따라가는 것을 의미합니다:
function decodeProject(raw: Uint8Array) {
let o = 0;
[id, o] = readU32LE(raw, o); // u32: 4 bytes LE
...
그 U256 라인 때문에 오후 시간을 통째로 날렸습니다. Casper의 U256은 고정된 32바이트가 아니라, 길이 접두사(length-prefixed)가 붙은 가변 길이(variable-width) 방식입니다. 이를 잘못 처리하면 그 뒤에 오는 모든 필드가 쓰레기 값으로 디코딩됩니다.
하지만 그만한 가치가 있습니다. 대시보드의 API 라우트는 프로젝트, 리스팅(listings), 그리고 에이전트 평판(reputations)을 공개 RPC 노드에서 직접 디코딩합니다. 즉, 데이터베이스도, 인덱서(indexer)도, 이벤트 파이프라인(event pipeline)도 필요하지 않습니다. agents/src/test-chain-read.ts는 실행될 때마다 라이브 테스트넷(testnet)을 대상으로 전체 유도(derivation) 과정을 검증합니다.
ABI 기반 배포 (ABI-driven deploys)
쓰기 경로(write path)에도 고유한 실수 유발 요소(foot-gun)가 있었습니다. Casper의 엔트리 포인트(entry points)는 정확한 CL 타입(project_id: U32, score: U8, credit_supply: U256)을 선언하며, U32가 예상되는 곳에 U512를 사용하는 배포는 런타임(runtime)에 실패합니다. 타입을 하드코딩하는 대신, 에이전트 라이브러리는 **전역 상태(global state)로부터 컨트랙트의 엔트리 포인트를 조사(introspects)**하여 그에 맞는 인자(arguments)를 생성합니다:
const argTypes = await getEntryPointArgTypes(contractHash, entryPoint); // from query_global_state
switch (argTypes.get(key)) {
case "U8": clArgs[key] = CLValue.newCLUint8(Number(val)); break;
...
잘못된 타입의 인자를 사용하는 것은 구조적으로 불가능해집니다. 체인 자체의 ABI가 곧 스키마(schema)가 되기 때문입니다.
검증 가능한 AI 추론 (Verifiable AI reasoning)
제가 가장 기대하고 있는 패턴입니다. 검증자(verifier)가 GPT-4o를 사용하여 프로젝트에 점수를 매길 때, 단순히 결과에만 기반하여 동작하는 것이 아니라 그 결과를 확정(commit)합니다:
export function storeReasoning(payload: unknown): string {
const json = JSON.stringify(payload); // 정확한 문자열
const hash = createHash("sha256").update(json).digest("hex"); // 확정(commitment)
...
해당 해시(hash)는 verify_project 배포 시 reasoning_hash로 함께 전달됩니다. 그런 다음 대시보드가 사용자의 브라우저 내에서 루프를 완성합니다:
const text = await (await fetch(`/reasoning/${hash}.json`)).text();
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
const computed = [...new Uint8Array(digest)].map(b => b.toString(16).padStart(2, "0")).join("");
...
당신은 AI가 무엇을 말했는지에 대한 제 서버의 주장을 신뢰하지 않습니다. 대신 체인(chain)과 대조하여 바이트 단위로 직접 검증합니다. 컴플라이언스 에이전트(Compliance agent) 역시 프로젝트를 슬래싱(slashing)할 때 사기 증거를 위해 동일한 패턴을 사용합니다.
프로덕션 관점에서의 에이전트들
각 에이전트는 실제 통합(integration)이 적용된 단순한 폴링 루프(poll loop)로 구성되어 있으며, 모의 객체(mock)는 어디에도 사용되지 않았습니다:
- 검증기 (Verifier) (60초마다): 상태 사전(state dictionary)에서 대기 중인 프로젝트를 읽음 → Carbonmark v19에서 프로젝트의 레지스트리(registry) 데이터를 가져옴 → GPT-4o가 방법론(methodology), 추가성(additionality), 영구성(permanence), 누출(leakage)을 점수화함 → 신뢰도가 높고
score ≥ 60인 경우:verify_project→activate_project→ 마켓플레이스에list함. 프로젝트당 세 번의 실제 배포가 이루어지며, 각 배포는testnet.cspr.live링크와 함께 기록됩니다. - 컴플라이언스 (Compliance) (지속적): 검증됨/활성 상태(Verified/Active)인 프로젝트를 대상으로 사기 신호를 재검토함; 신뢰도가 70% 이상이면 증거 해시와 함께
slash_project를 실행함. 이미 테스트넷에서 프로젝트 하나를 자율적으로 슬래싱한 사례가 있으며, 당시 GPT-4o는 REDD+ 베이스라인에서의 과다 크레딧 발행(over-crediting) 패턴을 근거로 제시했습니다. - 마켓 (Market) (60초마다): 실시간 Carbonmark 현물 가격(상장 항목들의 중앙값, $5 하한선)을 가져와, 각 온체인(on-chain) 상장 항목의 스프레드(spread)를 베이시스 포인트(bps)로 계산하고, 시장가와 50 bps 이상 차이 나는 항목은
cancel_listing함.
평판(Reputation)은 자동으로 피드백됩니다: verify_project는 내부적으로 AgentRegistry의 record_verification을 호출하므로, 에이전트의 실적은 업무 자체와 원자적(atomically)으로 결합되어 쌓이게 됩니다.
얻은 교훈
- 프레임워크의 매크로 소스(macro source)를 읽으세요. Odra의 스토리지 레이아웃(storage layout)은 외부 독자를 위해 문서화되어 있지 않습니다. 해답은
odra-macros의 IR (idx as u8 + 1)과contract_env.rs에 있었습니다. - Casper의 bytesrepr은 단순하지만 매우 엄격합니다. 길이 접두사가 붙은
U256, 태그가 붙은Address(1 + 32 바이트), 태그 바이트 + 페이로드(payload) 형태의Option<T>형식을 따릅니다. 디코더(decoder)를 하나 작성하여 실제 상태(live state)와 대조하며 테스트한 뒤, 모든 곳에서 재사용하세요. - 하드코딩하지 말고 내성(introspect)하세요. 엔트리 포인트(Entry-point) 시그니처는 온체인(on-chain)에 존재하므로, 이를 활용하세요.
- 에이전트 유형당 하나의 ID(identity)를 갖는 것은 기능(feature)입니다. 등록 버그로 시작되었던 것이 보안 모델이 되었습니다.
- 공용 RPC 엔드포인트(endpoints)가 중요합니다. 에이전트에게는 속도 제한(rate-limited)이 걸린 게이트웨이로도 충분합니다. 하지만 딕셔너리 읽기(dictionary reads)를 몰아치는 대시보드의 경우, 공유된 상태 루트(state-root) 캐시, 순차적 읽기(sequential reads), 그리고 짧은 서버 측 응답 캐시(8초 설정이 효과적이었습니다)가 필요합니다.
다음 단계
브라우저 내 상쇄(retirement)를 위한 CSPR.click 지갑 통합, 상장된 크레딧이 엔드 투 엔드(end-to-end)로 전송 가능하도록 레지스트리 기반의 CEP-18 민팅(minting), 컴플라이언스(compliance) 에이전트를 위한 x402 미터링 기반의 위성/뉴스 피드, 그리고 스테이크 가중치 점수(stake-weighted scoring)를 적용한 멀티 검증자 합의(multi-verifier consensus)가 예정되어 있습니다.
더 큰 논지는 다음과 같습니다. 실물 자산(RWA)이 온체인으로 이동함에 따라 검증이 병목 현상이 될 것이며, 그 해답은 단 하나의 신뢰할 수 있는 AI가 아니라, _암호화된 ID, 체인에 의해 강제되는 권한, 그리고 감사 가능한 추론 능력을 갖춘, 서로를 신뢰하지 않는 다수의 에이전트_입니다. Casper의 Odra 프레임워크, 크로스 컨트랙트 호출(cross-contract calls), 그리고 저렴한 배포 비용 덕분에 이러한 패턴을 며칠 만에 구축할 수 있었습니다.
스크린샷
Code & more: https://www.dailybuild.xyz/project/186-casper-carbon
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기



