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

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 (135) hide show
  1. package/dist/attr/index.cjs +1 -1
  2. package/dist/attr/index.d.cts +1 -1
  3. package/dist/attr/index.d.ts +1 -1
  4. package/dist/attr/index.js +1 -1
  5. package/dist/{attr-BvxHN99W.cjs → attr-BXTkpnoc.cjs} +13 -3
  6. package/dist/{attr-BDZ-APg5.js → attr-tmyREHtw.js} +14 -4
  7. package/dist/attr-tmyREHtw.js.map +1 -0
  8. package/dist/chains/index.cjs +1 -1
  9. package/dist/chains/index.d.cts +3 -0
  10. package/dist/chains/index.d.cts.map +1 -1
  11. package/dist/chains/index.d.ts +3 -0
  12. package/dist/chains/index.d.ts.map +1 -1
  13. package/dist/chains/index.js +1 -1
  14. package/dist/{chains-BYRTTipa.cjs → chains-BfNQliHU.cjs} +3 -0
  15. package/dist/{chains-BJrZs6sG.js → chains-Du8tql7I.js} +4 -1
  16. package/dist/chains-Du8tql7I.js.map +1 -0
  17. package/dist/{changeOwnership-DwgtdSFc.d.ts → changeOwnership-B0ar96ub.d.ts} +47 -34
  18. package/dist/changeOwnership-B0ar96ub.d.ts.map +1 -0
  19. package/dist/{changeOwnership-B7bEgla4.d.cts → changeOwnership-BofkLnXJ.d.cts} +47 -34
  20. package/dist/changeOwnership-BofkLnXJ.d.cts.map +1 -0
  21. package/dist/entity/index.cjs +5 -4
  22. package/dist/entity/index.d.cts +2 -2
  23. package/dist/entity/index.d.ts +2 -2
  24. package/dist/entity/index.js +5 -5
  25. package/dist/{entity-CFhbAv0R.js → entity-CniBgBSd.js} +17 -5
  26. package/dist/entity-CniBgBSd.js.map +1 -0
  27. package/dist/{entity-wfBnZd91.cjs → entity-DJ0Gml9c.cjs} +23 -5
  28. package/dist/{errors-DGB8AgT2.js → errors-D2E1GRjw.js} +2 -2
  29. package/dist/errors-D2E1GRjw.js.map +1 -0
  30. package/dist/{errors-D-eJkGVc.cjs → errors-DhV00966.cjs} +1 -1
  31. package/dist/expiry-BxOlvVWC.d.ts.map +1 -1
  32. package/dist/{expiry-BUYIXTGb.cjs → expiry-CG9GhVXv.cjs} +1 -1
  33. package/dist/{expiry-DGRzrSan.js → expiry-Cgdb_dbq.js} +2 -2
  34. package/dist/expiry-Cgdb_dbq.js.map +1 -0
  35. package/dist/expiry-DRV29F1r.d.cts.map +1 -1
  36. package/dist/{flags-Ct8k0gpB.cjs → flags-DCD26uhe.cjs} +1 -1
  37. package/dist/{flags-ZtlvOIIk.js → flags-DUT2gHEU.js} +2 -2
  38. package/dist/{flags-ZtlvOIIk.js.map → flags-DUT2gHEU.js.map} +1 -1
  39. package/dist/{index-D0CmtiWT.d.cts → index-BGp2plHs.d.cts} +15 -5
  40. package/dist/index-BGp2plHs.d.cts.map +1 -0
  41. package/dist/{index-VCqgn29D.d.ts → index-BKB875Um.d.ts} +4 -4
  42. package/dist/{index-VCqgn29D.d.ts.map → index-BKB875Um.d.ts.map} +1 -1
  43. package/dist/{index-DVYFaXNV.d.ts → index-CIcHPx2j.d.cts} +13 -6
  44. package/dist/index-CIcHPx2j.d.cts.map +1 -0
  45. package/dist/{index-D8LZt-3J.d.cts → index-Cd2aaWme.d.ts} +13 -6
  46. package/dist/index-Cd2aaWme.d.ts.map +1 -0
  47. package/dist/{index-ClgIuxJ8.d.ts → index-ezMZ2-JH.d.ts} +15 -5
  48. package/dist/index-ezMZ2-JH.d.ts.map +1 -0
  49. package/dist/{index-Cgw1u5aY.d.cts → index-rINthNuU.d.cts} +4 -4
  50. package/dist/{index-Cgw1u5aY.d.cts.map → index-rINthNuU.d.cts.map} +1 -1
  51. package/dist/index.cjs +59 -50
  52. package/dist/index.d.cts +58 -50
  53. package/dist/index.d.cts.map +1 -1
  54. package/dist/index.d.ts +58 -50
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +59 -51
  57. package/dist/index.js.map +1 -1
  58. package/dist/{payload-Dgx3PQeK.d.ts → payload-CGvVPZQX.d.ts} +60 -1
  59. package/dist/payload-CGvVPZQX.d.ts.map +1 -0
  60. package/dist/{payload-CIxSn_DW.js → payload-DE7wMztL.js} +28 -3
  61. package/dist/payload-DE7wMztL.js.map +1 -0
  62. package/dist/{payload-P0nWyjLg.d.cts → payload-Dfu30njp.d.cts} +60 -1
  63. package/dist/payload-Dfu30njp.d.cts.map +1 -0
  64. package/dist/{payload-Bnw1hrvb.cjs → payload-DqFweqiO.cjs} +27 -2
  65. package/dist/query/index.cjs +4 -4
  66. package/dist/query/index.d.cts +3 -3
  67. package/dist/query/index.d.ts +3 -3
  68. package/dist/query/index.js +4 -4
  69. package/dist/{queryBuilder-CbLL4pG5.js → queryBuilder-COWt6aIF.js} +17 -12
  70. package/dist/queryBuilder-COWt6aIF.js.map +1 -0
  71. package/dist/{queryBuilder-BXDvX1K_.cjs → queryBuilder-DaLxNgeI.cjs} +16 -11
  72. package/dist/{selection-BvK6yX0g.d.cts → selection-Bv8_o-7i.d.cts} +16 -8
  73. package/dist/selection-Bv8_o-7i.d.cts.map +1 -0
  74. package/dist/{selection-fpmI3ADQ.d.ts → selection-ox7zdtVm.d.ts} +16 -8
  75. package/dist/selection-ox7zdtVm.d.ts.map +1 -0
  76. package/dist/types/index.d.cts +5 -5
  77. package/dist/types/index.d.ts +5 -5
  78. package/dist/utils/index.cjs +3 -3
  79. package/dist/utils/index.d.cts +1 -1
  80. package/dist/utils/index.d.ts +1 -1
  81. package/dist/utils/index.js +3 -3
  82. package/package.json +1 -5
  83. package/src/actions/public/getEntity.ts +1 -0
  84. package/src/actions/public/predictEntityKeys.test.ts +17 -1
  85. package/src/actions/public/predictEntityKeys.ts +28 -14
  86. package/src/actions/wallet/createEntity.ts +8 -6
  87. package/src/actions/wallet/{mutateEntities.test.ts → executeBatch.test.ts} +7 -7
  88. package/src/actions/wallet/{mutateEntities.ts → executeBatch.ts} +9 -9
  89. package/src/actions/wallet/extendEntity.ts +2 -0
  90. package/src/actions/wallet/patchEntity.ts +3 -3
  91. package/src/attr/attributes.ts +8 -2
  92. package/src/attr/errors.ts +2 -2
  93. package/src/attr/types.ts +3 -3
  94. package/src/attr/values.ts +5 -1
  95. package/src/chains/localhost.ts +3 -0
  96. package/src/clients/createPublicClient.test.ts +1 -1
  97. package/src/clients/createPublicClient.ts +5 -5
  98. package/src/clients/createWalletClient.test.ts +2 -2
  99. package/src/clients/createWalletClient.ts +3 -5
  100. package/src/clients/decorators/arkivPublic.ts +13 -12
  101. package/src/clients/decorators/arkivWallet.ts +40 -32
  102. package/src/entity/create.test.ts +20 -1
  103. package/src/entity/errors.ts +4 -3
  104. package/src/entity/expiry.ts +1 -0
  105. package/src/entity/index.ts +2 -1
  106. package/src/entity/key.ts +23 -3
  107. package/src/entity/operations.ts +1 -0
  108. package/src/errors.ts +7 -2
  109. package/src/query/errors.ts +1 -1
  110. package/src/query/expression.ts +14 -7
  111. package/src/query/queryBuilder.ts +1 -1
  112. package/src/types/index.ts +4 -4
  113. package/src/types/rpcSchema.ts +1 -0
  114. package/src/types/txParams.ts +8 -0
  115. package/src/utils/arkivTransactions.test.ts +7 -1
  116. package/src/utils/arkivTransactions.ts +10 -5
  117. package/src/utils/expirationTime.ts +39 -0
  118. package/src/utils/payload.ts +25 -0
  119. package/dist/attr-BDZ-APg5.js.map +0 -1
  120. package/dist/chains-BJrZs6sG.js.map +0 -1
  121. package/dist/changeOwnership-B7bEgla4.d.cts.map +0 -1
  122. package/dist/changeOwnership-DwgtdSFc.d.ts.map +0 -1
  123. package/dist/entity-CFhbAv0R.js.map +0 -1
  124. package/dist/errors-DGB8AgT2.js.map +0 -1
  125. package/dist/expiry-DGRzrSan.js.map +0 -1
  126. package/dist/index-ClgIuxJ8.d.ts.map +0 -1
  127. package/dist/index-D0CmtiWT.d.cts.map +0 -1
  128. package/dist/index-D8LZt-3J.d.cts.map +0 -1
  129. package/dist/index-DVYFaXNV.d.ts.map +0 -1
  130. package/dist/payload-CIxSn_DW.js.map +0 -1
  131. package/dist/payload-Dgx3PQeK.d.ts.map +0 -1
  132. package/dist/payload-P0nWyjLg.d.cts.map +0 -1
  133. package/dist/queryBuilder-CbLL4pG5.js.map +0 -1
  134. package/dist/selection-BvK6yX0g.d.cts.map +0 -1
  135. package/dist/selection-fpmI3ADQ.d.ts.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it, vi } from "bun:test"
