@arkiv-network/sdk 0.7.0-dev.1 → 0.8.0-dev.1

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.
Files changed (210) hide show
  1. package/README.md +29 -16
  2. package/dist/attr/index.cjs +34 -0
  3. package/dist/attr/index.d.cts +2 -0
  4. package/dist/attr/index.d.ts +2 -0
  5. package/dist/attr/index.js +3 -0
  6. package/dist/attr-BDZ-APg5.js +809 -0
  7. package/dist/attr-BDZ-APg5.js.map +1 -0
  8. package/dist/attr-BvxHN99W.cjs +1029 -0
  9. package/dist/chains/index.cjs +2 -3
  10. package/dist/chains/index.d.cts +22 -60
  11. package/dist/chains/index.d.cts.map +1 -1
  12. package/dist/chains/index.d.ts +22 -60
  13. package/dist/chains/index.d.ts.map +1 -1
  14. package/dist/chains/index.js +2 -2
  15. package/dist/chains-BJrZs6sG.js +57 -0
  16. package/dist/chains-BJrZs6sG.js.map +1 -0
  17. package/dist/chains-BYRTTipa.cjs +73 -0
  18. package/dist/changeOwnership-B7bEgla4.d.cts +509 -0
  19. package/dist/changeOwnership-B7bEgla4.d.cts.map +1 -0
  20. package/dist/changeOwnership-DwgtdSFc.d.ts +509 -0
  21. package/dist/changeOwnership-DwgtdSFc.d.ts.map +1 -0
  22. package/dist/entity/index.cjs +19 -0
  23. package/dist/entity/index.d.cts +4 -0
  24. package/dist/entity/index.d.ts +4 -0
  25. package/dist/entity/index.js +6 -0
  26. package/dist/entity-B0t6sN7F.cjs +71 -0
  27. package/dist/entity-BIp_df9u.js +67 -0
  28. package/dist/entity-BIp_df9u.js.map +1 -0
  29. package/dist/entity-CFhbAv0R.js +70 -0
  30. package/dist/entity-CFhbAv0R.js.map +1 -0
  31. package/dist/entity-wfBnZd91.cjs +98 -0
  32. package/dist/errors-D-eJkGVc.cjs +56 -0
  33. package/dist/errors-DGB8AgT2.js +33 -0
  34. package/dist/errors-DGB8AgT2.js.map +1 -0
  35. package/dist/expiry-BUYIXTGb.cjs +135 -0
  36. package/dist/expiry-BxOlvVWC.d.ts +79 -0
  37. package/dist/expiry-BxOlvVWC.d.ts.map +1 -0
  38. package/dist/expiry-DGRzrSan.js +95 -0
  39. package/dist/expiry-DGRzrSan.js.map +1 -0
  40. package/dist/expiry-DRV29F1r.d.cts +79 -0
  41. package/dist/expiry-DRV29F1r.d.cts.map +1 -0
  42. package/dist/flags-0Vl5rrHq.d.cts +41 -0
  43. package/dist/flags-0Vl5rrHq.d.cts.map +1 -0
  44. package/dist/flags-Ct8k0gpB.cjs +43 -0
  45. package/dist/flags-ZtlvOIIk.js +33 -0
  46. package/dist/flags-ZtlvOIIk.js.map +1 -0
  47. package/dist/flags-wmcMoAnN.d.ts +41 -0
  48. package/dist/flags-wmcMoAnN.d.ts.map +1 -0
  49. package/dist/index-Cgw1u5aY.d.cts +278 -0
  50. package/dist/index-Cgw1u5aY.d.cts.map +1 -0
  51. package/dist/index-ClgIuxJ8.d.ts +159 -0
  52. package/dist/index-ClgIuxJ8.d.ts.map +1 -0
  53. package/dist/index-D0CmtiWT.d.cts +159 -0
  54. package/dist/index-D0CmtiWT.d.cts.map +1 -0
  55. package/dist/index-D8LZt-3J.d.cts +417 -0
  56. package/dist/index-D8LZt-3J.d.cts.map +1 -0
  57. package/dist/index-DVYFaXNV.d.ts +417 -0
  58. package/dist/index-DVYFaXNV.d.ts.map +1 -0
  59. package/dist/index-VCqgn29D.d.ts +278 -0
  60. package/dist/index-VCqgn29D.d.ts.map +1 -0
  61. package/dist/index.cjs +676 -375
  62. package/dist/index.d.cts +247 -208
  63. package/dist/index.d.cts.map +1 -1
  64. package/dist/index.d.ts +247 -208
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +626 -368
  67. package/dist/index.js.map +1 -1
  68. package/dist/payload-Bnw1hrvb.cjs +83 -0
  69. package/dist/payload-CIxSn_DW.js +67 -0
  70. package/dist/payload-CIxSn_DW.js.map +1 -0
  71. package/dist/payload-Dgx3PQeK.d.ts +94 -0
  72. package/dist/payload-Dgx3PQeK.d.ts.map +1 -0
  73. package/dist/payload-P0nWyjLg.d.cts +94 -0
  74. package/dist/payload-P0nWyjLg.d.cts.map +1 -0
  75. package/dist/query/index.cjs +24 -150
  76. package/dist/query/index.d.cts +5 -3
  77. package/dist/query/index.d.ts +5 -3
  78. package/dist/query/index.js +6 -138
  79. package/dist/queryBuilder-BXDvX1K_.cjs +1123 -0
  80. package/dist/queryBuilder-CbLL4pG5.js +963 -0
  81. package/dist/queryBuilder-CbLL4pG5.js.map +1 -0
  82. package/dist/selection-BvK6yX0g.d.cts +433 -0
  83. package/dist/selection-BvK6yX0g.d.cts.map +1 -0
  84. package/dist/selection-fpmI3ADQ.d.ts +433 -0
  85. package/dist/selection-fpmI3ADQ.d.ts.map +1 -0
  86. package/dist/types/index.cjs +3 -3
  87. package/dist/types/index.d.cts +7 -2
  88. package/dist/types/index.d.ts +7 -2
  89. package/dist/types/index.js +3 -3
  90. package/dist/utils/index.cjs +4 -17
  91. package/dist/utils/index.d.cts +3 -17
  92. package/dist/utils/index.d.ts +3 -17
  93. package/dist/utils/index.js +4 -18
  94. package/package.json +26 -5
  95. package/src/actions/public/getEntity.ts +27 -43
  96. package/src/actions/public/getEntityNonce.ts +30 -0
  97. package/src/actions/public/predictEntityKeys.test.ts +143 -0
  98. package/src/actions/public/predictEntityKeys.ts +138 -0
  99. package/src/actions/public/query.ts +33 -69
  100. package/src/actions/public/watchEntityEvents.test.ts +414 -0
  101. package/src/actions/public/watchEntityEvents.ts +225 -0
  102. package/src/actions/wallet/changeOwnership.ts +2 -2
  103. package/src/actions/wallet/createEntity.ts +99 -24
  104. package/src/actions/wallet/extendEntity.ts +37 -13
  105. package/src/actions/wallet/mutateEntities.test.ts +76 -0
  106. package/src/actions/wallet/mutateEntities.ts +19 -21
  107. package/src/actions/wallet/patchEntity.ts +90 -0
  108. package/src/attr/attributes.test.ts +120 -0
  109. package/src/attr/attributes.ts +190 -0
  110. package/src/attr/codec.test.ts +162 -0
  111. package/src/attr/codec.ts +220 -0
  112. package/src/attr/decimal.ts +86 -0
  113. package/src/attr/errors.ts +145 -0
  114. package/src/attr/index.ts +50 -0
  115. package/src/attr/names.ts +106 -0
  116. package/src/attr/types.ts +150 -0
  117. package/src/attr/values.test.ts +271 -0
  118. package/src/attr/values.ts +502 -0
  119. package/src/chains/cheesecake.ts +22 -0
  120. package/src/chains/index.ts +1 -2
  121. package/src/clients/createPublicClient.test.ts +3 -5
  122. package/src/clients/createPublicClient.ts +5 -4
  123. package/src/clients/createWalletClient.test.ts +2 -2
  124. package/src/clients/createWalletClient.ts +13 -7
  125. package/src/clients/decorators/arkivPublic.ts +160 -139
  126. package/src/clients/decorators/arkivWallet.ts +86 -94
  127. package/src/entity/create.test.ts +184 -0
  128. package/src/entity/errors.ts +38 -0
  129. package/src/entity/events.ts +16 -0
  130. package/src/entity/expiry.test.ts +195 -0
  131. package/src/entity/expiry.ts +181 -0
  132. package/src/entity/flags.ts +65 -0
  133. package/src/entity/index.ts +14 -0
  134. package/src/entity/key.ts +68 -0
  135. package/src/entity/operations.ts +134 -0
  136. package/src/entity/params.ts +15 -0
  137. package/src/errors.ts +26 -49
  138. package/src/index.ts +6 -0
  139. package/src/query/engine.ts +59 -139
  140. package/src/query/errors.ts +151 -0
  141. package/src/query/expression.test.ts +286 -0
  142. package/src/query/expression.ts +493 -0
  143. package/src/query/index.ts +40 -4
  144. package/src/query/injection.test.ts +151 -0
  145. package/src/query/queryBuilder.test.ts +322 -637
  146. package/src/query/queryBuilder.ts +126 -359
  147. package/src/query/queryResult.ts +54 -51
  148. package/src/query/selection.test.ts +48 -98
  149. package/src/query/selection.ts +77 -65
  150. package/src/types/entity.test.ts +44 -155
  151. package/src/types/entity.ts +60 -52
  152. package/src/types/events.ts +59 -16
  153. package/src/types/index.ts +20 -17
  154. package/src/types/rpcSchema.ts +69 -37
  155. package/src/utils/arkivTransactions.test.ts +600 -155
  156. package/src/utils/arkivTransactions.ts +181 -190
  157. package/src/utils/chains.test.ts +21 -24
  158. package/src/utils/compression.ts +1 -1
  159. package/src/utils/entities.test.ts +62 -0
  160. package/src/utils/entities.ts +109 -37
  161. package/src/utils/expirationTime.ts +114 -10
  162. package/src/utils/logger.ts +2 -2
  163. package/src/utils/revert.test.ts +99 -0
  164. package/src/utils/revert.ts +145 -0
  165. package/dist/chains-BifaY04V.cjs +0 -108
  166. package/dist/chains-CrtkVbBh.js +0 -86
  167. package/dist/chains-CrtkVbBh.js.map +0 -1
  168. package/dist/consts-Bz0LZQZY.js +0 -7
  169. package/dist/consts-Bz0LZQZY.js.map +0 -1
  170. package/dist/consts-CxT1-RXU.cjs +0 -18
  171. package/dist/entity-BhSXaB23.cjs +0 -81
  172. package/dist/entity-CN41IOrq.d.cts +0 -401
  173. package/dist/entity-CN41IOrq.d.cts.map +0 -1
  174. package/dist/entity-DGfVyB-a.js +0 -71
  175. package/dist/entity-DGfVyB-a.js.map +0 -1
  176. package/dist/entity-NnEVlePN.d.ts +0 -401
  177. package/dist/entity-NnEVlePN.d.ts.map +0 -1
  178. package/dist/index-ClkpS6s_.d.ts +0 -481
  179. package/dist/index-ClkpS6s_.d.ts.map +0 -1
  180. package/dist/index-w4qop0Pe.d.cts +0 -481
  181. package/dist/index-w4qop0Pe.d.cts.map +0 -1
  182. package/dist/payload-BpNUPOnY.js +0 -13
  183. package/dist/payload-BpNUPOnY.js.map +0 -1
  184. package/dist/payload-C2KHOUoj.d.ts +0 -6
  185. package/dist/payload-C2KHOUoj.d.ts.map +0 -1
  186. package/dist/payload-CJN8fTvX.d.cts +0 -6
  187. package/dist/payload-CJN8fTvX.d.cts.map +0 -1
  188. package/dist/payload-D8YLKcJj.cjs +0 -23
  189. package/dist/query/index.js.map +0 -1
  190. package/dist/queryBuilder-BE_XhLLa.js +0 -735
  191. package/dist/queryBuilder-BE_XhLLa.js.map +0 -1
  192. package/dist/queryBuilder-J37buGCi.cjs +0 -865
  193. package/dist/utils/index.d.cts.map +0 -1
  194. package/dist/utils/index.d.ts.map +0 -1
  195. package/dist/utils/index.js.map +0 -1
  196. package/src/actions/public/buildQuery.ts +0 -10
  197. package/src/actions/public/subscribeEntityEvents.ts +0 -93
  198. package/src/actions/wallet/updateEntity.ts +0 -59
  199. package/src/chains/braga.ts +0 -26
  200. package/src/chains/kaolin.ts +0 -29
  201. package/src/query/engine.test.ts +0 -571
  202. package/src/query/predicate.test.ts +0 -461
  203. package/src/query/predicate.ts +0 -144
  204. package/src/query/queryResult.test.ts +0 -270
  205. package/src/query/selectQueryBuilder.test.ts +0 -213
  206. package/src/types/attributes.ts +0 -28
  207. package/src/utils/validation.test.ts +0 -106
  208. package/src/utils/validation.ts +0 -80
  209. /package/dist/{types-Ci4BRClw.js → types-Bx1EKe0v.js} +0 -0
  210. /package/dist/{types-a8gm_IaQ.cjs → types-CKPddlfS.cjs} +0 -0
