maroo-viem-poc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ # maroo-viem-poc
2
+
3
+ Maroo 체인용 viem 확장 — **PoC**입니다. Maroo의 precompile 4종(pcl / okrw / eas / agent)을
4
+ viem 클라이언트 데코레이터로 노출하고, ABI만으로는 알 수 없는 것들(PCL 정책 인코딩,
5
+ `runOnPcl` 경유 규칙, 40종 정책 위반 에러의 의미)을 SDK 계층에서 채웁니다.
6
+
7
+ 모든 동작은 **살아있는 marooTestnet(chainId 450815)에 대해 검증**되었습니다 —
8
+ 읽기·revert 디코딩·서명 트랜잭션 전부요. 유닛 41개 + 라이브 22개 테스트가 근거입니다.
9
+
10
+ ```bash
11
+ npm i maroo-viem-poc viem
12
+ ```
13
+
14
+ ## Quickstart
15
+
16
+ ```ts
17
+ import { createPublicClient, createWalletClient, http } from 'viem'
18
+ import { marooTestnet } from 'viem/chains' // viem 2.55+ 에 포함
19
+ import { marooPublicActions, marooWalletActions } from 'maroo-viem-poc'
20
+
21
+ const client = createPublicClient({ chain: marooTestnet, transport: http() })
22
+ .extend(marooPublicActions())
23
+
24
+ // 네 precompile 모두 getParams를 노출하므로 네임스페이스가 필수입니다.
25
+ const { policyAdmin, entrypoints } = await client.pcl.getParams()
26
+ const { minter, mintDenom } = await client.okrw.getParams() // mintDenom: 'atokrw'
27
+ const { schemaRegistry, eas, indexer } = await client.eas.getParams()
28
+ const [agentIds] = await client.agent.getAgentIds({
29
+ args: [wallet, { key: '0x', offset: 0n, limit: 10n, countTotal: false, reverse: false }],
30
+ })
31
+ ```
32
+
33
+ ## 왜 ABI만으로 부족한가
34
+
35
+ 이 패키지가 존재하는 이유이자, dApp을 만들 때 반드시 알아야 하는 체인 특성입니다.
36
+
37
+ **`runOnPcl`을 경유해야 컨트랙트 정책이 적용됩니다.** 타깃 컨트랙트를 직접 호출하면
38
+ 글로벌 정책만 검사되고, 컨트랙트 스코프 정책은 **조용히 건너뜁니다.** 에러도 없습니다.
39
+
40
+ ```ts
41
+ const wallet = createWalletClient({ account, chain: marooTestnet, transport: http() })
42
+ .extend(marooWalletActions())
43
+
44
+ // writeContract와 같은 모양 — 한 단어만 바꾸면 정책이 적용됩니다.
45
+ const hash = await wallet.pcl.runOnPcl({
46
+ address: token,
47
+ abi: erc20Abi,
48
+ functionName: 'transfer',
49
+ args: [recipient, amount],
50
+ })
51
+
52
+ // 네이티브 전송도 정책 검사를 태울 수 있습니다.
53
+ await wallet.pcl.runOnPcl({ address: recipient, value: parseEther('1') })
54
+ ```
55
+
56
+ **`runOnPcl`에 `msg.value`를 붙이면 돈이 사라집니다.** 전달할 값은 *인자*로 갑니다 —
57
+ 체인이 `evm.Call(caller, target, data, gas, value)`로 호출자에게서 직접 차감하며,
58
+ 바깥 트랜잭션의 `msg.value`는 포워딩도 환불도 되지 않고 precompile 주소에 영구히
59
+ 남습니다(testnet의 `0x…0005`에 이미 12만+ tOKRW가 이렇게 잠겨 있습니다).
60
+ 이 SDK는 **타입과 런타임 양쪽에서 이 경로를 차단**합니다.
61
+
62
+ **`PolicySet.policy`는 self-describing이 아닙니다.** `templateId` 문자열이 opaque
63
+ `bytes`의 레이아웃을 결정하는데, 그 매핑은 ABI 어디에도 없습니다. 이 SDK의
64
+ policy 코덱이 그 매핑입니다(중첩 포함 9종 전부, 라이브 체인 바이트로 검증).
65
+
66
+ ```ts
67
+ import { decodePolicySet } from 'maroo-viem-poc'
68
+
69
+ const { policies } = await client.pcl.globalPolicies()
70
+ const tree = policies.map((p) => decodePolicySet(p))
71
+ // FOR_EACH(Every of AgentOwners) → LOGICAL(Or) → [PERIODIC_VOLUME 10M/24h, EAS …]
72
+ // 모르는 templateId는 throw 대신 { unknown: true, raw } 로 강등됩니다.
73
+ ```
74
+
75
+ **정책 위반 revert는 사람이 읽을 수 있어야 합니다.** viem이 에러 이름·인자까지는
76
+ 디코딩해 주지만, 40종 중 어떤 것이 "정책이 사용자를 거부했다"는 뜻인지,
77
+ `resetAt`이 언제인지는 알려주지 않습니다.
78
+
79
+ ```ts
80
+ import { decodePclError } from 'maroo-viem-poc'
81
+
82
+ try {
83
+ await wallet.pcl.runOnPcl({ ... })
84
+ } catch (err) {
85
+ const v = decodePclError(err) // PCL 소유가 아닌 에러면 null (셀렉터 기준 판정)
86
+ if (v?.kind === 'periodic-volume')
87
+ console.log(`24h 한도 초과 — ${v.resetAt.toLocaleString()}에 리셋`)
88
+ if (v?.kind === 'any-of-rejected')
89
+ console.log('OR 정책의 모든 분기가 거부:', v.children) // 재귀 디코딩, 깊이 상한 8
90
+ }
91
+ ```
92
+
93
+ **배치 조회는 `.call()`로.** 모든 read 액션은 실행하지 않고 multicall/simulate용
94
+ 디스크립터만 만드는 `.call()`을 함께 노출합니다.
95
+
96
+ ```ts
97
+ const volumes = await client.multicall({
98
+ contracts: users.map((u) =>
99
+ client.pcl.globalOkrwEasPeriodicVolume.call({ args: [u] })),
100
+ allowFailure: false,
101
+ })
102
+ // PeriodicVolume은 4필드입니다: amount · maxAmount · resetPeriodSeconds · resetAt
103
+ // (앞 2필드만 디코딩해도 우연히 맞는 값이 나오므로 주의 — 리셋 시각을 잃습니다)
104
+ ```
105
+
106
+ ## 알아두면 좋은 체인 특성
107
+
108
+ - precompile은 `eth_getCode`가 `0x`를 반환합니다. "컨트랙트 배포됐나" 식 프리플라이트로 걸러내면 안 됩니다.
109
+ - testnet 네이티브 denom은 `atokrw`, mainnet은 `aokrw`. `VOLUME_POLICY.tokens[]`가 denom 문자열 키라 하드코딩하면 한쪽에서 깨집니다.
110
+ - 정책 템플릿 id는 `FOR_EACH_POLICY`입니다 — `FOREACH_POLICY`가 아닙니다.
111
+ - 일부 PCL 에러 인자는 주소를 bech32(`maroo1…`)로 반환합니다. hex와 직접 비교할 수 없습니다.
112
+ - 쓰기 표면의 다수는 권한 계층이 있습니다: `okrw.mint`는 체인 파라미터의 minter만, `pcl.setGlobalPolicies`/`registerPolicyTemplate`은 `policyAdmin`만, `changeContractPolicies`는 등록 시 지정한 admin만 호출할 수 있습니다.
113
+
114
+ ## API
115
+
116
+ | Export | 용도 |
117
+ |---|---|
118
+ | `marooPublicActions()` | `client.{pcl,okrw,eas,agent}.*` 읽기 + `.call()` 디스크립터 |
119
+ | `marooWalletActions()` | `client.pcl.runOnPcl` 래퍼 + 쓰기 액션(권한 계층 JSDoc 참고) |
120
+ | `decodePolicySet` / `encodePolicy` | PCL 정책 코덱 (9종, 재귀, 라이브 검증) |
121
+ | `decodePclError` | revert → `PclViolation` 유니언. 남의 에러는 `null` |
122
+ | `precompileAddresses` / `easContracts` | 주소 상수 (드리프트 가드 테스트로 고정) |
123
+ | `precompileActions(abi, addr)` | 임의 `as const` ABI → 타입드 액션 팩토리 |
124
+ | `normalizeSelector` / `SELECTOR_ALL` / `NO_MAX_LIMIT` | 정책 셀렉터·센티널 유틸 |
125
+
126
+ ## 테스트
127
+
128
+ ```bash
129
+ pnpm test # 유닛 — 네트워크 불필요
130
+ pnpm test:live # 라이브 marooTestnet 읽기/revert 검증 (가스 불필요)
131
+ MAROO_TEST_PRIVATE_KEY=0x… pnpm test:live # 서명 쓰기 경로까지 (tOKRW 필요)
132
+ ```
133
+
134
+ 주소·ABI 드리프트 가드가 포함되어 있습니다: `@maroo-chain/contracts`를 올렸을 때
135
+ precompile 주소가 이동하거나 함수 시그니처가 바뀌면 유닛 테스트가 시끄럽게 실패합니다.
136
+
137
+ ## 범위 밖 (의도적)
138
+
139
+ - **privacy precompile (`0x…000b`)** — clairveil 쪽 SDK의 책임입니다. 주소 상수만 노출합니다. (testnet 미배포)
140
+ - **커스텀 tx 타입 / formatters / serializers** — Maroo는 표준 EVM 트랜잭션만 사용하므로 필요 없습니다.
141
+ - **wagmi 훅 계층** — viem 계층이 안정된 뒤 별도 패키지로.
142
+ - 이것은 **PoC입니다.** 감사받지 않았고, API는 예고 없이 바뀝니다.
143
+
144
+ ## License
145
+
146
+ MIT