2
2
  import type { ArkivClient } from "../../clients/baseClient"
3
- import { mutateEntities } from "./mutateEntities"
3
+ import { executeBatch } from "./executeBatch"
4
4
 
5
5
  const ENTITY_KEY = `0x${"ab".repeat(32)}` as const
6
6
  const NEW_OWNER = "0x2222222222222222222222222222222222222222"
@@ -29,7 +29,7 @@ describe("the empty-batch guard", () => {
29
29
  // An ownership-only batch is a batch: it used to be rejected because the guard listed
30
30
  // creates/updates/deletes/extensions and forgot ownershipChanges.
31
31
  const { client, writeContract } = makeClient()
32
- const result = await mutateEntities(client, {
32
+ const result = await executeBatch(client, {
33
33
  ownershipChanges: [{ entityKey: ENTITY_KEY, newOwner: NEW_OWNER }],
34
34
  })
35
35
  expect(writeContract).toHaveBeenCalledTimes(1)
@@ -38,9 +38,9 @@ describe("the empty-batch guard", () => {
38
38
 
39
39
  it("rejects a batch with nothing in it", async () => {
40
40
  const { client, writeContract } = makeClient()
41
- await expect(mutateEntities(client, {})).rejects.toThrow(/No operations/)
41
+ await expect(executeBatch(client, {})).rejects.toThrow(/No operations/)
42
42
  // Present but empty is just as empty — the engine would revert with EmptyBatch().
43
- await expect(mutateEntities(client, { deletes: [], ownershipChanges: [] })).rejects.toThrow(
43
+ await expect(executeBatch(client, { deletes: [], ownershipChanges: [] })).rejects.toThrow(
44
44
  /No operations/,
45
45
  )
46
46
  expect(writeContract).not.toHaveBeenCalled()
@@ -52,10 +52,10 @@ describe("the empty-batch guard", () => {
52
52
  // keys would score this batch as non-empty and then drop it on the way to the wire, sending
53
53
  // execute([]) — so the guard counts the five kinds that actually reach the transaction.
54
54
  const stale = { updates: [{ entityKey: ENTITY_KEY, set: { level: 1 } }] } as never
55
- await expect(mutateEntities(client, stale)).rejects.toThrow(/No operations/)
55
+ await expect(executeBatch(client, stale)).rejects.toThrow(/No operations/)
56
56
  // A typo is the same mistake with a different name.
57
57
  await expect(
58
- mutateEntities(client, { delete: [{ entityKey: ENTITY_KEY }] } as never),
58
+ executeBatch(client, { delete: [{ entityKey: ENTITY_KEY }] } as never),
59
59
  ).rejects.toThrow(/No operations/)
60
60
  expect(writeContract).not.toHaveBeenCalled()
61
61
  })
@@ -65,7 +65,7 @@ describe("the empty-batch guard", () => {
65
65
  // The dangerous shape: the creates apply and the transaction succeeds, so nothing surfaces the
66
66
  // fact that the `updates` never happened. The guard cannot catch this one — only the caller's
67
67
  // types can — but the result must at least not claim they did.
68
- const result = await mutateEntities(client, {
68
+ const result = await executeBatch(client, {
69
69
  deletes: [{ entityKey: ENTITY_KEY }],
70
70
  updates: [{ entityKey: ENTITY_KEY, set: { level: 1 } }],
71
71
  } as never)
@@ -9,15 +9,15 @@ import type { DeleteEntityParameters } from "./deleteEntity"
9
9
  import type { ExtendEntityParameters } from "./extendEntity"
10
10
  import type { PatchEntityParameters } from "./patchEntity"
11
11
 
12
- const logger = getLogger("actions:wallet:mutate-entities")
12
+ const logger = getLogger("actions:wallet:execute-batch")
13
13
 
14
14
  /**
15
- * Parameters for the mutateEntities function.
15
+ * Parameters for the executeBatch function.
16
16
  *
17
17
  * At least one operation is required; a batch with nothing in it throws rather than spending gas on
18
18
  * an empty transaction.
19
19
  */
20
- export type MutateEntitiesParameters = {
20
+ export type ExecuteBatchParameters = {
21
21
  /** The entities to create. */
22
22
  creates?: CreateEntityParameters[]
23
23
  /** The patches to apply. */
@@ -30,8 +30,8 @@ export type MutateEntitiesParameters = {
30
30
  ownershipChanges?: ChangeOwnershipParameters[]
31
31
  }
32
32
 
33
- /** Return type for the mutateEntities function. */
34
- export type MutateEntitiesReturnType = {
33
+ /** Return type for the executeBatch function. */
34
+ export type ExecuteBatchReturnType = {
35
35
  /** The transaction hash. */
36
36
  txHash: Hash
37
37
  /** The keys of the created entities, in batch order. */
@@ -46,14 +46,14 @@ export type MutateEntitiesReturnType = {
46
46
  ownershipChanges: Hex[]
47
47
  }
48
48
 
49
- export async function mutateEntities(
49
+ export async function executeBatch(
50
50
  client: ArkivClient,
51
- data: MutateEntitiesParameters,
51
+ data: ExecuteBatchParameters,
52
52
  txParams?: TxParams,
53
- ): Promise<MutateEntitiesReturnType> {
53
+ ): Promise<ExecuteBatchReturnType> {
54
54
  const { receipt, createdEntityKeys } = await sendArkivTransaction(client, data, txParams)
55
55
 
56
- logger("Receipt from mutateEntities %o", receipt)
56
+ logger("Receipt from executeBatch %o", receipt)
57
57
 
58
58
  return {
59
59
  txHash: receipt.transactionHash as Hash,
@@ -4,6 +4,7 @@ import type { Expiry } from "../../entity"
4
4
  import { EntityMutationError } from "../../errors"
5
5
  import type { TxParams } from "../../types"
6
6
  import { sendArkivTransaction } from "../../utils/arkivTransactions"
7
+ import type { ExpirationTime } from "../../utils/expirationTime"
7
8
  import { getLogger } from "../../utils/logger"
8
9
 
9
10
  const logger = getLogger("actions:wallet:extend-entity")
@@ -61,6 +62,7 @@ export async function extendEntity(
61
62
  if (expiresAt === undefined) {
62
63
  throw new EntityMutationError(
63
64
  `Transaction ${receipt.transactionHash} succeeded, but the receipt did not carry the expected entity expiry.`,
65
+ { txHash: receipt.transactionHash },
64
66
  )
65
67
  }
66
68
 
@@ -38,9 +38,9 @@ export type PatchEntityParameters = {
38
38
  * Attributes to write, keyed by name. An existing attribute is overwritten — including with a
39
39
  * different type — and a name the entity does not carry yet is added.
40
40
  *
41
- * Values are the tagged constructors from `@arkiv-network/sdk/attr` — `i32`, `u256`, `dec`, `str`,
42
- * `addr`, `key`, `bytes32`, `bool` — or a bare `boolean`, `number`, `bigint` or `string` where the
43
- * type is unambiguous.
41
+ * Values are the tagged constructors from `@arkiv-network/sdk/attr` — `i32`, `u64`, `u256`,
42
+ * `dec`, `str`, `addr`, `key`, `bytes32`, `bool` — or a bare `boolean`, `number`, `bigint` or
43
+ * `string` where the type is unambiguous.
44
44
  *
45
45
  * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
46
46
  * @throws {InvalidValueError} If a value does not fit the type it names or defaults to.
@@ -10,6 +10,10 @@ import { str, toValue, type ValueInput } from "./values"
10
10
  */
11
11
  export const MAX_ATTRIBUTES = 32
12
12
 
13
+ /**
14
+ * The largest payload an entity may carry, in bytes. The payload travels as the `$payload` system
15
+ * cell, so this is a bound on that one attribute rather than on the operation as a whole.
16
+ */
13
17
  export const MAX_PAYLOAD_BYTES = 128 * 1024
14
18
 
15
19
  /** The system attribute holding an entity's opaque payload. */
@@ -20,7 +24,7 @@ const CONTENT_TYPE_CELL = "$contentType"
20
24
  /**
21
25
  * The attributes written on an entity, as a plain object keyed by attribute name.
22
26
  *
23
- * Values may be tagged ({@link ValueInput}) or bare where the type is unambiguous — see
27
+ * Values may be tagged ({@link ArkivValue}) or bare where the type is unambiguous — see
24
28
  * {@link toValue} for the bare-form defaults.
25
29
  *
26
30
  * @example
@@ -141,8 +145,10 @@ export type MutationInputs = SystemCells & {
141
145
  *
142
146
  * @throws {ConflictingMutationError} If a name appears in both `set` and `unset`.
143
147
  * @throws {InvalidAttributeNameError} If a name violates the attribute-name grammar.
148
+ * @throws {MissingValueError} If a value is `undefined` or `null`.
144
149
  * @throws {InvalidValueError} If a value does not fit its type.
145
- * @throws {TooManyAttributesError} If `set` names more than {@link MAX_ATTRIBUTES} attributes.
150
+ * @throws {TooManyAttributesError} If the mutations exceed {@link MAX_ATTRIBUTES}.
151
+ * @throws {TypeError} If `unset` is not an array.
146
152
  */
147
153
  export function encodeMutations({
148
154
  set,
@@ -32,8 +32,8 @@ export class UntypedValueError extends Error {
32
32
  constructor(input: unknown) {
33
33
  super(
34
34
  `Cannot infer an Arkiv type for ${describe(input)}. ` +
35
- "Attribute values must be a tagged value (i32, u256, dec, str, addr, key, bytes32, bool) " +
36
- "or a bare boolean, number, bigint or string.",
35
+ "Attribute values must be a tagged value (i32, u64, u256, dec, str, addr, key, bytes32, " +
36
+ "bool) or a bare boolean, number, bigint or string.",
37
37
  )
38
38
  this.name = "UntypedValueError"
39
39
  this.input = input
package/src/attr/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Address, Hex } from "viem"
2
+ import type { decUnits } from "./values"
2
3
 
3
4
  /**
4
5
  * The protocol's attribute type vocabulary, mapping each short **tag** (the name used by the
@@ -54,8 +55,7 @@ export function isUserSettable(tag: TypeTag): tag is UserTypeTag {
54
55
  /**
55
56
  * Marks a value as having passed its constructor's validation. The symbol is module-private, so a
56
57
  * hand-written object literal is not assignable to {@link ArkivValue} — every value crossing the
57
- * wire has been range-, length- and format-checked by {@link i32}, {@link u256}, {@link dec}, and
58
- * friends.
58
+ * wire has been range-, length- and format-checked by `i32`, `u256`, `dec`, and friends.
59
59
  */
60
60
  declare const validated: unique symbol
61
61
 
@@ -89,7 +89,7 @@ export type U256Value = Value<"u256", bigint>
89
89
  export type DecValue = Value<"dec", string>
90
90
  /** 32 opaque bytes, as lowercase `0x` hex. */
91
91
  export type Bytes32Value = Value<"bytes32", Hex>
92
- /** A UTF-8 string of at most 128 bytes. */
92
+ /** A UTF-8 string of at most 128 bytes, free of C0 control characters and DEL. */
93
93
  export type StrValue = Value<"str", string>
94
94
  /** A 20-byte Ethereum address, as EIP-55 checksummed hex. */
95
95
  export type AddrValue = Value<"addr", Address>
@@ -271,7 +271,11 @@ export function decFromUnits(units: bigint): DecValue {
271
271
  *
272
272
  * The limit is on **bytes**, not characters: a string of 128 emoji does not fit.
273
273
  *
274
- * @throws {InvalidValueError} If the string exceeds 128 UTF-8 bytes.
274
+ * C0 control characters and DEL are rejected: the query language writes a `str` as `str('...')`,
275
+ * whose only escape is a doubled quote, so a control character could be stored but never queried.
276
+ *
277
+ * @throws {InvalidValueError} If the value is not a string, holds a control character, or exceeds
278
+ * 128 UTF-8 bytes.
275
279
  *
276
280
  * @example
277
281
  * str("Bob")
@@ -1,5 +1,8 @@
1
1
  import { defineChain } from "viem"
2
2
 
3
+ /**
4
+ * A node running on the local machine
5
+ */
3
6
  export const localhost = defineChain({
4
7
  id: 1337,
5
8
  name: "Localhost",
@@ -35,5 +35,5 @@ test("creates", () => {
35
35
  expect(client.deleteEntity).not.toBeDefined()
36
36
  expect(client.extendEntity).not.toBeDefined()
37
37
  expect(client.changeOwnership).not.toBeDefined()
38
- expect(client.mutateEntities).not.toBeDefined()
38
+ expect(client.executeBatch).not.toBeDefined()
39
39
  })
@@ -23,14 +23,14 @@ export type PublicArkivClient<
23
23
  >
24
24
 
25
25
  /**
26
- * Creates a Public Client with a given [Transport](https://viem.sh/docs/clients/intro) configured for a [Chain](https://viem.sh/docs/clients/chains).
26
+ * Creates a Public Client with a given [Transport](https://viem.sh/docs/clients/intro) configured for a [Chain](https://viem.sh/docs/chains/introduction).
27
27
  *
28
- * - Docs: https://docs.arkiv.network/ts-sdk/clients/public
29
- *
30
- * A Public Client is an interface to "public" [Ethereum JSON-RPC API](https://ethereum.org/en/developers/docs/apis/json-rpc/), [Arkiv JSON-RPC API](https://docs.arkiv.network/json-rpc/), and [Cheesecake JSON-RPC API](https://rpc.cheesecake.db-chain.devnet.gobas.me) methods such as retrieving block numbers, transactions, reading from smart contracts, etc through [Public Actions](/docs/actions/public/introduction).
28
+ * A Public Client is an interface to the "public" [Ethereum JSON-RPC API](https://ethereum.org/en/developers/docs/apis/json-rpc/)
29
+ * and the Arkiv JSON-RPC API — retrieving block numbers and transactions, reading from
30
+ * contracts, and querying entities — through {@link PublicArkivActions}.
31
31
  *
32
32
  * @param parameters - Configuration object for the public client (chain, transport, etc.)
33
- * @returns A Arkiv Public Client. {@link PublicArkivClient}
33
+ * @returns An Arkiv Public Client. {@link PublicArkivClient}
34
34
  *
35
35
  * @example
36
36
  * import { createPublicClient } from "@arkiv-network/sdk"
@@ -29,6 +29,6 @@ test("creates", () => {
29
29
  expect(typeof client.extendEntity).toBe("function")
30
30
  expect(client.changeOwnership).toBeDefined()
31
31
  expect(typeof client.changeOwnership).toBe("function")
32
- expect(client.mutateEntities).toBeDefined()
33
- expect(typeof client.mutateEntities).toBe("function")
32
+ expect(client.executeBatch).toBeDefined()
33
+ expect(typeof client.executeBatch).toBe("function")
34
34
  })
@@ -24,16 +24,14 @@ export type WalletArkivClient<
24
24
 
25
25
  /**
26
26
  * Creates a Wallet Client with a given [Transport](https://viem.sh/docs/clients/intro) configured
27
- * for a [Chain](https://viem.sh/docs/clients/chains) and an account.
28
- *
29
- * - Docs: https://docs.arkiv.network/ts-sdk/clients/wallet
27
+ * for a [Chain](https://viem.sh/docs/chains/introduction) and an account.
30
28
  *
31
29
  * A Wallet Client signs and sends transactions, so it carries an `account` and exposes everything
32
30
  * that mutates state — `createEntity`, `patchEntity`, `deleteEntity`, `extendEntity`,
33
- * `changeOwnership` and `mutateEntities` — on top of the read actions a Public Client has.
31
+ * `changeOwnership` and `executeBatch` — on top of the read actions a Public Client has.
34
32
  *
35
33
  * @param parameters - Configuration object for the wallet client (chain, transport, account, etc.)
36
- * @returns A Arkiv Wallet Client. {@link WalletArkivClient}
34
+ * @returns An Arkiv Wallet Client. {@link WalletArkivClient}
37
35
  *
38
36
  * @example
39
37
  * import { createWalletClient } from "@arkiv-network/sdk"
@@ -1,5 +1,5 @@
1
1
  import type { Account, Address, Chain, Client, Hex, PublicActions, Transport } from "viem"
2
- import { getBlockTiming } from "../../actions/public/getBlockTiming"
2
+ import { type GetBlockTimingReturnType, getBlockTiming } from "../../actions/public/getBlockTiming"
3
3
  import { getEntity } from "../../actions/public/getEntity"
4
4
  import { getEntityCount } from "../../actions/public/getEntityCount"
5
5
  import { getEntityNonce } from "../../actions/public/getEntityNonce"
@@ -13,6 +13,7 @@ import {
13
13
  type WatchEntityEventsParameters,
14
14
  watchEntityEvents,
15
15
  } from "../../actions/public/watchEntityEvents"
16
+ import type { SaltInput } from "../../entity/key"
16
17
  import type { Expression } from "../../query/expression"
17
18
  import { SelectQueryBuilder } from "../../query/queryBuilder"
18
19
  import type { EntitySelection, FullEntity, ProjectedEntity, SelectArg } from "../../query/selection"
@@ -38,11 +39,15 @@ export type PublicArkivActions<
38
39
  /**
39
40
  * Returns the entity with the given key.
40
41
  *
41
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/public/getEntity
42
+ * - JSON-RPC Methods: `arkiv_query`
42
43
  *
43
44
  * @param key - The entity key (hex string)
44
45
  * @returns The entity with the given key, with every field populated. {@link FullEntity}
45
46
  *
47
+ * @throws {InvalidValueError} If the key is not 32 bytes.
48
+ * @throws {NoEntityFoundError} If no live entity has that key — it never existed, or it was
49
+ * deleted or has expired.
50
+ *
46
51
  * @example
47
52
  * import { createPublicClient } from "@arkiv-network/sdk"
48
53
  * import { cheesecake } from "@arkiv-network/sdk/chains"
@@ -69,7 +74,7 @@ export type PublicArkivActions<
69
74
  * read entities. You declare up front which parts of an entity you want returned, so results
70
75
  * always contain exactly the data you asked for.
71
76
  *
72
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/public/query
77
+ * - JSON-RPC Methods: `arkiv_query`
73
78
  *
74
79
  * @param selection - What to include in the results. Omit it (or pass `"*"`) to select everything,
75
80
  * or pass an object to select specific parts (at least one field is required). Every part is
@@ -130,7 +135,7 @@ export type PublicArkivActions<
130
135
  /**
131
136
  * Runs one query and returns one page, with no builder in between.
132
137
  *
133
- * Use {@link select} for anything typed — this returns full {@link Entity} objects whatever the
138
+ * Use {@link PublicArkivActions.select} for anything typed — this returns full {@link Entity} objects whatever the
134
139
  * selection, and takes a raw string as an escape hatch for a query built elsewhere. A raw string
135
140
  * goes to the node exactly as written, with none of the name, type or operator checks the
136
141
  * expression combinators apply.
@@ -224,7 +229,7 @@ export type PublicArkivActions<
224
229
  * import { key } from "@arkiv-network/sdk/attr"
225
230
  *
226
231
  * const [parent, child] = await client.predictEntityKeys({ owner: account.address, count: 2 })
227
- * await wallet.mutateEntities({
232
+ * await wallet.executeBatch({
228
233
  * creates: [
229
234
  * { payload, contentType, expires, salt: parent.salt },
230
235
  * {
@@ -239,7 +244,7 @@ export type PublicArkivActions<
239
244
  */
240
245
  predictEntityKeys: <
241
246
  const TCount extends number = number,
242
- const TSalts extends readonly bigint[] | undefined = undefined,
247
+ const TSalts extends readonly SaltInput[] | undefined = undefined,
243
248
  >(
244
249
  parameters: PredictEntityKeysParameters<TCount, TSalts>,
245
250
  ) => Promise<PredictEntityKeysReturnType<TCount, TSalts>>
@@ -264,11 +269,7 @@ export type PublicArkivActions<
264
269
  * // blockDuration: 2, // in seconds
265
270
  * // }
266
271
  */
267
- getBlockTiming: () => Promise<{
268
- currentBlock: bigint
269
- currentBlockTime: number
270
- blockDuration: number
271
- }>
272
+ getBlockTiming: () => Promise<GetBlockTimingReturnType>
272
273
 
273
274
  /**
274
275
  * Watches entity events, calling the handlers you pass as they arrive.
@@ -314,7 +315,7 @@ export function publicArkivActions<
314
315
  getEntityNonce: (owner: Address) => getEntityNonce(client, owner),
315
316
  predictEntityKeys: <
316
317
  const TCount extends number = number,
317
- const TSalts extends readonly bigint[] | undefined = undefined,
318
+ const TSalts extends readonly SaltInput[] | undefined = undefined,
318
319
  >(
319
320
  parameters: PredictEntityKeysParameters<TCount, TSalts>,
320
321
  ) => predictEntityKeys(client, parameters),
@@ -14,16 +14,16 @@ import type {
14
14
  DeleteEntityReturnType,
15
15
  } from "../../actions/wallet/deleteEntity"
16
16
  import { deleteEntity } from "../../actions/wallet/deleteEntity"
17
+ import type {
18
+ ExecuteBatchParameters,
19
+ ExecuteBatchReturnType,
20
+ } from "../../actions/wallet/executeBatch"
21
+ import { executeBatch } from "../../actions/wallet/executeBatch"
17
22
  import type {
18
23
  ExtendEntityParameters,
19
24
  ExtendEntityReturnType,
20
25
  } from "../../actions/wallet/extendEntity"
21
26
  import { extendEntity } from "../../actions/wallet/extendEntity"
22
- import type {
23
- MutateEntitiesParameters,
24
- MutateEntitiesReturnType,
25
- } from "../../actions/wallet/mutateEntities"
26
- import { mutateEntities } from "../../actions/wallet/mutateEntities"
27
27
  import type { PatchEntityParameters, PatchEntityReturnType } from "../../actions/wallet/patchEntity"
28
28
  import { patchEntity } from "../../actions/wallet/patchEntity"
29
29
  import type { TxParams } from "../../types"
@@ -50,12 +50,12 @@ export type WalletArkivActions<
50
50
  /**
51
51
  * Creates a new entity.
52
52
  *
53
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/createEntity
54
- * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
53
+ * - JSON-RPC Methods: `eth_sendRawTransaction`
55
54
  *
56
55
  * @param data - The entity creation parameters
57
56
  * @param txParams - Optional transaction parameters
58
- * @returns The created entity with transaction hash
57
+ * @returns The new entity's key, the transaction hash, and the block it is expected to
58
+ * expire at. {@link CreateEntityReturnType}
59
59
  *
60
60
  * @throws {InvalidExpiryError} If the expiry exceeds a protocol bound or would leave the
61
61
  * entity dead on arrival.
@@ -67,8 +67,10 @@ export type WalletArkivActions<
67
67
  * import { i32 } from "@arkiv-network/sdk/attr"
68
68
  * import { cheesecake } from "@arkiv-network/sdk/chains"
69
69
  * import { http } from "viem"
70
+ * import { privateKeyToAccount } from "viem/accounts"
70
71
  *
71
72
  * const client = createWalletClient({
73
+ * account: privateKeyToAccount("0x..."),
72
74
  * chain: cheesecake,
73
75
  * transport: http(),
74
76
  * })
@@ -88,12 +90,11 @@ export type WalletArkivActions<
88
90
  * Applies a patch to the entity with the given key: sets some fields, unsets others, and leaves
89
91
  * everything it does not name alone.
90
92
  *
91
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/patchEntity
92
- * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
93
+ * - JSON-RPC Methods: `eth_sendRawTransaction`
93
94
  *
94
95
  * @param data - The entity key and the mutations to apply
95
96
  * @param txParams - Optional transaction parameters
96
- * @returns The entity key and the transaction hash
97
+ * @returns The patched entity's key and the transaction hash. {@link PatchEntityReturnType}
97
98
  *
98
99
  * @throws {EmptyPatchError} If the patch has nothing to apply.
99
100
  * @throws {ConflictingMutationError} If a name appears in both `set` and `unset`.
@@ -105,8 +106,10 @@ export type WalletArkivActions<
105
106
  * import { i32 } from "@arkiv-network/sdk/attr"
106
107
  * import { cheesecake } from "@arkiv-network/sdk/chains"
107
108
  * import { http } from "viem"
109
+ * import { privateKeyToAccount } from "viem/accounts"
108
110
  *
109
111
  * const client = createWalletClient({
112
+ * account: privateKeyToAccount("0x..."),
110
113
  * chain: cheesecake,
111
114
  * transport: http(),
112
115
  * })
@@ -126,19 +129,20 @@ export type WalletArkivActions<
126
129
  /**
127
130
  * Deletes the entity with the given key.
128
131
  *
129
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/deleteEntity
130
- * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
132
+ * - JSON-RPC Methods: `eth_sendRawTransaction`
131
133
  *
132
134
  * @param data - The entity deletion parameters
133
135
  * @param txParams - Optional transaction parameters
134
- * @returns The deleted entity with transaction hash
136
+ * @returns The deleted entity's key and the transaction hash. {@link DeleteEntityReturnType}
135
137
  *
136
138
  * @example
137
139
  * import { createWalletClient } from "@arkiv-network/sdk"
138
140
  * import { cheesecake } from "@arkiv-network/sdk/chains"
139
141
  * import { http } from "viem"
142
+ * import { privateKeyToAccount } from "viem/accounts"
140
143
  *
141
144
  * const client = createWalletClient({
145
+ * account: privateKeyToAccount("0x..."),
142
146
  * chain: cheesecake,
143
147
  * transport: http(),
144
148
  * })
@@ -157,12 +161,12 @@ export type WalletArkivActions<
157
161
  * than adding to what the entity has left, and `atBlock` / `atDate` pin an absolute deadline.
158
162
  * The engine rejects an extension that would not move the expiry later.
159
163
  *
160
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/extendEntity
161
- * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
164
+ * - JSON-RPC Methods: `eth_sendRawTransaction`
162
165
  *
163
166
  * @param data - The entity key and its new lifetime
164
167
  * @param txParams - Optional transaction parameters
165
- * @returns The entity key, transaction hash, and the block it is now expected to expire at
168
+ * @returns The entity's key, the transaction hash, and the block it is now expected to expire
169
+ * at. {@link ExtendEntityReturnType}
166
170
  *
167
171
  * @throws {InvalidExpiryError} If the expiry is malformed, exceeds a protocol bound, or would
168
172
  * leave the entity dead on arrival.
@@ -171,8 +175,10 @@ export type WalletArkivActions<
171
175
  * import { createWalletClient, ExpirationTime } from "@arkiv-network/sdk"
172
176
  * import { cheesecake } from "@arkiv-network/sdk/chains"
173
177
  * import { http } from "viem"
178
+ * import { privateKeyToAccount } from "viem/accounts"
174
179
  *
175
180
  * const client = createWalletClient({
181
+ * account: privateKeyToAccount("0x..."),
176
182
  * chain: cheesecake,
177
183
  * transport: http(),
178
184
  * })
@@ -187,14 +193,13 @@ export type WalletArkivActions<
187
193
  ) => Promise<ExtendEntityReturnType>
188
194
 
189
195
  /**
190
- * Changes the ownership of the entity with the given address.
196
+ * Hands the entity with the given key to a new owner.
191
197
  *
192
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/changeOwnership
193
- * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
198
+ * - JSON-RPC Methods: `eth_sendRawTransaction`
194
199
  *
195
200
  * @param data - The ownership change parameters
196
201
  * @param txParams - Optional transaction parameters
197
- * @returns The entity with updated ownership and transaction hash
202
+ * @returns The entity's key and the transaction hash. {@link ChangeOwnershipReturnType}
198
203
  */
199
204
  changeOwnership: (
200
205
  data: ChangeOwnershipParameters,
@@ -202,17 +207,18 @@ export type WalletArkivActions<
202
207
  ) => Promise<ChangeOwnershipReturnType>
203
208
 
204
209
  /**
205
- * Mutates the entities with the given keys.
210
+ * Applies a batch of entity operations — creates, patches, deletes, extensions and ownership
211
+ * transfers — in one transaction.
206
212
  *
207
- * - Docs: https://docs.arkiv.network/ts-sdk/actions/wallet/mutateEntities
208
- * - JSON-RPC Methods: [`eth_sendRawTransaction`](https://docs.arkiv.network/dev/json-rpc-api/#mutateEntities)
213
+ * - JSON-RPC Methods: `eth_sendRawTransaction`
209
214
  *
210
215
  * Every operation lands in one transaction, so the whole batch applies or none of it does.
211
216
  * At least one operation is required.
212
217
  *
213
- * @param data - The mutation parameters (creates, patches, deletes, extensions, ownershipChanges)
218
+ * @param data - The batch parameters (creates, patches, deletes, extensions, ownershipChanges)
214
219
  * @param txParams - Optional transaction parameters
215
- * @returns The mutation result with transaction hash
220
+ * @returns The transaction hash, plus the keys touched by each kind of operation.
221
+ * {@link ExecuteBatchReturnType}
216
222
  *
217
223
  * @throws {InvalidExpiryError} If an expiry exceeds a protocol bound or would leave the entity
218
224
  * dead on arrival.
@@ -222,12 +228,14 @@ export type WalletArkivActions<
222
228
  * import { createWalletClient, ExpirationTime, jsonToPayload } from "@arkiv-network/sdk"
223
229
  * import { cheesecake } from "@arkiv-network/sdk/chains"
224
230
  * import { http } from "viem"
231
+ * import { privateKeyToAccount } from "viem/accounts"
225
232
  *
226
233
  * const client = createWalletClient({
234
+ * account: privateKeyToAccount("0x..."),
227
235
  * chain: cheesecake,
228
236
  * transport: http(),
229
237
  * })
230
- * const { txHash, createdEntities } = await client.mutateEntities({
238
+ * const { txHash, createdEntities } = await client.executeBatch({
231
239
  * creates: [{
232
240
  * payload: jsonToPayload({ entityType: "testType", entityId: "testId" }),
233
241
  * contentType: "application/json",
@@ -243,10 +251,10 @@ export type WalletArkivActions<
243
251
  * ownershipChanges: [{ entityKey: keyToHandOver, newOwner }],
244
252
  * })
245
253
  */
246
- mutateEntities: (
247
- data: MutateEntitiesParameters,
254
+ executeBatch: (
255
+ data: ExecuteBatchParameters,
248
256
  txParams?: TxParams,
249
- ) => Promise<MutateEntitiesReturnType>
257
+ ) => Promise<ExecuteBatchReturnType>
250
258
  }
251
259
 
252
260
  export function walletArkivActions<
@@ -265,7 +273,7 @@ export function walletArkivActions<
265
273
  extendEntity(client, data, txParams),
266
274
  changeOwnership: (data: ChangeOwnershipParameters, txParams?: TxParams) =>
267
275
  changeOwnership(client, data, txParams),
268
- mutateEntities: (data: MutateEntitiesParameters, txParams?: TxParams) =>
269
- mutateEntities(client, data, txParams),
276
+ executeBatch: (data: ExecuteBatchParameters, txParams?: TxParams) =>
277
+ executeBatch(client, data, txParams),
270
278
  }
271
279
  }
@@ -4,7 +4,7 @@ import { encodeAttributes } from "../attr/attributes"
4
4
  import { dec, i32, str, u256 } from "../attr/values"
5
5
  import { InvalidCreationFlagsError } from "./errors"
6
6
  import { decodeCreationFlags, encodeCreationFlags } from "./flags"
7
- import { MAX_SALT, predictEntityKey, randomSalt, validateSalt } from "./key"
7
+ import { MAX_SALT, NO_SALT, predictEntityKey, randomSalt, resolveSalt, validateSalt } from "./key"
8
8
  import { createOperation } from "./operations"
9
9
  import { OperationType } from "./params"
10
10
 
@@ -73,6 +73,18 @@ describe("salt", () => {
73
73
  expect(() => validateSalt(MAX_SALT + 1n)).toThrow(/uint128/)
74
74
  expect(() => validateSalt(-1n)).toThrow(/uint128/)
75
75
  })
76
+
77
+ it("resolves NO_SALT to the zero, and passes anything else through", () => {
78
+ expect(resolveSalt(NO_SALT)).toBe(0n)
79
+ expect(resolveSalt(0n)).toBe(0n)
80
+ expect(resolveSalt(42n)).toBe(42n)
81
+ expect(() => resolveSalt(MAX_SALT + 1n)).toThrow(/uint128/)
82
+ })
83
+
84
+ it("reports a salt it cannot use without choking on a symbol", () => {
85
+ // @ts-expect-error
86
+ expect(() => validateSalt(Symbol("nope"))).toThrow(/Invalid salt/)
87
+ })
76
88
  })
77
89
 
78
90
  describe("predictEntityKey", () => {
@@ -86,6 +98,13 @@ describe("predictEntityKey", () => {
86
98
  ).not.toBe(predictEntityKey(base))
87
99
  })
88
100
 
101
+ it("derives the same key from NO_SALT as from an explicit zero", () => {
102
+ const base = { owner: OWNER, nonce: 3n, chainId: 1 } as const
103
+ expect(predictEntityKey({ ...base, salt: NO_SALT })).toBe(
104
+ predictEntityKey({ ...base, salt: 0n }),
105
+ )
106
+ })
107
+
89
108
  it("separates chains through the domain", () => {
90
109
  const base = { owner: OWNER, nonce: 0n, salt: 0n } as const
91
110
  expect(predictEntityKey({ ...base, chainId: 1 })).not.toBe(
@@ -16,10 +16,11 @@ export class InvalidCreationFlagsError extends Error {
16
16
 
17
17
  /** A key salt was outside the `uint128` the wire carries. */
18
18
  export class InvalidSaltError extends Error {
19
- constructor(salt: bigint) {
19
+ constructor(salt: unknown) {
20
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.`,
21
+ `Invalid salt ${String(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 NO_SALT for a key derived ` +
23
+ `from the owner and nonce alone.`,
23
24
  )
24
25
  this.name = "InvalidSaltError"
25
26
  }
@@ -1,4 +1,5 @@
1
1
  import { BLOCK_TIME } from "../consts"
2
+ import type { ExpirationTime } from "../utils/expirationTime"
2
3
  import { InvalidExpiryError } from "./errors"
3
4
  import { MAX_EXPIRES_AT } from "./params"
4
5