@@ -24,11 +24,8 @@ import type {
24
24
  MutateEntitiesReturnType,
25
25
  } from "../../actions/wallet/mutateEntities"
26
26
  import { mutateEntities } from "../../actions/wallet/mutateEntities"
27
- import type {
28
- UpdateEntityParameters,
29
- UpdateEntityReturnType,
30
- } from "../../actions/wallet/updateEntity"
31
- import { updateEntity } from "../../actions/wallet/updateEntity"
27
+ import type { PatchEntityParameters, PatchEntityReturnType } from "../../actions/wallet/patchEntity"
28
+ import { patchEntity } from "../../actions/wallet/patchEntity"
32
29
  import type { TxParams } from "../../types"
33
30
 
34
31
  export type WalletArkivActions<
@@ -60,30 +57,27 @@ export type WalletArkivActions<
60
57
  * @param txParams - Optional transaction parameters
61
58
  * @returns The created entity with transaction hash
62
59
  *
63
- * @throws {InvalidExpirationError} If `expiresIn` is not a positive integer
64
- * that is a multiple of the block time (2 seconds).
65
- * @throws {InvalidAttributeError} If a numeric attribute value is not an
66
- * integer.
60
+ * @throws {InvalidExpiryError} If the expiry exceeds a protocol bound or would leave the
61
+ * entity dead on arrival.
62
+ * @throws {InvalidValueError} If an attribute value does not fit the type it names.
63
+ * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
67
64
  *
