@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.
- package/README.md +29 -16
- package/dist/attr/index.cjs +34 -0
- package/dist/attr/index.d.cts +2 -0
- package/dist/attr/index.d.ts +2 -0
- package/dist/attr/index.js +3 -0
- package/dist/attr-BDZ-APg5.js +809 -0
- package/dist/attr-BDZ-APg5.js.map +1 -0
- package/dist/attr-BvxHN99W.cjs +1029 -0
- package/dist/chains/index.cjs +2 -3
- package/dist/chains/index.d.cts +22 -60
- package/dist/chains/index.d.cts.map +1 -1
- package/dist/chains/index.d.ts +22 -60
- package/dist/chains/index.d.ts.map +1 -1
- package/dist/chains/index.js +2 -2
- package/dist/chains-BJrZs6sG.js +57 -0
- package/dist/chains-BJrZs6sG.js.map +1 -0
- package/dist/chains-BYRTTipa.cjs +73 -0
- package/dist/changeOwnership-B7bEgla4.d.cts +509 -0
- package/dist/changeOwnership-B7bEgla4.d.cts.map +1 -0
- package/dist/changeOwnership-DwgtdSFc.d.ts +509 -0
- package/dist/changeOwnership-DwgtdSFc.d.ts.map +1 -0
- package/dist/entity/index.cjs +19 -0
- package/dist/entity/index.d.cts +4 -0
- package/dist/entity/index.d.ts +4 -0
- package/dist/entity/index.js +6 -0
- package/dist/entity-B0t6sN7F.cjs +71 -0
- package/dist/entity-BIp_df9u.js +67 -0
- package/dist/entity-BIp_df9u.js.map +1 -0
- package/dist/entity-CFhbAv0R.js +70 -0
- package/dist/entity-CFhbAv0R.js.map +1 -0
- package/dist/entity-wfBnZd91.cjs +98 -0
- package/dist/errors-D-eJkGVc.cjs +56 -0
- package/dist/errors-DGB8AgT2.js +33 -0
- package/dist/errors-DGB8AgT2.js.map +1 -0
- package/dist/expiry-BUYIXTGb.cjs +135 -0
- package/dist/expiry-BxOlvVWC.d.ts +79 -0
- package/dist/expiry-BxOlvVWC.d.ts.map +1 -0
- package/dist/expiry-DGRzrSan.js +95 -0
- package/dist/expiry-DGRzrSan.js.map +1 -0
- package/dist/expiry-DRV29F1r.d.cts +79 -0
- package/dist/expiry-DRV29F1r.d.cts.map +1 -0
- package/dist/flags-0Vl5rrHq.d.cts +41 -0
- package/dist/flags-0Vl5rrHq.d.cts.map +1 -0
- package/dist/flags-Ct8k0gpB.cjs +43 -0
- package/dist/flags-ZtlvOIIk.js +33 -0
- package/dist/flags-ZtlvOIIk.js.map +1 -0
- package/dist/flags-wmcMoAnN.d.ts +41 -0
- package/dist/flags-wmcMoAnN.d.ts.map +1 -0
- package/dist/index-Cgw1u5aY.d.cts +278 -0
- package/dist/index-Cgw1u5aY.d.cts.map +1 -0
- package/dist/index-ClgIuxJ8.d.ts +159 -0
- package/dist/index-ClgIuxJ8.d.ts.map +1 -0
- package/dist/index-D0CmtiWT.d.cts +159 -0
- package/dist/index-D0CmtiWT.d.cts.map +1 -0
- package/dist/index-D8LZt-3J.d.cts +417 -0
- package/dist/index-D8LZt-3J.d.cts.map +1 -0
- package/dist/index-DVYFaXNV.d.ts +417 -0
- package/dist/index-DVYFaXNV.d.ts.map +1 -0
- package/dist/index-VCqgn29D.d.ts +278 -0
- package/dist/index-VCqgn29D.d.ts.map +1 -0
- package/dist/index.cjs +676 -375
- package/dist/index.d.cts +247 -208
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +247 -208
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +626 -368
- package/dist/index.js.map +1 -1
- package/dist/payload-Bnw1hrvb.cjs +83 -0
- package/dist/payload-CIxSn_DW.js +67 -0
- package/dist/payload-CIxSn_DW.js.map +1 -0
- package/dist/payload-Dgx3PQeK.d.ts +94 -0
- package/dist/payload-Dgx3PQeK.d.ts.map +1 -0
- package/dist/payload-P0nWyjLg.d.cts +94 -0
- package/dist/payload-P0nWyjLg.d.cts.map +1 -0
- package/dist/query/index.cjs +24 -150
- package/dist/query/index.d.cts +5 -3
- package/dist/query/index.d.ts +5 -3
- package/dist/query/index.js +6 -138
- package/dist/queryBuilder-BXDvX1K_.cjs +1123 -0
- package/dist/queryBuilder-CbLL4pG5.js +963 -0
- package/dist/queryBuilder-CbLL4pG5.js.map +1 -0
- package/dist/selection-BvK6yX0g.d.cts +433 -0
- package/dist/selection-BvK6yX0g.d.cts.map +1 -0
- package/dist/selection-fpmI3ADQ.d.ts +433 -0
- package/dist/selection-fpmI3ADQ.d.ts.map +1 -0
- package/dist/types/index.cjs +3 -3
- package/dist/types/index.d.cts +7 -2
- package/dist/types/index.d.ts +7 -2
- package/dist/types/index.js +3 -3
- package/dist/utils/index.cjs +4 -17
- package/dist/utils/index.d.cts +3 -17
- package/dist/utils/index.d.ts +3 -17
- package/dist/utils/index.js +4 -18
- package/package.json +26 -5
- package/src/actions/public/getEntity.ts +27 -43
- package/src/actions/public/getEntityNonce.ts +30 -0
- package/src/actions/public/predictEntityKeys.test.ts +143 -0
- package/src/actions/public/predictEntityKeys.ts +138 -0
- package/src/actions/public/query.ts +33 -69
- package/src/actions/public/watchEntityEvents.test.ts +414 -0
- package/src/actions/public/watchEntityEvents.ts +225 -0
- package/src/actions/wallet/changeOwnership.ts +2 -2
- package/src/actions/wallet/createEntity.ts +99 -24
- package/src/actions/wallet/extendEntity.ts +37 -13
- package/src/actions/wallet/mutateEntities.test.ts +76 -0
- package/src/actions/wallet/mutateEntities.ts +19 -21
- package/src/actions/wallet/patchEntity.ts +90 -0
- package/src/attr/attributes.test.ts +120 -0
- package/src/attr/attributes.ts +190 -0
- package/src/attr/codec.test.ts +162 -0
- package/src/attr/codec.ts +220 -0
- package/src/attr/decimal.ts +86 -0
- package/src/attr/errors.ts +145 -0
- package/src/attr/index.ts +50 -0
- package/src/attr/names.ts +106 -0
- package/src/attr/types.ts +150 -0
- package/src/attr/values.test.ts +271 -0
- package/src/attr/values.ts +502 -0
- package/src/chains/cheesecake.ts +22 -0
- package/src/chains/index.ts +1 -2
- package/src/clients/createPublicClient.test.ts +3 -5
- package/src/clients/createPublicClient.ts +5 -4
- package/src/clients/createWalletClient.test.ts +2 -2
- package/src/clients/createWalletClient.ts +13 -7
- package/src/clients/decorators/arkivPublic.ts +160 -139
- package/src/clients/decorators/arkivWallet.ts +86 -94
- package/src/entity/create.test.ts +184 -0
- package/src/entity/errors.ts +38 -0
- package/src/entity/events.ts +16 -0
- package/src/entity/expiry.test.ts +195 -0
- package/src/entity/expiry.ts +181 -0
- package/src/entity/flags.ts +65 -0
- package/src/entity/index.ts +14 -0
- package/src/entity/key.ts +68 -0
- package/src/entity/operations.ts +134 -0
- package/src/entity/params.ts +15 -0
- package/src/errors.ts +26 -49
- package/src/index.ts +6 -0
- package/src/query/engine.ts +59 -139
- package/src/query/errors.ts +151 -0
- package/src/query/expression.test.ts +286 -0
- package/src/query/expression.ts +493 -0
- package/src/query/index.ts +40 -4
- package/src/query/injection.test.ts +151 -0
- package/src/query/queryBuilder.test.ts +322 -637
- package/src/query/queryBuilder.ts +126 -359
- package/src/query/queryResult.ts +54 -51
- package/src/query/selection.test.ts +48 -98
- package/src/query/selection.ts +77 -65
- package/src/types/entity.test.ts +44 -155
- package/src/types/entity.ts +60 -52
- package/src/types/events.ts +59 -16
- package/src/types/index.ts +20 -17
- package/src/types/rpcSchema.ts +69 -37
- package/src/utils/arkivTransactions.test.ts +600 -155
- package/src/utils/arkivTransactions.ts +181 -190
- package/src/utils/chains.test.ts +21 -24
- package/src/utils/compression.ts +1 -1
- package/src/utils/entities.test.ts +62 -0
- package/src/utils/entities.ts +109 -37
- package/src/utils/expirationTime.ts +114 -10
- package/src/utils/logger.ts +2 -2
- package/src/utils/revert.test.ts +99 -0
- package/src/utils/revert.ts +145 -0
- package/dist/chains-BifaY04V.cjs +0 -108
- package/dist/chains-CrtkVbBh.js +0 -86
- package/dist/chains-CrtkVbBh.js.map +0 -1
- package/dist/consts-Bz0LZQZY.js +0 -7
- package/dist/consts-Bz0LZQZY.js.map +0 -1
- package/dist/consts-CxT1-RXU.cjs +0 -18
- package/dist/entity-BhSXaB23.cjs +0 -81
- package/dist/entity-CN41IOrq.d.cts +0 -401
- package/dist/entity-CN41IOrq.d.cts.map +0 -1
- package/dist/entity-DGfVyB-a.js +0 -71
- package/dist/entity-DGfVyB-a.js.map +0 -1
- package/dist/entity-NnEVlePN.d.ts +0 -401
- package/dist/entity-NnEVlePN.d.ts.map +0 -1
- package/dist/index-ClkpS6s_.d.ts +0 -481
- package/dist/index-ClkpS6s_.d.ts.map +0 -1
- package/dist/index-w4qop0Pe.d.cts +0 -481
- package/dist/index-w4qop0Pe.d.cts.map +0 -1
- package/dist/payload-BpNUPOnY.js +0 -13
- package/dist/payload-BpNUPOnY.js.map +0 -1
- package/dist/payload-C2KHOUoj.d.ts +0 -6
- package/dist/payload-C2KHOUoj.d.ts.map +0 -1
- package/dist/payload-CJN8fTvX.d.cts +0 -6
- package/dist/payload-CJN8fTvX.d.cts.map +0 -1
- package/dist/payload-D8YLKcJj.cjs +0 -23
- package/dist/query/index.js.map +0 -1
- package/dist/queryBuilder-BE_XhLLa.js +0 -735
- package/dist/queryBuilder-BE_XhLLa.js.map +0 -1
- package/dist/queryBuilder-J37buGCi.cjs +0 -865
- package/dist/utils/index.d.cts.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/src/actions/public/buildQuery.ts +0 -10
- package/src/actions/public/subscribeEntityEvents.ts +0 -93
- package/src/actions/wallet/updateEntity.ts +0 -59
- package/src/chains/braga.ts +0 -26
- package/src/chains/kaolin.ts +0 -29
- package/src/query/engine.test.ts +0 -571
- package/src/query/predicate.test.ts +0 -461
- package/src/query/predicate.ts +0 -144
- package/src/query/queryResult.test.ts +0 -270
- package/src/query/selectQueryBuilder.test.ts +0 -213
- package/src/types/attributes.ts +0 -28
- package/src/utils/validation.test.ts +0 -106
- package/src/utils/validation.ts +0 -80
- /package/dist/{types-Ci4BRClw.js → types-Bx1EKe0v.js} +0 -0
- /package/dist/{types-a8gm_IaQ.cjs → types-CKPddlfS.cjs} +0 -0
|
@@ -1,50 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Hex } from "viem"
|
|
2
|
+
import { key } from "../../attr"
|
|
2
3
|
import type { ArkivClient } from "../../clients/baseClient"
|
|
3
4
|
import { NoEntityFoundError } from "../../errors"
|
|
4
|
-
import {
|
|
5
|
+
import { runQuery } from "../../query/engine"
|
|
6
|
+
import { eq } from "../../query/expression"
|
|
7
|
+
import { type FullEntity, toRpcSelect } from "../../query/selection"
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const result = await client.request({
|
|
24
|
-
method: "arkiv_query",
|
|
25
|
-
params: [
|
|
26
|
-
`$key = ${hexKey}`,
|
|
27
|
-
{
|
|
28
|
-
includeData: {
|
|
29
|
-
key: true,
|
|
30
|
-
attributes: true,
|
|
31
|
-
payload: true,
|
|
32
|
-
contentType: true,
|
|
33
|
-
expiration: true,
|
|
34
|
-
owner: true,
|
|
35
|
-
creator: true,
|
|
36
|
-
createdAtBlock: true,
|
|
37
|
-
lastModifiedAtBlock: true,
|
|
38
|
-
transactionIndexInBlock: true,
|
|
39
|
-
operationIndexInTransaction: true,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
],
|
|
9
|
+
/**
|
|
10
|
+
* Reads one entity by key, with every field selected.
|
|
11
|
+
*
|
|
12
|
+
* Returns a {@link FullEntity} rather than an {@link Entity}: the selection is fixed at `"*"`, so
|
|
13
|
+
* every field is populated and none of them needs a `?` at the call site. The bare `Entity` type
|
|
14
|
+
* has every field optional because a `select()` result carries only what it asked for.
|
|
15
|
+
*
|
|
16
|
+
* @throws {InvalidValueError} If the key is not 32 bytes.
|
|
17
|
+
* @throws {NoEntityFoundError} If no live entity has that key — it never existed, or it was
|
|
18
|
+
* deleted or has expired.
|
|
19
|
+
*/
|
|
20
|
+
export async function getEntity(client: ArkivClient, entityKey: Hex): Promise<FullEntity> {
|
|
21
|
+
const { entities } = await runQuery(client, {
|
|
22
|
+
query: eq("$key", key(entityKey)).toString(),
|
|
23
|
+
select: toRpcSelect("*"),
|
|
24
|
+
limit: 1,
|
|
43
25
|
})
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
27
|
+
const entity = entities[0]
|
|
28
|
+
if (entity === undefined) {
|
|
29
|
+
throw new NoEntityFoundError(entityKey)
|
|
47
30
|
}
|
|
48
|
-
|
|
49
|
-
|
|
31
|
+
// Every field was selected, so the runtime Entity carries them all; the cast is the same
|
|
32
|
+
// narrowing `client.select("*")` gets from its own inference.
|
|
33
|
+
return entity as unknown as FullEntity
|
|
50
34
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Address } from "viem"
|
|
2
|
+
import { readContract } from "viem/actions"
|
|
3
|
+
import type { ArkivClient } from "../../clients/baseClient"
|
|
4
|
+
import { ARKIV_ADDRESS } from "../../consts"
|
|
5
|
+
import { ENTITY_NONCE_ABI } from "../../entity/operations"
|
|
6
|
+
import { getLogger } from "../../utils/logger"
|
|
7
|
+
|
|
8
|
+
const logger = getLogger("actions:public:get-entity-nonce")
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Reads an owner's entity-minting nonce — how many entities that account has created.
|
|
12
|
+
*
|
|
13
|
+
* This is not the account's transaction nonce. The engine keeps a separate counter per creator and
|
|
14
|
+
* mixes it into every key it mints, which is what makes two identical creates from one account land
|
|
15
|
+
* on different keys.
|
|
16
|
+
*
|
|
17
|
+
* @param owner - The account whose nonce to read.
|
|
18
|
+
* @returns The number of entities `owner` has created so far, which is the nonce its next create
|
|
19
|
+
* will use.
|
|
20
|
+
*/
|
|
21
|
+
export async function getEntityNonce(client: ArkivClient, owner: Address): Promise<bigint> {
|
|
22
|
+
const nonce = await readContract(client, {
|
|
23
|
+
address: ARKIV_ADDRESS,
|
|
24
|
+
abi: ENTITY_NONCE_ABI,
|
|
25
|
+
functionName: "entityNonce",
|
|
26
|
+
args: [owner],
|
|
27
|
+
})
|
|
28
|
+
logger("entity nonce of %s is %s", owner, nonce)
|
|
29
|
+
return nonce
|
|
30
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "bun:test"
|
|
2
|
+
import { encodeFunctionResult, type Hex } from "viem"
|
|
3
|
+
import type { ArkivClient } from "../../clients/baseClient"
|
|
4
|
+
import { ARKIV_ADDRESS } from "../../consts"
|
|
5
|
+
import { predictEntityKey } from "../../entity/key"
|
|
6
|
+
import { ENTITY_NONCE_ABI } from "../../entity/operations"
|
|
7
|
+
import { getEntityNonce } from "./getEntityNonce"
|
|
8
|
+
import { predictEntityKeys } from "./predictEntityKeys"
|
|
9
|
+
|
|
10
|
+
const OWNER = "0x1111111111111111111111111111111111111111" as const
|
|
11
|
+
const CHAIN_ID = 1337
|
|
12
|
+
|
|
13
|
+
/** A client whose `eth_call` answers one nonce, and records what it was asked. */
|
|
14
|
+
function makeClient(nonce: bigint, options: { chain?: boolean } = {}) {
|
|
15
|
+
const request = vi.fn(async ({ method }: { method: string }) => {
|
|
16
|
+
if (method === "eth_call") {
|
|
17
|
+
return encodeFunctionResult({
|
|
18
|
+
abi: ENTITY_NONCE_ABI,
|
|
19
|
+
functionName: "entityNonce",
|
|
20
|
+
result: nonce,
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
if (method === "eth_chainId") return `0x${CHAIN_ID.toString(16)}`
|
|
24
|
+
throw new Error(`unexpected request: ${method}`)
|
|
25
|
+
})
|
|
26
|
+
const client = {
|
|
27
|
+
...(options.chain === false ? {} : { chain: { id: CHAIN_ID } }),
|
|
28
|
+
request,
|
|
29
|
+
} as unknown as ArkivClient
|
|
30
|
+
return { client, request }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The `eth_call` the client was asked to make. */
|
|
34
|
+
function ethCall(request: ReturnType<typeof vi.fn>) {
|
|
35
|
+
const call = request.mock.calls.find(([body]) => body.method === "eth_call")
|
|
36
|
+
return call?.[0].params[0] as { to: Hex; data: Hex }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("getEntityNonce", () => {
|
|
40
|
+
it("reads the owner's minting nonce off the registry", async () => {
|
|
41
|
+
const { client, request } = makeClient(7n)
|
|
42
|
+
|
|
43
|
+
expect(await getEntityNonce(client, OWNER)).toBe(7n)
|
|
44
|
+
|
|
45
|
+
// The selector is part of the node's ABI: `cast sig 'entityNonce(address)'` is 0x36917bfd.
|
|
46
|
+
const { to, data } = ethCall(request)
|
|
47
|
+
expect(to).toBe(ARKIV_ADDRESS)
|
|
48
|
+
expect(data).toBe(`0x36917bfd000000000000000000000000${OWNER.slice(2)}`)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("reports a fresh account as zero rather than as an absence", async () => {
|
|
52
|
+
const { client } = makeClient(0n)
|
|
53
|
+
expect(await getEntityNonce(client, OWNER)).toBe(0n)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe("predictEntityKeys", () => {
|
|
58
|
+
it("derives one pair per create, walking the nonce forward", async () => {
|
|
59
|
+
const { client } = makeClient(7n)
|
|
60
|
+
|
|
61
|
+
const pairs = await predictEntityKeys(client, { owner: OWNER, salts: [1n, 2n, 3n] })
|
|
62
|
+
|
|
63
|
+
expect(pairs).toEqual([
|
|
64
|
+
{ salt: 1n, key: predictEntityKey({ owner: OWNER, nonce: 7n, salt: 1n, chainId: CHAIN_ID }) },
|
|
65
|
+
{ salt: 2n, key: predictEntityKey({ owner: OWNER, nonce: 8n, salt: 2n, chainId: CHAIN_ID }) },
|
|
66
|
+
{ salt: 3n, key: predictEntityKey({ owner: OWNER, nonce: 9n, salt: 3n, chainId: CHAIN_ID }) },
|
|
67
|
+
])
|
|
68
|
+
// Distinct keys, which is the whole point of the nonce.
|
|
69
|
+
expect(new Set(pairs.map((pair) => pair.key)).size).toBe(3)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it("makes up the salts when only asked for a count, and pairs each with its key", async () => {
|
|
73
|
+
const { client } = makeClient(0n)
|
|
74
|
+
|
|
75
|
+
// A literal count is a tuple, so this destructures without a cast — that it compiles is the
|
|
76
|
+
// assertion.
|
|
77
|
+
const [first, second] = await predictEntityKeys(client, { owner: OWNER, count: 2 })
|
|
78
|
+
|
|
79
|
+
// Random salts, so the only thing worth asserting is that the keys follow from them.
|
|
80
|
+
expect(first.key).toBe(
|
|
81
|
+
predictEntityKey({ owner: OWNER, nonce: 0n, salt: first.salt, chainId: CHAIN_ID }),
|
|
82
|
+
)
|
|
83
|
+
expect(second.key).toBe(
|
|
84
|
+
predictEntityKey({ owner: OWNER, nonce: 1n, salt: second.salt, chainId: CHAIN_ID }),
|
|
85
|
+
)
|
|
86
|
+
expect(first.salt).not.toBe(second.salt)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("keeps a runtime count a plain array rather than guessing its length", async () => {
|
|
90
|
+
const { client } = makeClient(0n)
|
|
91
|
+
const count: number = 3
|
|
92
|
+
|
|
93
|
+
const pairs = await predictEntityKeys(client, { owner: OWNER, count })
|
|
94
|
+
|
|
95
|
+
expect(pairs).toHaveLength(3)
|
|
96
|
+
expect(pairs.map((pair) => pair.key)).toEqual(
|
|
97
|
+
pairs.map((pair, index) =>
|
|
98
|
+
predictEntityKey({
|
|
99
|
+
owner: OWNER,
|
|
100
|
+
nonce: BigInt(index),
|
|
101
|
+
salt: pair.salt,
|
|
102
|
+
chainId: CHAIN_ID,
|
|
103
|
+
}),
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it("prefers the salts it was given over the count", async () => {
|
|
109
|
+
const { client } = makeClient(0n)
|
|
110
|
+
const pairs = await predictEntityKeys(client, { owner: OWNER, count: 5, salts: [9n] })
|
|
111
|
+
expect(pairs).toEqual([
|
|
112
|
+
{ salt: 9n, key: predictEntityKey({ owner: OWNER, nonce: 0n, salt: 9n, chainId: CHAIN_ID }) },
|
|
113
|
+
])
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it("asks the chain for its id when the client was built without one", async () => {
|
|
117
|
+
const { client, request } = makeClient(0n, { chain: false })
|
|
118
|
+
|
|
119
|
+
const [only] = await predictEntityKeys(client, { owner: OWNER, salts: [1n] })
|
|
120
|
+
|
|
121
|
+
expect(request.mock.calls.some(([body]) => body.method === "eth_chainId")).toBe(true)
|
|
122
|
+
expect(only.key).toBe(
|
|
123
|
+
predictEntityKey({ owner: OWNER, nonce: 0n, salt: 1n, chainId: CHAIN_ID }),
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it("refuses a request that does not say how many keys it wants", async () => {
|
|
128
|
+
const { client } = makeClient(0n)
|
|
129
|
+
await expect(predictEntityKeys(client, { owner: OWNER })).rejects.toThrow(/`count` or `salts`/)
|
|
130
|
+
await expect(predictEntityKeys(client, { owner: OWNER, salts: [] })).rejects.toThrow(/empty/)
|
|
131
|
+
await expect(predictEntityKeys(client, { owner: OWNER, count: 0 })).rejects.toThrow(
|
|
132
|
+
/positive whole number/,
|
|
133
|
+
)
|
|
134
|
+
await expect(predictEntityKeys(client, { owner: OWNER, count: 1.5 })).rejects.toThrow(
|
|
135
|
+
/positive whole number/,
|
|
136
|
+
)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it("rejects a salt too wide for the uint128 the engine mixes in", async () => {
|
|
140
|
+
const { client } = makeClient(0n)
|
|
141
|
+
await expect(predictEntityKeys(client, { owner: OWNER, salts: [2n ** 128n] })).rejects.toThrow()
|
|
142
|
+
})
|
|
143
|
+
})
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Address, Hex } from "viem"
|
|
2
|
+
import { getChainId } from "viem/actions"
|
|
3
|
+
import type { ArkivClient } from "../../clients/baseClient"
|
|
4
|
+
import { predictEntityKey, randomSalt, validateSalt } from "../../entity/key"
|
|
5
|
+
import { getEntityNonce } from "./getEntityNonce"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A key a create will be given, together with the salt that mints it.
|
|
9
|
+
*
|
|
10
|
+
* The two travel as a pair because neither is any use alone: a create defaults to a *random* salt,
|
|
11
|
+
* so the key only comes out as predicted if that same create carries this salt.
|
|
12
|
+
*/
|
|
13
|
+
export type PredictedEntityKey = {
|
|
14
|
+
/** The key the create will be given. */
|
|
15
|
+
key: Hex
|
|
16
|
+
/** The salt the create must carry for its key to come out as {@link PredictedEntityKey.key}. */
|
|
17
|
+
salt: bigint
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Parameters for {@link predictEntityKeys}. */
|
|
21
|
+
export type PredictEntityKeysParameters<
|
|
22
|
+
TCount extends number = number,
|
|
23
|
+
TSalts extends readonly bigint[] | undefined = readonly bigint[] | undefined,
|
|
24
|
+
> = {
|
|
25
|
+
/** The account that will sign the creates. Its nonce is what the keys are derived from. */
|
|
26
|
+
owner: Address
|
|
27
|
+
/**
|
|
28
|
+
* How many creates the batch will carry. Ignored when `salts` is given, which already says how
|
|
29
|
+
* many there are.
|
|
30
|
+
*
|
|
31
|
+
* A literal count comes back as a fixed-length tuple, so `count: 2` destructures into two named
|
|
32
|
+
* pairs without a cast.
|
|
33
|
+
*/
|
|
34
|
+
count?: TCount | undefined
|
|
35
|
+
/**
|
|
36
|
+
* The salt each create will carry, in batch order. Defaults to `count` fresh random salts —
|
|
37
|
+
* either way each key comes back paired with the salt that mints it.
|
|
38
|
+
*/
|
|
39
|
+
salts?: TSalts
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The keys a batch would mint, in batch order, each paired with its salt.
|
|
44
|
+
*
|
|
45
|
+
* The length follows the request whenever it is known statically: a literal `count`, or a `salts`
|
|
46
|
+
* tuple, gives a fixed-length tuple; a count computed at runtime gives a plain array.
|
|
47
|
+
*
|
|
48
|
+
* `TSalts` is wrapped in a tuple to stop the conditional distributing over it. Left naked, the
|
|
49
|
+
* `readonly bigint[] | undefined` default would take both branches and union a tuple with an array,
|
|
50
|
+
* which widens the length straight back to `number`.
|
|
51
|
+
*/
|
|
52
|
+
export type PredictEntityKeysReturnType<
|
|
53
|
+
TCount extends number = number,
|
|
54
|
+
TSalts extends readonly bigint[] | undefined = readonly bigint[] | undefined,
|
|
55
|
+
> = [TSalts] extends [readonly bigint[]]
|
|
56
|
+
? { -readonly [Index in keyof TSalts]: PredictedEntityKey }
|
|
57
|
+
: Repeat<PredictedEntityKey, TCount>
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A tuple of `TLength` × `T`, or a plain array when the length is only known at runtime.
|
|
61
|
+
*
|
|
62
|
+
* Written tail-recursively so TypeScript can build the batch sizes anyone would actually send.
|
|
63
|
+
*/
|
|
64
|
+
type Repeat<T, TLength extends number, TAcc extends T[] = []> = number extends TLength
|
|
65
|
+
? T[]
|
|
66
|
+
: TAcc["length"] extends TLength
|
|
67
|
+
? TAcc
|
|
68
|
+
: Repeat<T, TLength, [...TAcc, T]>
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Works out the keys an account's next creates will be given, before sending them.
|
|
72
|
+
*
|
|
73
|
+
* A key is `keccak256(chainId ++ registry ++ owner ++ nonce ++ salt)`. Everything but the nonce is
|
|
74
|
+
* known to the caller, and the nonce is read here — so a batch can reference a key it is about to
|
|
75
|
+
* mint, storing it as a `key` attribute on a sibling entity created in the same transaction.
|
|
76
|
+
*
|
|
77
|
+
* Each key arrives paired with its salt, and the create must be given that salt: a create left to
|
|
78
|
+
* pick its own salt picks a random one, which mints a different key.
|
|
79
|
+
*
|
|
80
|
+
* The prediction is valid only while nothing else from this owner is in flight. Every create that
|
|
81
|
+
* lands first takes the nonce with it and moves these keys. For a key you can rely on
|
|
82
|
+
* unconditionally, read it back from the create instead — `createEntity` and `mutateEntities`
|
|
83
|
+
* report the keys the engine actually minted.
|
|
84
|
+
*
|
|
85
|
+
* @param parameters - Owner, and either a count or the salts. {@link PredictEntityKeysParameters}
|
|
86
|
+
* @returns One `{ key, salt }` pair per create, in batch order. {@link PredictEntityKeysReturnType}
|
|
87
|
+
*
|
|
88
|
+
* @example A batch whose second entity points at its first.
|
|
89
|
+
* import { key } from "@arkiv-network/sdk/attr"
|
|
90
|
+
*
|
|
91
|
+
* const [parent, child] = await client.predictEntityKeys({ owner: account.address, count: 2 })
|
|
92
|
+
* await wallet.mutateEntities({
|
|
93
|
+
* creates: [
|
|
94
|
+
* { payload, contentType, expires, salt: parent.salt },
|
|
95
|
+
* {
|
|
96
|
+
* payload,
|
|
97
|
+
* contentType,
|
|
98
|
+
* expires,
|
|
99
|
+
* salt: child.salt,
|
|
100
|
+
* attributes: { parent: key(parent.key) },
|
|
101
|
+
* },
|
|
102
|
+
* ],
|
|
103
|
+
* })
|
|
104
|
+
*/
|
|
105
|
+
export async function predictEntityKeys<
|
|
106
|
+
const TCount extends number = number,
|
|
107
|
+
const TSalts extends readonly bigint[] | undefined = undefined,
|
|
108
|
+
>(
|
|
109
|
+
client: ArkivClient,
|
|
110
|
+
{ owner, count, salts }: PredictEntityKeysParameters<TCount, TSalts>,
|
|
111
|
+
): Promise<PredictEntityKeysReturnType<TCount, TSalts>> {
|
|
112
|
+
const resolvedSalts = resolveSalts(count, salts)
|
|
113
|
+
|
|
114
|
+
const chainId = client.chain?.id ?? (await getChainId(client))
|
|
115
|
+
const nonce = await getEntityNonce(client, owner)
|
|
116
|
+
|
|
117
|
+
return resolvedSalts.map((salt, index) => ({
|
|
118
|
+
key: predictEntityKey({ owner, nonce: nonce + BigInt(index), salt, chainId }),
|
|
119
|
+
salt,
|
|
120
|
+
})) as PredictEntityKeysReturnType<TCount, TSalts>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The salts to derive from: the ones given, or `count` fresh random ones. */
|
|
124
|
+
function resolveSalts(count: number | undefined, salts: readonly bigint[] | undefined): bigint[] {
|
|
125
|
+
if (salts !== undefined) {
|
|
126
|
+
if (salts.length === 0) {
|
|
127
|
+
throw new Error("predictEntityKeys: `salts` is empty — there is nothing to predict")
|
|
128
|
+
}
|
|
129
|
+
return salts.map(validateSalt)
|
|
130
|
+
}
|
|
131
|
+
if (count === undefined) {
|
|
132
|
+
throw new Error("predictEntityKeys: pass either `count` or `salts`")
|
|
133
|
+
}
|
|
134
|
+
if (!Number.isInteger(count) || count <= 0) {
|
|
135
|
+
throw new Error(`predictEntityKeys: \`count\` must be a positive whole number, got ${count}`)
|
|
136
|
+
}
|
|
137
|
+
return Array.from({ length: count }, () => randomSalt())
|
|
138
|
+
}
|
|
@@ -1,83 +1,47 @@
|
|
|
1
|
-
import { numberToHex } from "viem"
|
|
2
1
|
import type { ArkivClient } from "../../clients/baseClient"
|
|
2
|
+
import { runQuery } from "../../query/engine"
|
|
3
|
+
import type { Expression } from "../../query/expression"
|
|
4
|
+
import { type SelectArg, toRpcSelect } from "../../query/selection"
|
|
3
5
|
import type { Entity } from "../../types/entity"
|
|
4
|
-
import type { RpcQueryOptions } from "../../types/rpcSchema"
|
|
5
|
-
import { entityFromRpcResult } from "../../utils/entities"
|
|
6
|
-
|
|
7
|
-
export type QueryOptionsIncludeData = {
|
|
8
|
-
attributes?: boolean
|
|
9
|
-
payload?: boolean
|
|
10
|
-
metadata?: boolean
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* @deprecated Server-side ordering is not supported by the network. Sort the fetched
|
|
14
|
-
* entities in JavaScript instead (e.g. `entities.sort(...)`). This type will be removed
|
|
15
|
-
* in a future release.
|
|
16
|
-
*/
|
|
17
|
-
export type QueryOptionsOrderBy = {
|
|
18
|
-
name: string
|
|
19
|
-
type: "string" | "numeric"
|
|
20
|
-
desc: "asc" | "desc"
|
|
21
|
-
}
|
|
22
6
|
|
|
23
7
|
export type QueryOptions = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
orderBy?: QueryOptionsOrderBy[]
|
|
32
|
-
resultsPerPage?: number | undefined
|
|
8
|
+
/** What to return. Defaults to everything. */
|
|
9
|
+
select?: SelectArg | undefined
|
|
10
|
+
/** Block height to read at. Defaults to the head. */
|
|
11
|
+
atBlock?: bigint | undefined
|
|
12
|
+
/** Page size, up to the node maximum of 200. */
|
|
13
|
+
limit?: number | undefined
|
|
14
|
+
/** Cursor from a previous page. */
|
|
33
15
|
cursor?: string | undefined
|
|
34
16
|
}
|
|
35
17
|
|
|
36
18
|
export type QueryReturnType = {
|
|
37
19
|
entities: Entity[]
|
|
20
|
+
/** The cursor for the next page, or `undefined` when no pages remain. */
|
|
38
21
|
cursor: string | undefined
|
|
39
|
-
|
|
22
|
+
/** The block the page was read at. */
|
|
23
|
+
blockNumber: bigint
|
|
40
24
|
}
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
desc: order.desc === "desc",
|
|
63
|
-
})),
|
|
64
|
-
}),
|
|
65
|
-
...(queryOptions?.resultsPerPage !== undefined && {
|
|
66
|
-
resultsPerPage: numberToHex(queryOptions.resultsPerPage),
|
|
67
|
-
}),
|
|
68
|
-
...(queryOptions?.cursor !== undefined && { cursor: queryOptions?.cursor }),
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const result = await client.request({
|
|
72
|
-
method: "arkiv_query",
|
|
73
|
-
params: [query, rpcQueryOptions],
|
|
26
|
+
/**
|
|
27
|
+
* Runs a single query and returns one page, with no builder in between.
|
|
28
|
+
*
|
|
29
|
+
* Takes either an {@link Expression} or a raw query string. The string form is an escape hatch for
|
|
30
|
+
* a query built elsewhere — it goes to the node exactly as written, with none of the name, type or
|
|
31
|
+
* operator checks the expression combinators apply.
|
|
32
|
+
*
|
|
33
|
+
* @throws {QueryError} If the node rejects the query.
|
|
34
|
+
*/
|
|
35
|
+
export function query(
|
|
36
|
+
client: ArkivClient,
|
|
37
|
+
query: Expression | string,
|
|
38
|
+
options: QueryOptions = {},
|
|
39
|
+
): Promise<QueryReturnType> {
|
|
40
|
+
return runQuery(client, {
|
|
41
|
+
query: query.toString(),
|
|
42
|
+
select: toRpcSelect(options.select),
|
|
43
|
+
limit: options.limit,
|
|
44
|
+
cursor: options.cursor,
|
|
45
|
+
atBlock: options.atBlock,
|
|
74
46
|
})
|
|
75
|
-
|
|
76
|
-
const entities = await Promise.all(result.data.map((entity) => entityFromRpcResult(entity)))
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
entities,
|
|
80
|
-
cursor: result.cursor,
|
|
81
|
-
blockNumber: BigInt(result.blockNumber),
|
|
82
|
-
}
|
|
83
47
|
}
|