[ERC8444] AI 에이전트의 제공 기능 및 보상 지급 요청을 온체인에서 찾는 메커니즘
요약
ERC8444는 AI 에이전트가 검색한 기능이나 보상 지급 요청에 온체인 근거를 연결하여 신뢰성을 높이는 메커니즘입니다. 이를 통해 단순 설명문만으로는 알 수 없었던 소유권, 결제처, 예치된 보상 등의 정보를 재확인할 수 있습니다. 이 기술은 AI 에이전트의 자원 발견(Resource Discovery) 과정을 온체인으로 강화합니다.
핵심 포인트
- ERC8444는 검색 결과에 온체인 근거를 연결하여 신뢰성을 높입니다.
- AI 에이전트는 기능 및 보상 요청을 온체인 정보로 재확인할 수 있습니다.
- 온체인 기록(record)과 설명문(descriptor)의 해시값 불일치를 감지합니다.
- Adapter는 대상 계약에서 필요한 정보를 읽어오는 연결 부품 역할을 합니다.
안녕하세요.
『DApps 개발 입문』이라는 책이나 여러 글을 쓰고 있는 karude입니다.
LLM을 사용한 사양 주도 개발(Specification-Driven Development)을 다루는 서적 『LLM과 진행하는 사양 주도 개발』을 발매할 예정입니다.
핸즈온 자료와 LLM에 의해 개발이 어떻게 변하는지, 실전 팁들을 정리했습니다.
아래에서도 정보를 전하고 있으니, 흥미로운 글이 있다면 꼭 읽어보세요!
AI 에이전트가 사용할 수 있는 기능이나 보상 지급 요청을 찾을 때, 설명문만으로는 현재 소유자, 결제처, 예치된 보상까지 확인할 수 없습니다.
ERC8444는 평소의 검색 결과에 온체인 근거를 연결하여, 사용하기 전에 재확인할 수 있게 하는 메커니즘입니다.
2026년 10월 17일 기준으로 Draft 상태입니다.
이 글에서는 전제되는 검색 방식, 모든 API, 공식 참고 구현, 테스트, 안전성까지 설명합니다.
공식 사양은 다음과 같습니다.
여행을 계획하는 AI 에이전트가 항공권을 검색할 수 있는 기능과, 여정 작성 요청을 보상 지급으로 의뢰한 상대를 찾는 상황을 가정해 보겠습니다.
검색 결과에 '예약 API', '보상 100 USDC'라고 적혀 있어도, 그 설명을 현재 소유자가 승인했는지, 보상이 실제로 예치되어 있는지 문장만으로는 알 수 없습니다.
오래된 소유자의 결제처가 남아 있다면, 올바른 기능을 찾았더라도 잘못된 상대에게 지불할 가능성이 있습니다.
웹상의 공개 정보를 모아 일반적인 언어로 AI를 위한 기능이나 연결처를 찾을 수 있는 공통 방식을 Agentic Resource Discovery,
줄여서 ARD라고 부릅니다.
ERC8444는 ARD를 대체하지 않습니다.
ARD의 검색 결과와 토큰, 요청에 붙은 보상, AI 에이전트의 신원, 검색 서비스 운영자를 온체인 정보로 연결합니다.
제공자는 검색 결과에 올릴 설명문을 만들고, 그 내용에서 얻은 해시값을 온체인에 기록합니다.
검색 서비스는 설명문을 가져와서, 기록된 해시값, 현재 소유자, 가치의 근거, 공개 출처 도메인과의 관계를 대조합니다.
사용하는 AI 에이전트는 검색의 관련도와 온체인의 근거를 나누어 읽고 후보를 선택합니다.
ARD에서는 사용자 측 AI 에이전트가 'Base에서 USDC 결제 이미지 생성 기능'과 같은, 일반적인 언어로 작성된 검색 조건을 보냅니다.
검색 측은 기능, API, AI 에이전트 등을 나타내는 entry를 반환합니다.
entry는 누가 공개했는지 어떤 연결처인지를 검색 결과로 보여주는 JSON입니다.
ERC8444는 제공하는 기능뿐만 아니라, 작업을 의뢰하는 쪽도 검색 대상으로 합니다.
보상 지급 요청에는 원하는 결과물의 형식, 보상, 기한, 수락 조건을 적을 수 있습니다.
요청을 맡는 AI 에이전트는 자신이 제공할 수 있는 결과물과 조건이 맞는 보상 지급 요청을 찾을 수 있습니다.
검색 결과의 설명과 온체인 정보가 만나는 대상을 anchor라고 부릅니다.
예를 들어, 기능을 나타내는 NFT, 보상을 예치한 요청을 나타내는 NFT, AI 에이전트를 나타내는 address가 anchor가 됩니다.
anchor마다, 설명문의 해시값이나 공개명을 저장하는 온체인 기록이 record입니다.
설명문을 포함하는 ARD entry를 descriptor라고 부릅니다.
현재 소유자는 descriptor 중 제공 조건과 관련된 부분을 해시화하고, 그 값을 record에 등록합니다.
획득처가 descriptor를 수정하더라도, 검색 측은 해시 불일치로 감지할 수 있습니다.
anchor의 종류별로, 기존 contract를 읽는 방식이 다릅니다.
그래서 대상 contract에서 '존재하는지', '현재 누가 조작할 수 있는지', '가치의 근거가 있는지', '설명문과 대조되는 값은 무엇인지'를 같은 형태로 읽어오는 연결 부품을 adapter라고 부릅니다.
adapter는 값을 저장하지 않고, 호출된 시점의 정보를 대상 contract에서 읽습니다.
획득한 문서와 온체인 정보를 대조하여 검색 결과에 값을 추가하는 소프트웨어가 resolver입니다.
resolver가 ARD의 검색 API까지 공개할 경우에는 gateway라고 부릅니다.
검색 시작부터 이용 판단까지는 다음 순서로 진행됩니다.
제공자가 descriptor를 공개하고, 현재 소유자가 그 내용의 해시값을 record에 등록합니다
gateway는 record와 adapter를 읽어, descriptor와 도메인상의 공개 문서를 가져옵니다
resolver는 해시값, 현재 소유자, 공개 도메인과의 관계, 가치 증거를 계산합니다
gateway는 일관된 entry만 일반적인 ARD 검색 결과에 올립니다
이용자는 검색 조건과의 근접성, 상태, 소유자의 승인, 가치 증거를 개별적으로 확인합니다.
검색 순위 점수는 검색 조건에 가까운지를 나타내는 값이며, 신뢰도는 아닙니다.
또한, 소유자가 바뀐 후 새로운 소유자가 descriptor를 승인하지 않은 record나 폐지된 record는 검색 결과에서 제외됩니다.
일시 정지 중인 record를 검색에 올릴지, 어느 레벨로 표시할지는 현행 사양만으로는 유일하게 확정할 수 없습니다.
참고 gateway는 일시 정지를 숫자상의 level 0으로 취급하지만, 이를 규격 요건이라고 단정할 수는 없습니다.
이용자는 status와 소유자의 승인 상태를 개별적으로 확인하고, 일시 정지 중인 offer를 유효한 후보로 취급하지 않는 구현을 할 것입니다.
ARD는 어떤 공개원이 어떤 기능을 안내하는지를 검색하는 메커니즘입니다.
한편, 가격, 지불, 보상 예치, 신용 정보까지는 규정하지 않습니다.
ERC8444는 기존 ARD entry를 그대로 이용하면서, 가치와 관련된 사실을 기존 contract에서 읽을 수 있도록 합니다.
'이미지를 생성할 수 있다'라는 설명만으로는 호출 1회당 가격이나 지불처가 확정되지 않습니다.
'보상 100 USDC의 이미지 제작 요청'이라는 설명만으로도, 100 USDC가 예치된 상태인지, 요청이 취소되지 않았는지는 알 수 없습니다.
가치에 관한 정보를 검색용 registry에 복제하면, 원본 contract의 잔액이나 상태와 어긋날 가능성이 있습니다.
ERC8444는 record에 설명문의 commitment를 두고, 잔액이나 소유자는 adapter를 통해 원본 contract에서 그때그때 읽습니다.
검색용 사본을 두 번째 정본으로 삼지 않는 설계입니다.
Web의 domain을 관리하는 주체와, 자금을 받는 address를 관리하는 주체는 같지 않을 수 있습니다.
두 주체를 동등한 권한자로 설정하면, domain 측이 address 소유자가 승인하지 않은 가격을 공개하거나, address 측이 도메인 이름을 멋대로 사칭할 수 있습니다.
ERC8444에서는 자금이나 anchor를 조작하는 address를 가치 정보의 기점으로 삼습니다.
domain은 해당 address를 자신의 공개 문서로 인정함으로써 신뢰 관계를 추가합니다.
비밀키를 가지지 않은 domain 공개자도 기존 ARD entry를 공개할 수 있으며, 이 경우 온체인 근거가 없는 단계로 검색될 수 있습니다.
기존의 ARD 검색은 이용자가 사용하고 싶은 기능을 찾는 방향이 중심입니다.
ERC8444의 task는 성과물을 의뢰하고, 인수 조건을 충족한 상대에게 지급할 보상을 나타내는 공개 정보를 entry로 표현합니다.
요청하는 측은 수령 가능한 성과물의 형식, 보상액, 기한, 심사 방식, 응모 조건을 공개합니다.
제공측의 AI 에이전트는 '자신의 기능을 누가 필요로 하는지'를 검색할 수 있습니다.
다만, 성과물의 제출, 심사, 보상 해방 그 자체는 ERC8444의 범위를 벗어납니다.
기능 NFT를 판매하더라도, record에 저장된 descriptor의 해시값은 자동으로 변하지 않습니다.
그대로 배포하면, 구매자가 구 소유자의 endpoint, 이용 조건, 지불처를 지지하는 것처럼 보일 수 있습니다.
수령을 원하지 않는 address로 NFT를 보내는 경우도 마찬가지입니다.
ERC8444는 descriptor를 마지막으로 등록한 address와 현재 소유자를 비교합니다.
일치하지 않을 때는 검색 결과에서 제외하고, 새로운 소유자가 같은 해시값 또는 새로운 해시값을 등록한 후에 재게재합니다.
검색 대상이 거래의 어느 쪽에 위치하는지를 나타내는 구분을 kind라고 부릅니다.
데이터 형식을 나타내는 ARD의 type과는 별개의 값입니다.
| kind | 역할 | anchor | 현재 조작 가능한 주체 | 보증 조건 |
|---|---|---|---|---|
skill | 기능 제공 | 실행 가능한 기능을 나타내는 token | token owner | package hash가 존재함 |
task | 보상 지급 요청 | 보상이 걸린 요청을 나타내는 token | token owner | 취소되지 않았고, 보상이 있으며, 기한 내임 |
agent | 거래 주체 | address | 해당 address | identity registry에서 active임 |
registry | 검색처 | 운영 address와 식별값 | 해당 address | 항상 보증 없음 |
skill, task, agent, registry의 4가지 종류는 모든 registry instance가 대응합니다.
새로운 JSON 형식이 추가되어도 kind는 늘리지 않습니다.
assertion, policy, market은 향후 사용을 위해 예약되었습니다.
kind ID는 다음 수식으로 만듭니다.
bytes32 kindId = keccak256("ard-onchain.kind.<name>");
anchor는 (chainId, target, id)의 조합입니다.
token을 anchor로 사용할 때는 chain, token contract, token ID를 나타내는 CAIP-19 형식으로, address를 anchor로 사용할 때는 chain과 address를 나타내는 CAIP-10 형식으로 표시합니다.
CAIP 형식을 통해 이용 측은 어느 chain의 어떤 contract 또는 address를 확인해야 할지 특정할 수 있습니다.
record ID는 anchor에서 다음 수식으로 만듭니다.
recordId = keccak256(abi.encode(block.chainid, target, id));
같은 anchor에는 하나의 record만 등록할 수 있습니다.
다른 kind를 지정해도 두 번째는 등록할 수 없습니다.
다른 chain 상의 같은 token address와 ID는 별도의 anchor입니다.
ARD의 공개명은 urn:air:<publisher>:<namespace>:<name> 형식이었습니다.
이 URN은 별칭이며, record의 동일성은 anchor로 비교합니다.
현재 소유자와 publisher domain 간의 관계가 확인되고, domain의 manifest에도 같은 URN이 올라 있는 동안만 canonical name으로 사용할 수 있습니다.
gateway는 자신의 domain에서 도달 가능한 alias를 만들 수 있습니다.
여러 gateway가 다른 alias를 반환하더라도, 이용 측은 value:anchor를 비교하여 동일한 record인지 판단합니다.
descriptor는 표시명, 연결처, 설명, 제공 조건 등을 담은 ARD entry입니다.
현재 소유자는 제공 내용과 관련된 부분만 고정 순서의 JSON으로 변환하여 SHA-256을 계산하고 record에 저장합니다.
descriptor의 member는 3가지 종류로 나뉩니다.
| 구분 | 주요 값 | 해시 대상 | 이유 |
|---|---|---|---|
| 공개 측 정보 | identifier, trustManifest, @context, @id | 제외 | gateway마다 별명이나 공개 정보를 가질 수 있도록 함 |
| 내용의 정보 | displayName, type, url 또는 data, 설명, 제공 조건 | 대상 | 소유자가 무엇을 제공하거나 요청했는지 고정함 |
| resolver의 계산 결과 | value:anchor, binding, level, status, adopted 등 | 제외 | 읽기 시점의 chain과 domain에서 재계산함 |
내용의 정보는 RFC 8785의 JSON Canonicalization Scheme으로 정규화합니다.
이는 같은 JSON 내용이라도 공백이나 key 순서에 좌우되지 않는 동일한 byte열로 변환하는 방식입니다.
그 UTF-8 byte열에 SHA-256을 적용한 값이 descriptorHash입니다.
descriptorURI는 획득처의 힌트일 뿐, 신뢰의 근거가 아닙니다.
획득할 수 없거나 hash가 일치하지 않는 경우, resolver는 일관된 record로서 제공할 수 없습니다.
소유자가 작성하는 추가 정보에는 다음이 있습니다.
| member | 독자가 확인하는 내용 |
|---|---|
value:kind | entry가 기능 제공, 보상 지급 요청, 주체, 검색 대상 중 어느 것인지 |
value:controller | 공개자가 주장하는 controller. 권한 판정에는 사용하지 않음 |
value:terms | 기능의 단위, 가격, 통화, 공급량 |
value:settlement | 온체인(onchain), x402, 선언만으로의 지급 경로와 지급처 |
value:provenance | package와 공개 card의 digest |
value:credit | 외부 신용 정보를 확인하는 registry와 level |
value:dispute | 분쟁 판단을 확인하는 policy와 registry |
value:eligibility | task에 응모할 수 있는 주체의 조건 |
value:registration | agent의 registration file |
value:indexes | registry가 인덱싱하는 registry instance |
task
의 payload는 application/ai-task+json을 사용합니다.
요구되는 결과물 형태를 나타내는 wants와, 보상액・통화를 나타내는 bounty는 필수입니다. 기한, 건수, 심사 방식, 응모 조건은 임의로 추가할 수 있습니다.
온체인 정보가 없어도 유효한 ARD entry가 되므로, domain만으로 하는 보상 지급 요청도 공개할 수 있습니다.
domain과 address가 서로를 인정했는지를 나타내는 관계가 binding입니다.
address 측의 온체인 선언과, domain 측의 웹상의 승인을 resolver가 대조합니다. address는 대상 domain을 자신과 연결하고 싶다는 선언을 registry에 저장합니다.
본인이 transaction을 보내는 방법과, 서명을 제3자에게 받도록 하는 방법이 있습니다.
domain은 소문자 ASCII, 숫자, -, .의 1~253 bytes로 정규화합니다. 맨 앞・맨 뒤의 .와 빈 label은 사용할 수 없습니다.
국제화 domain은 punycode로 변환한 후 전달합니다.
같은 domain을 여러 address가 claim할 수 있습니다. registry는 진정한 소유자를 결정하지 않고, 각 address의 선언만 저장합니다.
domain 측은 /.well-known/ard.json에 address를 기재하여 승인합니다.
manifest 상단의 value:controllers에 포함하거나, entry의 value:controller에 작성합니다.
chain은 HTTPS상의 manifest를 읽을 수 없습니다. 따라서, registry contract가 verified 판정을 저장하는 것이 아니라, resolver가 onchain claim과 manifest를 합쳐서 계산합니다.
binding은, domain과 address 사이에 어떤 선언이 갖춰졌는지를 나타냅니다. 이용 측은 이 상태를 읽어 domain 이름을 신뢰 표시로 사용할 수 있을지 판단합니다.
| 상태 | 저장・공개된 조건 | 이용 측의 판단 |
|---|---|
unbound | address의 claim이 없음 | 관계없다고 취급 |
claimed | 1 address만 claim하고, domain은 미승인 | address 측만의 주장으로 취급 |
verified | address가 claim하고, domain도 승인 | domain과 address의 상호 확인 완료로 취급 |
contested | 여러 address가 claim하고, 대상 address는 미승인 | 경쟁 중이라고 취급 |
record에 URN이 없는 경우는 none, 현재 controller가 URN의 publisher domain을 claim하지 않은 경우는 third-party로 표시합니다.
승인이 사라지면 canonical URN은 stale(오래됨)해집니다. 나중에 승인이 돌아와도, 승인이 없었던 기간에 배포된 내용까지 소급하여 정당화하지 않습니다.
level은, 검색 대상에 어떤 온체인 근거가 있는지를 나타냅니다. domain과의 binding이나 신용 정보와는 독립적입니다.
| 레벨 | 조건 | 이용 측이 확인할 수 있는 것 |
|---|---|---|
L0 | record가 없음 | domain 공개자의 선언만임 |
L1 | status가 Active인 record가 있음 | anchor와 현재 controller를 chain에서 읽을 수 있음 |
L2 | L1에 더해 adapter가 backed를 반환하고, 필요한 provenance도 일치함 | 보상(reward), package, identity 등 kind 고유의 뒷받침이 있음 |
L2는 가격의 타당성이나 제공 품질까지 보장하지 않습니다.
이용 측은 (level, binding, credit)를 별도로 읽습니다.
record에는 이용 중, 일시 정지, 폐지됨 중 하나가 저장됩니다.
일시 정지는 현재 controller가 해제할 수 있지만, 폐지는 이후의 소유자도 해제할 수 없습니다.
| 보존 상태 | 다음에 허용되는 변경 | 검색 시 처리 |
|---|---|
Active | Suspended 또는 Retired | L1 또는 L2 후보가 됨 |
Suspended | Active 또는 Retired | 사양상의 검색 게재와 level에 모호함이 남아있기 때문에 status를 명시 확인해야 함 |
Retired | 변경 불가 | 검색 결과에 올리지 않음 |
구매 전에는 status를 확인합니다.
일시적으로 멈추게 할 뿐이라면 폐지를 선택하지 않습니다.
descriptor를 마지막으로 commit한 address를 attestor라고 부릅니다.
attestor와 현재 controller가 일치하는 상태가 adopted이고, 일치하지 않는 상태가 unadopted입니다.
unadopted여도 record, level, status는 남아있습니다. 다만 resolver는 검색 결과와 목록에서 제외합니다.
새로운 controller가 updateDescriptor 함수로 같은 descriptorHash를 재등록하면, 그 address가 attestor가 되어 재게재할 수 있습니다.
IARDOnchainRegistry는 anchor별 commitment, domain claim, kind와 adapter의 대응을 다루는 interface입니다.
enum Status { None, Active, Suspended, Retired }
Status는 record가 존재하는지, 현재 이용 중인지, 일시 정지 상태인지, 폐지되었는지를 나타내는 Enum 타입입니다.
| 값 | 보존 상태 | 판정 시 처리 |
|---|---|
None | record가 미등록 | 업데이트나 상태 변경을 거부함 |
Active | 이용 중 | L1 또는 L2 판정으로 진행 |
Suspended | controller가 일시 정지 | 이용 후보에서 제외하는 판단에 사용 |
Retired | 영구적으로 폐지 | 업데이트를 거부하고 검색에 올리지 않음 |
struct Record { bytes32 kind; address target; ... }
Record는 controller가 등록한 descriptor의 commitment와 anchor를 모아서 저장하는 구조체입니다.
필드
| field | 저장하는 값 |
|---|---|
kind | 4가지 종류 중 어떤 것으로 읽을지 |
target | token contract 또는 주체 address |
id | token ID, agent의 0, registry의 비(非)0 식별값 |
descriptorHash | 내용 member의 SHA-256 |
descriptorURI | descriptor 획득처의 힌트 |
urn | controller가 제시한 canonical 후보 |
status | 이용 중, 일시 정지, 폐지의 상태 |
attestor | descriptor를 마지막으로 commit한 controller |
updatedAt | 최종 업데이트 시각 |
controller, backing, anchor hint는 저장하지 않고 adapter에서 매번 읽습니다.
이벤트 Registered(bytes32 indexed recordId, bytes32 indexed kind, address indexed target, uint256 id, address attestor, bytes32 descriptorHash, string descriptorURI, string urn);
anchor에서 첫 번째 record를 등록할 때 발행되는 이벤트입니다.
indexer는 이 로그로부터 후보들을 나열할 수 있습니다.
필드
| field | 상세 |
|---|---|
recordId | anchor가 만든 record 식별자 |
kind | adapter를 선택하는 kind ID |
target | anchor의 contract 또는 address |
id | token ID 또는 address anchor의 식별값 |
attestor | 등록 시의 controller |
descriptorHash | commit한 내용 hash |
descriptorURI | descriptor 획득처 |
urn | canonical 후보 |
event DescriptorUpdated(bytes32 indexed recordId, bytes32 descriptorHash, string descriptorURI, string urn);
현재 controller가 descriptor commitment를 업데이트할 때 발행되는 이벤트입니다.
같은 hash의 재commit도 기록되지만, attestor의 새로운 값은 record를 다시 읽어서 확인합니다.
필드
| field | 상세 |
|---|---|
recordId | 업데이트한 record |
descriptorHash | 업데이트 후 내용 hash |
descriptorURI | 업데이트 후 획득처 |
urn | 업데이트 후 canonical 후보 |
event StatusChanged(bytes32 indexed recordId, Status status);
record의 이용 상태를 변경할 때 발행되는 이벤트입니다.
필드
| field | 상세 |
|---|---|
recordId | 상태를 바꾼 record |
status | 변경 후 상태 |
event DomainClaimed(bytes32 indexed domainHash, address indexed controller, string domain);
address의 domain claim을 저장할 때 발행되는 이벤트입니다.
필드
| field | 상세 |
|---|---|
domainHash | 정규화된 domain의 keccak256 |
controller | claim한 address |
domain | 정규화된 domain 문자열 |
event DomainReleased(bytes32 indexed domainHash, address indexed controller);
address가 자신의 domain claim을 철회할 때 발행되는 이벤트입니다.
필드
| field | 상세 |
|---|---|
domainHash | 대상 domain의 hash |
controller | claim을 철회한 address |
event KindRegistered(bytes32 indexed kind, address adapter, string uri);
instance owner가 kind와 adapter의 대응을 처음 등록할 때 발행되는 이벤트입니다.
필드
| field | 상세 |
|---|---|
kind | 등록한 kind ID |
adapter | live 정보를 읽는 adapter address |
uri | adapter 정보의 참조처 |
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
registry instance의 관리자를 변경할 때 발행되는 이벤트입니다.
필드
| field | 상세 |
|---|---|
previousOwner | 변경 전 instance owner |
newOwner | 변경 후 instance owner |
function register(bytes32 kind, address target, uint256 id, bytes32 descriptorHash, string calldata descriptorURI, string calldata urn) external returns (bytes32 recordId);
anchor에 첫 번째 레코드를 등록하는 함수입니다.
kind는 adapter여야 하고, anchor가 존재하며, 호출자가 현재 controller임을 확인합니다anchor로부터 record ID를 생성하고, 중복 및 빈 descriptor hash를 거부합니다**Active인 레코드를 저장하고, 호출자를 attestor로* Registered 이벤트를 발행합니다
인수
| 인수 | 상세 |
|---|---|
kind | adapter를 선택하는 kind ID |
target | token contract 또는 주체 address |
id | token ID 또는 address anchor의 식별 값 |
descriptorHash | 내용 member의 SHA-256 |
descriptorURI | descriptor 획득처 |
urn | canonical 후보. 빈 문자열도 가능 |
반환값
| 반환값 | 상세 |
|---|---|
recordId | chain ID, target, id로부터 만든 식별자 |
실행 조건 -
kind가 등록되어 있어야 합니다.
anchor가 존재하며, 호출자가 현재 controller여야 합니다.
- descriptorHash가 0이 아니며, anchor에 레코드가 미등록이어야 합니다.
function updateDescriptor(bytes32 recordId, bytes32 descriptorHash, string calldata descriptorURI, string calldata urn) external;
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기