68
65
  * @example
69
- * import { createPublicClient, http } from 'arkiv'
70
- * import { braga } from 'arkiv/chains'
66
+ * import { createWalletClient, ExpirationTime, jsonToPayload } from "@arkiv-network/sdk"
67
+ * import { i32 } from "@arkiv-network/sdk/attr"
68
+ * import { cheesecake } from "@arkiv-network/sdk/chains"
69
+ * import { http } from "viem"
71
70
  *
72
- * const client = createPublicClient({
73
- * chain: braga,
71
+ * const client = createWalletClient({
72
+ * chain: cheesecake,
74
73
  * transport: http(),
75
74
  * })
76
- * const { entityKey, txHash } = await client.createEntity({
77
- * payload: toBytes(JSON.stringify({ entity: { entityType: "testType", entityId: "testId" } })),
78
- * attributes: [{ key: "testKey", value: "testValue" }],
79
- * expiresIn: 1000,
75
+ * const { entityKey, txHash, expiresAt } = await client.createEntity({
76
+ * payload: jsonToPayload({ entityType: "testType", entityId: "testId" }),
77
+ * contentType: "application/json",
78
+ * attributes: { testKey: "testValue", level: i32(3) },
79
+ * expires: ExpirationTime.fromDays(30),
80
80
  * })
81
- * console.log("entityKey", entityKey)
82
- * console.log("txHash", txHash)
83
- * // {
84
- * // entityKey: "0x123",
85
- * // txHash: "0x123",
86
- * // }
87
81
  */
88
82
  createEntity: (
89
83
  data: CreateEntityParameters,
@@ -91,33 +85,43 @@ export type WalletArkivActions<
91
85
  ) => Promise<CreateEntityReturnType>
92
86
 
93
87
  /**
94
- * Updates the entity with the given key.
88
+ * Applies a patch to the entity with the given key: sets some fields, unsets others, and leaves
89
+ * everything it does not name alone.
95
90
  *
96
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/updateEntity
91
+ * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/patchEntity
97
92
  * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
98
93
  *
99
- * @param data - The entity update parameters
94
+ * @param data - The entity key and the mutations to apply
100
95
  * @param txParams - Optional transaction parameters
101
- * @returns The updated entity with transaction hash
96
+ * @returns The entity key and the transaction hash
102
97
  *
103
- * @throws {InvalidExpirationError} If `expiresIn` is not a positive integer
104
- * that is a multiple of the block time (2 seconds).
105
- * @throws {InvalidAttributeError} If a numeric attribute value is not an
106
- * integer.
98
+ * @throws {EmptyPatchError} If the patch has nothing to apply.
99
+ * @throws {ConflictingMutationError} If a name appears in both `set` and `unset`.
100
+ * @throws {InvalidValueError} If an attribute value does not fit the type it names.
101
+ * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
107
102
  *
108
103
  * @example
109
- * import { createWalletClient, http } from 'arkiv'
110
- * import { braga } from 'arkiv/chains'
104
+ * import { createWalletClient, jsonToPayload } from "@arkiv-network/sdk"
105
+ * import { i32 } from "@arkiv-network/sdk/attr"
106
+ * import { cheesecake } from "@arkiv-network/sdk/chains"
107
+ * import { http } from "viem"
111
108
  *
112
109
  * const client = createWalletClient({
113
- * chain: braga,
110
+ * chain: cheesecake,
114
111
  * transport: http(),
115
112
  * })
113
+ * // Publish the entity: one attribute changes, one goes away, the payload is replaced.
114
+ * const { txHash } = await client.patchEntity({
115
+ * entityKey,
116
+ * set: { status: "published", revision: i32(2) },
117
+ * unset: ["draft"],
118
+ * payload: jsonToPayload({ title: "Hello" }),
119
+ * })
116
120
  */
117
- updateEntity: (
118
- data: UpdateEntityParameters,
121
+ patchEntity: (
122
+ data: PatchEntityParameters,
119
123
  txParams?: TxParams,
120
- ) => Promise<UpdateEntityReturnType>
124
+ ) => Promise<PatchEntityReturnType>
121
125
 
122
126
  /**
123
127
  * Deletes the entity with the given key.
@@ -130,20 +134,16 @@ export type WalletArkivActions<
130
134
  * @returns The deleted entity with transaction hash
131
135
  *
132
136
  * @example
133
- * import { createWalletClient, http } from 'arkiv'
134
- * import { braga } from 'arkiv/chains'
137
+ * import { createWalletClient } from "@arkiv-network/sdk"
138
+ * import { cheesecake } from "@arkiv-network/sdk/chains"
139
+ * import { http } from "viem"
135
140
  *
136
141
  * const client = createWalletClient({
137
- * chain: braga,
142
+ * chain: cheesecake,
138
143
  * transport: http(),
139
144
  * })
140
- * const { entityKey, txHash } = await client.deleteEntity({ entityKey: "0x123" })
141
- * console.log("entityKey", entityKey)
142
- * console.log("txHash", txHash)
143
- * // {
144
- * // entityKey: "0x123",
145
- * // txHash: "0x123",
146
- * // }
145
+ * // entityKey is the bytes32 key returned by createEntity.
146
+ * const { txHash } = await client.deleteEntity({ entityKey })
147
147
  */
148
148
  deleteEntity: (
149
149
  data: DeleteEntityParameters,
@@ -151,35 +151,35 @@ export type WalletArkivActions<
151
151
  ) => Promise<DeleteEntityReturnType>
152
152
 
153
153
  /**
154
- * Extends the entity with the given key.
154
+ * Sets a new expiry on the entity with the given key.
155
+ *
156
+ * The new lifetime is resolved the same way a create's is: a duration counts from now rather
157
+ * than adding to what the entity has left, and `atBlock` / `atDate` pin an absolute deadline.
158
+ * The engine rejects an extension that would not move the expiry later.
155
159
  *
156
160
  * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/extendEntity
157
161
  * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
158
162
  *
159
- * @param data - The entity update parameters
163
+ * @param data - The entity key and its new lifetime
160
164
  * @param txParams - Optional transaction parameters
161
- * @returns The updated entity with transaction hash
165
+ * @returns The entity key, transaction hash, and the block it is now expected to expire at
162
166
  *
163
- * @throws {InvalidExpirationError} If `expiresIn` is not a positive integer
164
- * that is a multiple of the block time (2 seconds).
167
+ * @throws {InvalidExpiryError} If the expiry is malformed, exceeds a protocol bound, or would
168
+ * leave the entity dead on arrival.
165
169
  *
166
170
  * @example
167
- * import { createWalletClient, http } from 'arkiv'
168
- * import { braga } from 'arkiv/chains'
171
+ * import { createWalletClient, ExpirationTime } from "@arkiv-network/sdk"
172
+ * import { cheesecake } from "@arkiv-network/sdk/chains"
173
+ * import { http } from "viem"
169
174
  *
170
175
  * const client = createWalletClient({
171
- * chain: braga,
176
+ * chain: cheesecake,
172
177
  * transport: http(),
173
178
  * })
174
- * const { entityKey, txHash } = await client.extendEntity("0x123", {
175
- * expiresIn: 1000,
179
+ * const { txHash, expiresAt } = await client.extendEntity({
180
+ * entityKey,
181
+ * expires: ExpirationTime.fromDays(30),
176
182
  * })
177
- * console.log("entityKey", entityKey)
178
- * console.log("txHash", txHash)
179
- * // {
180
- * // entityKey: "0x123",
181
- * // txHash: "0x123",
182
- * // }
183
183
  */
184
184
  extendEntity: (
185
185
  data: ExtendEntityParameters,
@@ -207,49 +207,41 @@ export type WalletArkivActions<
207
207
  * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/mutateEntities
208
208
  * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
209
209
  *
210
- * @param data - The mutation parameters (creates, updates, deletes, extensions)
210
+ * Every operation lands in one transaction, so the whole batch applies or none of it does.
211
+ * At least one operation is required.
212
+ *
213
+ * @param data - The mutation parameters (creates, patches, deletes, extensions, ownershipChanges)
211
214
  * @param txParams - Optional transaction parameters
212
215
  * @returns The mutation result with transaction hash
213
216
  *
214
- * @throws {InvalidExpirationError} If any create/update/extension `expiresIn`
215
- * is not a positive integer that is a multiple of the block time (2 seconds).
216
- * @throws {InvalidAttributeError} If a numeric attribute value is not an
217
- * integer.
217
+ * @throws {InvalidExpiryError} If an expiry exceeds a protocol bound or would leave the entity
218
+ * dead on arrival.
219
+ * @throws {InvalidValueError} If an attribute value does not fit the type it names.
218
220
  *
219
221
  * @example
220
- * import { createWalletClient, http } from 'arkiv'
221
- * import { braga } from 'arkiv/chains'
222
+ * import { createWalletClient, ExpirationTime, jsonToPayload } from "@arkiv-network/sdk"
223
+ * import { cheesecake } from "@arkiv-network/sdk/chains"
224
+ * import { http } from "viem"
222
225
  *
223
226
  * const client = createWalletClient({
224
- * chain: braga,
227
+ * chain: cheesecake,
225
228
  * transport: http(),
226
229
  * })
227
- * const { entityKey, txHash } = await client.mutateEntities({
230
+ * const { txHash, createdEntities } = await client.mutateEntities({
228
231
  * creates: [{
229
- * payload: toBytes(JSON.stringify({ entity: { entityType: "testType", entityId: "testId" } })),
230
- * attriubutes: [{ key: "testKey", value: "testValue" }],
231
- * expiresIn: 1000,
232
- * }],
233
- * updates: [{
234
- * entityKey: "0x123",
235
- * payload: toBytes(JSON.stringify({ entity: { entityType: "testType", entityId: "testId" } })),
236
- * attributes: [{ key: "testKey", value: "testValue" }],
237
- * expiresIn: 1000,
238
- * }],
239
- * deletes: [{
240
- * entityKey: "0x321",
232
+ * payload: jsonToPayload({ entityType: "testType", entityId: "testId" }),
233
+ * contentType: "application/json",
234
+ * attributes: { testKey: "testValue" },
235
+ * expires: ExpirationTime.fromDays(30),
241
236
  * }],
237
+ * patches: [{ entityKey: keyToRevise, set: { status: "archived" }, unset: ["draft"] }],
238
+ * deletes: [{ entityKey: staleKey }],
242
239
  * extensions: [{
243
- * entityKey: "0x1234",
244
- * expiresIn: 1000,
240
+ * entityKey: keyToKeepAlive,
241
+ * expires: ExpirationTime.atBlock(1_200_000n),
245
242
  * }],
243
+ * ownershipChanges: [{ entityKey: keyToHandOver, newOwner }],
246
244
  * })
247
- * console.log("entityKey", entityKey)
248
- * console.log("txHash", txHash)
249
- * // {
250
- * // entityKey: "0x123",
251
- * // txHash: "0x123",
252
- * // }
253
245
  */
254
246
  mutateEntities: (
255
247
  data: MutateEntitiesParameters,
@@ -265,8 +257,8 @@ export function walletArkivActions<
265
257
  return {
266
258
  createEntity: (data: CreateEntityParameters, txParams?: TxParams) =>
267
259
  createEntity(client, data, txParams),
268
- updateEntity: (data: UpdateEntityParameters, txParams?: TxParams) =>
269
- updateEntity(client, data, txParams),
260
+ patchEntity: (data: PatchEntityParameters, txParams?: TxParams) =>
261
+ patchEntity(client, data, txParams),
270
262
  deleteEntity: (data: DeleteEntityParameters, txParams?: TxParams) =>
271
263
  deleteEntity(client, data, txParams),
272
264
  extendEntity: (data: ExtendEntityParameters, txParams?: TxParams) =>
@@ -0,0 +1,184 @@
1
+ import { describe, expect, it } from "bun:test"
2
+ import { decodeAbiParameters, parseAbiParameters, toHex } from "viem"
3
+ import { encodeAttributes } from "../attr/attributes"
4
+ import { dec, i32, str, u256 } from "../attr/values"
5
+ import { InvalidCreationFlagsError } from "./errors"
6
+ import { decodeCreationFlags, encodeCreationFlags } from "./flags"
7
+ import { MAX_SALT, predictEntityKey, randomSalt, validateSalt } from "./key"
8
+ import { createOperation } from "./operations"
9
+ import { OperationType } from "./params"
10
+
11
+ const OWNER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
12
+
13
+ const CREATE_PARAMS = parseAbiParameters(
14
+ "(uint128 salt, uint64 expiresAt, uint64 minLifetime, uint8 creationFlags, (bytes32 name, uint8 typeId, bytes value)[] attributes)",
15
+ )
16
+
17
+ /** Decodes an operation's payload back into the Create struct it encodes. */
18
+ function decodeCreate(operationData: `0x${string}`) {
19
+ const [create] = decodeAbiParameters(CREATE_PARAMS, operationData)
20
+ return create
21
+ }
22
+
23
+ describe("creation flags", () => {
24
+ it("packs each flag into its documented bit", () => {
25
+ expect(encodeCreationFlags({})).toBe(0b00)
26
+ expect(encodeCreationFlags({ readonly: true })).toBe(0b01)
27
+ expect(encodeCreationFlags({ permissionlessExtension: true })).toBe(0b10)
28
+ expect(encodeCreationFlags({ readonly: true, permissionlessExtension: true })).toBe(0b11)
29
+ expect(encodeCreationFlags()).toBe(0)
30
+ })
31
+
32
+ it("round-trips through the byte", () => {
33
+ for (const readonly of [true, false]) {
34
+ for (const permissionlessExtension of [true, false]) {
35
+ const decoded = decodeCreationFlags(
36
+ encodeCreationFlags({ readonly, permissionlessExtension }),
37
+ )
38
+ expect(decoded.readonly).toBe(readonly)
39
+ expect(decoded.permissionlessExtension).toBe(permissionlessExtension)
40
+ }
41
+ }
42
+ })
43
+
44
+ it("refuses a value that is not a byte at all", () => {
45
+ // Malformed input
46
+ expect(() => decodeCreationFlags(256)).toThrow(InvalidCreationFlagsError)
47
+ expect(() => decodeCreationFlags(256)).toThrow(/not a byte/)
48
+ expect(() => decodeCreationFlags(-1)).toThrow(/not a byte/)
49
+ })
50
+
51
+ it("keeps the raw byte, so unknown state stays visible", () => {
52
+ expect(decodeCreationFlags(0b11).raw).toBe(3)
53
+ })
54
+ })
55
+
56
+ describe("salt", () => {
57
+ it("generates 128 bits within the wire field", () => {
58
+ for (let i = 0; i < 32; i++) {
59
+ const salt = randomSalt()
60
+ expect(salt).toBeGreaterThanOrEqual(0n)
61
+ expect(salt).toBeLessThanOrEqual(MAX_SALT)
62
+ }
63
+ })
64
+
65
+ it("does not repeat", () => {
66
+ const seen = new Set(Array.from({ length: 64 }, () => randomSalt().toString()))
67
+ expect(seen.size).toBe(64)
68
+ })
69
+
70
+ it("bounds a caller-supplied salt to the uint128 field", () => {
71
+ expect(validateSalt(0n)).toBe(0n)
72
+ expect(validateSalt(MAX_SALT)).toBe(MAX_SALT)
73
+ expect(() => validateSalt(MAX_SALT + 1n)).toThrow(/uint128/)
74
+ expect(() => validateSalt(-1n)).toThrow(/uint128/)
75
+ })
76
+ })
77
+
78
+ describe("predictEntityKey", () => {
79
+ it("is deterministic in owner, nonce and salt", () => {
80
+ const base = { owner: OWNER, nonce: 0n, salt: 7n, chainId: 1 } as const
81
+ expect(predictEntityKey(base)).toBe(predictEntityKey(base))
82
+ expect(predictEntityKey({ ...base, nonce: 1n })).not.toBe(predictEntityKey(base))
83
+ expect(predictEntityKey({ ...base, salt: 8n })).not.toBe(predictEntityKey(base))
84
+ expect(
85
+ predictEntityKey({ ...base, owner: "0x1111111111111111111111111111111111111111" }),
86
+ ).not.toBe(predictEntityKey(base))
87
+ })
88
+
89
+ it("separates chains through the domain", () => {
90
+ const base = { owner: OWNER, nonce: 0n, salt: 0n } as const
91
+ expect(predictEntityKey({ ...base, chainId: 1 })).not.toBe(
92
+ predictEntityKey({ ...base, chainId: 2 }),
93
+ )
94
+ })
95
+
96
+ it("returns a 32-byte key", () => {
97
+ expect(predictEntityKey({ owner: OWNER, nonce: 0n, salt: 0n, chainId: 1 })).toMatch(
98
+ /^0x[0-9a-f]{64}$/,
99
+ )
100
+ })
101
+ })
102
+
103
+ describe("the Create operation", () => {
104
+ const build = (over: Partial<Parameters<typeof createOperation>[0]> = {}) =>
105
+ createOperation({
106
+ salt: 42n,
107
+ expiry: { expiresAt: 0n, minLifetime: 43_200n },
108
+ creationFlags: 0,
109
+ attributes: [],
110
+ ...over,
111
+ })
112
+
113
+ it("carries the CREATE tag", () => {
114
+ expect(build().operation).toBe(OperationType.Create)
115
+ })
116
+
117
+ it("round-trips its fields through the ABI", () => {
118
+ const create = decodeCreate(
119
+ build({
120
+ salt: 42n,
121
+ expiry: { expiresAt: 1_200_000n, minLifetime: 100n },
122
+ creationFlags: 0b11,
123
+ }).operationData,
124
+ )
125
+ expect(create.salt).toBe(42n)
126
+ expect(create.expiresAt).toBe(1_200_000n)
127
+ expect(create.minLifetime).toBe(100n)
128
+ expect(create.creationFlags).toBe(3)
129
+ })
130
+
131
+ it("carries attributes as (name, typeId, bytes), sorted ascending by name", () => {
132
+ const create = decodeCreate(
133
+ build({
134
+ attributes: encodeAttributes({ zebra: i32(1), apple: str("x"), mango: u256(2n) }),
135
+ }).operationData,
136
+ )
137
+ const names = create.attributes.map((a) => a.name)
138
+ expect(names).toEqual([...names].sort())
139
+ expect(create.attributes.map((a) => a.typeId)).toEqual([8, 4, 2]) // apple str, mango u256, zebra i32
140
+ })
141
+
142
+ it("sends the payload and content type as system cells, ahead of user attributes", () => {
143
+ const create = decodeCreate(
144
+ build({
145
+ attributes: encodeAttributes(
146
+ { level: i32(10) },
147
+ { payload: new Uint8Array([0xde, 0xad]), contentType: "application/json" },
148
+ ),
149
+ }).operationData,
150
+ )
151
+ const byName = Object.fromEntries(create.attributes.map((a) => [a.name, a]))
152
+
153
+ // "$" sorts below every letter, so the system cells lead.
154
+ expect(create.attributes[0].name).toBe(toHex("$contentType", { size: 32 }))
155
+ expect(create.attributes[1].name).toBe(toHex("$payload", { size: 32 }))
156
+
157
+ expect(byName[toHex("$payload", { size: 32 })]).toMatchObject({ typeId: 7, value: "0xdead" })
158
+ expect(byName[toHex("$contentType", { size: 32 })].typeId).toBe(8)
159
+ expect(byName[toHex("level", { size: 32 })].typeId).toBe(2)
160
+ })
161
+
162
+ it("omits the system cells entirely when there is no payload or content type", () => {
163
+ const create = decodeCreate(
164
+ build({ attributes: encodeAttributes({ level: i32(10) }) }).operationData,
165
+ )
166
+ expect(create.attributes).toHaveLength(1)
167
+ expect(create.attributes[0].name).toBe(toHex("level", { size: 32 }))
168
+ })
169
+
170
+ it("encodes each value type at its documented width", () => {
171
+ const create = decodeCreate(
172
+ build({
173
+ attributes: encodeAttributes({ n: i32(-42), big: u256(1n), d: dec("1.5"), s: str("hi") }),
174
+ }).operationData,
175
+ )
176
+ const byName = Object.fromEntries(create.attributes.map((a) => [a.name, a.value]))
177
+ // Word types are exactly 32 bytes; i32 is sign-extended.
178
+ expect(byName[toHex("n", { size: 32 })]).toBe(`0x${"ff".repeat(31)}d6`)
179
+ expect(byName[toHex("big", { size: 32 })]).toBe(`0x${"00".repeat(31)}01`)
180
+ expect(BigInt(byName[toHex("d", { size: 32 })])).toBe(1_500_000_000_000_000_000n)
181
+ // str is raw UTF-8, not padded.
182
+ expect(byName[toHex("s", { size: 32 })]).toBe("0x6869")
183
+ })
184
+ })
@@ -0,0 +1,38 @@
1
+ /** An expiry could not be expressed, or would be rejected by the engine. */
2
+ export class InvalidExpiryError extends Error {
3
+ constructor(reason: string) {
4
+ super(`Invalid expiry: ${reason}`)
5
+ this.name = "InvalidExpiryError"
6
+ }
7
+ }
8
+
9
+ /** A `creationFlags` byte could not be built or read. */
10
+ export class InvalidCreationFlagsError extends Error {
11
+ constructor(reason: string) {
12
+ super(`Invalid creation flags: ${reason}`)
13
+ this.name = "InvalidCreationFlagsError"
14
+ }
15
+ }
16
+
17
+ /** A key salt was outside the `uint128` the wire carries. */
18
+ export class InvalidSaltError extends Error {
19
+ constructor(salt: bigint) {
20
+ super(
21
+ `Invalid salt ${salt}. A salt is a uint128, so it must be between 0 and ` +
22
+ `${2n ** 128n - 1n}. Omit it to get 128 random bits, or pass 0 for a predictable key.`,
23
+ )
24
+ this.name = "InvalidSaltError"
25
+ }
26
+ }
27
+
28
+ /** No cryptographic randomness was available to generate a salt. */
29
+ export class NoRandomSourceError extends Error {
30
+ constructor() {
31
+ super(
32
+ "No cryptographic random source is available (globalThis.crypto.getRandomValues is " +
33
+ "missing), so a key salt cannot be generated. Upgrade to Node 18+ or a modern browser, " +
34
+ "or pass an explicit salt.",
35
+ )
36
+ this.name = "NoRandomSourceError"
37
+ }
38
+ }
@@ -0,0 +1,16 @@
1
+ import { parseAbi } from "viem"
2
+
3
+ /**
4
+ * The events the engine emits: one per applied operation, in application order, so an off-chain
5
+ * replica can replay a transaction operation by operation.
6
+ */
7
+ export const ENTITY_EVENTS_ABI = parseAbi([
8
+ "event EntityCreated(bytes32 indexed entityKey, address indexed owner, uint64 expiresAt, uint8 creationFlags)",
9
+ "event EntityPatched(bytes32 indexed entityKey, address indexed owner)",
10
+ "event ExpiryExtended(bytes32 indexed entityKey, address indexed owner, uint64 expiresAt)",
11
+ "event OwnershipTransferred(bytes32 indexed entityKey, address indexed previousOwner, address indexed newOwner)",
12
+ "event EntityDeleted(bytes32 indexed entityKey, address indexed owner)",
13
+ ])
14
+
15
+ /** The name of each event in {@link ENTITY_EVENTS_ABI}. */
16
+ export type EntityEventName = (typeof ENTITY_EVENTS_ABI)[number]["name"]