@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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test"
|
|
2
|
+
import { ExpirationTime } from "../utils/expirationTime"
|
|
3
|
+
import { InvalidExpiryError } from "./errors"
|
|
4
|
+
import { type Expiry, type ExpiryContext, resolveExpiry, toBlocks } from "./expiry"
|
|
5
|
+
import { MAX_EXPIRES_AT } from "./params"
|
|
6
|
+
|
|
7
|
+
const CURRENT_BLOCK = 1000n
|
|
8
|
+
const ctx: ExpiryContext = { currentBlock: CURRENT_BLOCK }
|
|
9
|
+
|
|
10
|
+
describe("what ExpirationTime builds", () => {
|
|
11
|
+
it("is the wire pair itself, so what you write is what is sent", () => {
|
|
12
|
+
expect(ExpirationTime.fromDays(30)).toEqual({ minLifetime: 1_296_000n, expiresAt: 0n } as never)
|
|
13
|
+
expect(ExpirationTime.atBlock(1_200_000n)).toEqual({
|
|
14
|
+
minLifetime: 0n,
|
|
15
|
+
expiresAt: 1_200_000n,
|
|
16
|
+
} as never)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("is plain data — no prototype, no methods to lose", () => {
|
|
20
|
+
const expiry = ExpirationTime.fromDays(1)
|
|
21
|
+
expect(Object.getPrototypeOf(expiry)).toBe(Object.prototype)
|
|
22
|
+
expect(structuredClone(expiry)).toEqual(expiry as never)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("puts a duration in minLifetime and a deadline in expiresAt, never the other way round", () => {
|
|
26
|
+
// Which slot a value lands in is what decides the rules that govern it.
|
|
27
|
+
for (const lifetime of [
|
|
28
|
+
ExpirationTime.fromSeconds(120),
|
|
29
|
+
ExpirationTime.fromMinutes(1),
|
|
30
|
+
ExpirationTime.fromHours(1),
|
|
31
|
+
ExpirationTime.fromDays(1),
|
|
32
|
+
ExpirationTime.fromWeeks(1),
|
|
33
|
+
ExpirationTime.fromMonths(1),
|
|
34
|
+
ExpirationTime.fromYears(1),
|
|
35
|
+
ExpirationTime.fromBlocks(3),
|
|
36
|
+
]) {
|
|
37
|
+
expect(lifetime.expiresAt).toBe(0n)
|
|
38
|
+
expect(lifetime.minLifetime > 0n).toBe(true)
|
|
39
|
+
}
|
|
40
|
+
expect(ExpirationTime.atBlock(5n).minLifetime).toBe(0n)
|
|
41
|
+
expect(ExpirationTime.atDate(new Date(Date.now() + 60_000)).minLifetime).toBe(0n)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe("durations — the everyday case", () => {
|
|
46
|
+
it("turns a duration into a lifetime, with no block arithmetic at the call site", () => {
|
|
47
|
+
const resolved = resolveExpiry(ExpirationTime.fromDays(30), ctx)
|
|
48
|
+
// 30 days at 2s/block.
|
|
49
|
+
expect(resolved.minLifetime).toBe(1_296_000n)
|
|
50
|
+
expect(resolved.expiresAt).toBe(0n)
|
|
51
|
+
expect(resolved.target).toBe(CURRENT_BLOCK + 1_296_000n)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("counts blocks exactly, with no conversion to be approximate about", () => {
|
|
55
|
+
expect(resolveExpiry(ExpirationTime.fromBlocks(7), ctx).minLifetime).toBe(7n)
|
|
56
|
+
expect(() => ExpirationTime.fromBlocks(0)).toThrow(/positive whole number of blocks/)
|
|
57
|
+
expect(() => ExpirationTime.fromBlocks(1.5)).toThrow(InvalidExpiryError)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("refuses a duration that is not a whole number of blocks, at the call site", () => {
|
|
61
|
+
// 3s is a block and a half. Rounding it would silently grant 4s, so it is rejected instead —
|
|
62
|
+
// and it throws where it is written, not later where it is sent.
|
|
63
|
+
expect(() => ExpirationTime.fromSeconds(3)).toThrow(/multiple of the 2s block time/)
|
|
64
|
+
expect(() => ExpirationTime.fromSeconds(999)).toThrow(/Try 998 or 1000/)
|
|
65
|
+
expect(() => ExpirationTime.fromSeconds(0)).toThrow(/positive whole number of seconds/)
|
|
66
|
+
expect(() => ExpirationTime.fromSeconds(-2)).toThrow(InvalidExpiryError)
|
|
67
|
+
expect(() => ExpirationTime.fromSeconds(1.5)).toThrow(InvalidExpiryError)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe("deadlines — the absolute case", () => {
|
|
72
|
+
it("takes a block height as given", () => {
|
|
73
|
+
const resolved = resolveExpiry(ExpirationTime.atBlock(5_000n), ctx)
|
|
74
|
+
expect(resolved.expiresAt).toBe(5_000n)
|
|
75
|
+
expect(resolved.minLifetime).toBe(0n)
|
|
76
|
+
expect(resolved.target).toBe(5_000n)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("places a Date against the block the transaction is built on", () => {
|
|
80
|
+
const inAnHour = ExpirationTime.atDate(new Date(Date.now() + 3600_000))
|
|
81
|
+
const resolved = resolveExpiry(inAnHour, ctx)
|
|
82
|
+
// 3600s at 2s/block, counted from the current block.
|
|
83
|
+
expect(resolved.expiresAt).toBe(CURRENT_BLOCK + 1800n)
|
|
84
|
+
expect(resolved.target).toBe(CURRENT_BLOCK + 1800n)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("rounds a Date up rather than rejecting it, unlike a duration", () => {
|
|
88
|
+
// A wall-clock instant becomes a block HEIGHT, which is whole by construction — there is no
|
|
89
|
+
// divisibility question to fail. `fromSeconds(3)` would have thrown; this cannot.
|
|
90
|
+
const odd = ExpirationTime.atDate(new Date(Date.now() + 3_000))
|
|
91
|
+
expect(resolveExpiry(odd, ctx).target).toBe(CURRENT_BLOCK + 2n)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("holds the Date until it is resolved, so the value does not depend on when it was built", () => {
|
|
95
|
+
const date = new Date(Date.now() + 3600_000)
|
|
96
|
+
const expiry = ExpirationTime.atDate(date)
|
|
97
|
+
expect(expiry.expiresAt).toEqual(date)
|
|
98
|
+
// The same value resolves against whatever block it is later sent with.
|
|
99
|
+
expect(resolveExpiry(expiry, { ...ctx, currentBlock: 9_000n }).target).toBe(9_000n + 1800n)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it("rejects a deadline already gone", () => {
|
|
103
|
+
expect(() => resolveExpiry(ExpirationTime.atDate(new Date(Date.now() - 1000)), ctx)).toThrow(
|
|
104
|
+
/in the past/,
|
|
105
|
+
)
|
|
106
|
+
expect(() => resolveExpiry(ExpirationTime.atBlock(CURRENT_BLOCK - 1n), ctx)).toThrow(
|
|
107
|
+
/dead on arrival/,
|
|
108
|
+
)
|
|
109
|
+
expect(() => resolveExpiry(ExpirationTime.atBlock(CURRENT_BLOCK), ctx)).toThrow(
|
|
110
|
+
/dead on arrival/,
|
|
111
|
+
)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it("rejects something that is neither a block height nor a Date", () => {
|
|
115
|
+
expect(() => ExpirationTime.atBlock(-1n)).toThrow(/non-negative block height/)
|
|
116
|
+
expect(() => ExpirationTime.atDate(new Date("nonsense"))).toThrow(/valid Date/)
|
|
117
|
+
expect(() => ExpirationTime.atBlock(5 as never)).toThrow(/non-negative block height/)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe("a deadline with a floor", () => {
|
|
122
|
+
it("lives until whichever is later", () => {
|
|
123
|
+
// The deadline is further out than the floor.
|
|
124
|
+
const deadlineWins = ExpirationTime.atBlock(5_000n, {
|
|
125
|
+
atLeast: ExpirationTime.fromSeconds(400),
|
|
126
|
+
})
|
|
127
|
+
expect(resolveExpiry(deadlineWins, ctx).target).toBe(5_000n)
|
|
128
|
+
|
|
129
|
+
// The floor is further out than the deadline.
|
|
130
|
+
const floorWins = ExpirationTime.atBlock(1_100n, { atLeast: ExpirationTime.fromDays(1) })
|
|
131
|
+
expect(resolveExpiry(floorWins, ctx).target).toBe(CURRENT_BLOCK + 43_200n)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it("puts both on the wire, leaving the engine to resolve them the same way", () => {
|
|
135
|
+
const resolved = resolveExpiry(
|
|
136
|
+
ExpirationTime.atBlock(5_000n, { atLeast: ExpirationTime.fromSeconds(200) }),
|
|
137
|
+
ctx,
|
|
138
|
+
)
|
|
139
|
+
expect(resolved.expiresAt).toBe(5_000n)
|
|
140
|
+
expect(resolved.minLifetime).toBe(100n)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it("works the same on a date deadline", () => {
|
|
144
|
+
const expiry = ExpirationTime.atDate(new Date(Date.now() + 2_000), {
|
|
145
|
+
atLeast: ExpirationTime.fromDays(1),
|
|
146
|
+
})
|
|
147
|
+
// The date is only a second or two out, so the floor is what keeps the entity alive.
|
|
148
|
+
expect(resolveExpiry(expiry, ctx).target).toBe(CURRENT_BLOCK + 43_200n)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it("treats an omitted floor as no floor", () => {
|
|
152
|
+
expect(ExpirationTime.atBlock(5_000n, {}).minLifetime).toBe(0n)
|
|
153
|
+
expect(ExpirationTime.atBlock(5_000n, { atLeast: undefined }).minLifetime).toBe(0n)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe("bounds and misuse", () => {
|
|
158
|
+
it("refuses an expiry that says nothing", () => {
|
|
159
|
+
// Only reachable from JS, or by hand-building the pair — the type requires ExpirationTime.
|
|
160
|
+
const nothing = { minLifetime: 0n, expiresAt: 0n } as unknown as Expiry
|
|
161
|
+
expect(() => resolveExpiry(nothing, ctx)).toThrow(/no expiry given/)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it("names ExpirationTime when handed something it did not build", () => {
|
|
165
|
+
expect(() => resolveExpiry(2_592_000 as unknown as Expiry, ctx)).toThrow(
|
|
166
|
+
/must be built with ExpirationTime/,
|
|
167
|
+
)
|
|
168
|
+
expect(() => resolveExpiry(undefined as unknown as Expiry, ctx)).toThrow(
|
|
169
|
+
/must be built with ExpirationTime/,
|
|
170
|
+
)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("bounds an expiry only by the width of the wire field", () => {
|
|
174
|
+
expect(() => resolveExpiry(ExpirationTime.atBlock(MAX_EXPIRES_AT + 1n), ctx)).toThrow(
|
|
175
|
+
/does not fit the uint64/,
|
|
176
|
+
)
|
|
177
|
+
expect(() => resolveExpiry(ExpirationTime.atBlock(MAX_EXPIRES_AT + 1n), ctx)).toThrow(
|
|
178
|
+
/ExpirationTime\.permanent\(\)/,
|
|
179
|
+
)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
describe("toBlocks", () => {
|
|
184
|
+
it("converts whole blocks' worth of seconds", () => {
|
|
185
|
+
expect(toBlocks(2)).toBe(1n)
|
|
186
|
+
expect(toBlocks(4)).toBe(2n)
|
|
187
|
+
expect(toBlocks(86_400)).toBe(43_200n)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it("rejects anything that is not a positive multiple of the block time", () => {
|
|
191
|
+
for (const bad of [1, 3, 999, 0, -2, 1.5, Number.NaN, Number.POSITIVE_INFINITY]) {
|
|
192
|
+
expect(() => toBlocks(bad)).toThrow(InvalidExpiryError)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
})
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { BLOCK_TIME } from "../consts"
|
|
2
|
+
import { InvalidExpiryError } from "./errors"
|
|
3
|
+
import { MAX_EXPIRES_AT } from "./params"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The brand. Module-private and never exported, so the only way to obtain an {@link Expiry} is to
|
|
7
|
+
* build one with {@link ExpirationTime} — a hand-written `{ minLifetime, expiresAt }` literal will
|
|
8
|
+
* not typecheck, and neither will a bare number of seconds.
|
|
9
|
+
*/
|
|
10
|
+
declare const validated: unique symbol
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How long an entity should live.
|
|
14
|
+
*
|
|
15
|
+
* This is the pair the ABI carries, and the engine resolves it as
|
|
16
|
+
* `max(expiresAt, currentBlock + minLifetime)` — whichever is later. Build one with
|
|
17
|
+
* {@link ExpirationTime}; there is no other way to make one.
|
|
18
|
+
*
|
|
19
|
+
* Which slot a value lands in also decides which rules govern it. `minLifetime` is a duration, so
|
|
20
|
+
* it must come to a whole number of blocks. `expiresAt` is a block height, which is a whole number
|
|
21
|
+
* by construction, so a wall-clock date rounds into it rather than being rejected.
|
|
22
|
+
*/
|
|
23
|
+
export type Expiry = {
|
|
24
|
+
/** Lifetime floor in blocks, counted from the block the transaction lands in. `0n` if unset. */
|
|
25
|
+
readonly minLifetime: bigint
|
|
26
|
+
/**
|
|
27
|
+
* Absolute deadline: a block height, or a `Date` still to be placed against the current block.
|
|
28
|
+
* `0n` if unset.
|
|
29
|
+
*/
|
|
30
|
+
readonly expiresAt: bigint | Date
|
|
31
|
+
readonly [validated]: true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* An expiry that is nothing but a duration — what the `ExpirationTime.from*` helpers return.
|
|
36
|
+
*
|
|
37
|
+
* Distinguished from a full {@link Expiry} by its `expiresAt` being the literal `0n`, which is what
|
|
38
|
+
* keeps `atBlock(x, { atLeast: atBlock(y) })` from compiling: a deadline is not a floor.
|
|
39
|
+
*/
|
|
40
|
+
export type Lifetime = Expiry & { readonly expiresAt: 0n }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds a branded {@link Expiry}.
|
|
44
|
+
*
|
|
45
|
+
* Internal seam for {@link ExpirationTime}, which is the only thing that should call it — it is not
|
|
46
|
+
* part of the package's public surface.
|
|
47
|
+
*/
|
|
48
|
+
export function makeExpiry(minLifetime: bigint, expiresAt: bigint | Date): Expiry {
|
|
49
|
+
return { minLifetime, expiresAt } as Expiry
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The two `uint256` values the ABI carries, with every `Date` already placed. */
|
|
53
|
+
export type EncodedExpiry = {
|
|
54
|
+
/** Absolute target block height, or `0n` for a purely relative lifetime. */
|
|
55
|
+
expiresAt: bigint
|
|
56
|
+
/** Relative floor in blocks, or `0n` for a purely absolute expiry. */
|
|
57
|
+
minLifetime: bigint
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** What an expiry resolves to, given the chain's current block. */
|
|
61
|
+
export type ResolvedExpiry = EncodedExpiry & {
|
|
62
|
+
/** The block the entity will expire at — `max(expiresAt, currentBlock + minLifetime)`. */
|
|
63
|
+
target: bigint
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** What the SDK needs to know about the chain to resolve an expiry. */
|
|
67
|
+
export type ExpiryContext = {
|
|
68
|
+
currentBlock: bigint
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolves an expiry into the wire pair and the block the entity will expire at.
|
|
73
|
+
*
|
|
74
|
+
* Durations are converted to blocks at the nominal `BLOCK_TIME` (2 seconds), which makes them
|
|
75
|
+
* **approximate**: block production is not a clock, so a lifetime asked for in days drifts if the
|
|
76
|
+
* chain runs fast or slow. The block it was converted to comes back as
|
|
77
|
+
* {@link ResolvedExpiry.target}, so the approximation is always observable rather than assumed.
|
|
78
|
+
*
|
|
79
|
+
* @throws {InvalidExpiryError} If the expiry is malformed, does not fit the `uint64` the wire
|
|
80
|
+
* carries, or would leave the entity dead on arrival.
|
|
81
|
+
*/
|
|
82
|
+
export function resolveExpiry(expiry: Expiry, context: ExpiryContext): ResolvedExpiry {
|
|
83
|
+
const { currentBlock } = context
|
|
84
|
+
|
|
85
|
+
if (typeof expiry?.minLifetime !== "bigint" || expiry.expiresAt === undefined) {
|
|
86
|
+
throw new InvalidExpiryError(
|
|
87
|
+
"expires must be built with ExpirationTime, e.g. ExpirationTime.fromDays(30) or " +
|
|
88
|
+
"ExpirationTime.atBlock(1_200_000n)",
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { minLifetime } = expiry
|
|
93
|
+
const absolute =
|
|
94
|
+
typeof expiry.expiresAt === "bigint"
|
|
95
|
+
? expiry.expiresAt
|
|
96
|
+
: blockAtDate(expiry.expiresAt, currentBlock)
|
|
97
|
+
|
|
98
|
+
if (minLifetime === 0n && absolute === 0n) {
|
|
99
|
+
throw new InvalidExpiryError(
|
|
100
|
+
"no expiry given. Pass ExpirationTime.fromDays(30) for a lifetime, " +
|
|
101
|
+
"ExpirationTime.atBlock(n) for an absolute deadline, or a deadline with " +
|
|
102
|
+
"{ atLeast } to take whichever is later.",
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (absolute > MAX_EXPIRES_AT) {
|
|
107
|
+
throw new InvalidExpiryError(
|
|
108
|
+
`expiresAt resolves to block ${absolute}, which does not fit the uint64 the wire carries ` +
|
|
109
|
+
`(max ${MAX_EXPIRES_AT}). Use ExpirationTime.permanent() for an entity that should not ` +
|
|
110
|
+
"expire.",
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
if (minLifetime > MAX_EXPIRES_AT) {
|
|
114
|
+
throw new InvalidExpiryError(
|
|
115
|
+
`the lifetime is ${minLifetime} blocks, which does not fit the uint64 the wire carries ` +
|
|
116
|
+
`(max ${MAX_EXPIRES_AT})`,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const relative = minLifetime === 0n ? 0n : currentBlock + minLifetime
|
|
121
|
+
const target = absolute > relative ? absolute : relative
|
|
122
|
+
|
|
123
|
+
if (target <= currentBlock) {
|
|
124
|
+
throw new InvalidExpiryError(
|
|
125
|
+
`the entity would expire at block ${target}, which is not after the current block ` +
|
|
126
|
+
`${currentBlock}, so it would be dead on arrival`,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
if (target > MAX_EXPIRES_AT) {
|
|
130
|
+
throw new InvalidExpiryError(
|
|
131
|
+
`a lifetime of ${minLifetime} blocks from block ${currentBlock} lands at ${target}, past ` +
|
|
132
|
+
`the largest expiry the wire carries (${MAX_EXPIRES_AT})`,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { expiresAt: absolute, minLifetime, target }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* A duration in seconds as a whole number of blocks.
|
|
141
|
+
*
|
|
142
|
+
* Arkiv counts lifetimes in blocks, so a duration that is not a whole number of them does not
|
|
143
|
+
* describe anything the chain can store. Rather than silently round a caller's `3` up to two blocks
|
|
144
|
+
* — four seconds, not what they asked for — this rejects it: a duration must be a positive multiple
|
|
145
|
+
* of `BLOCK_TIME` (2 seconds). The {@link ExpirationTime} helpers all produce such multiples.
|
|
146
|
+
*
|
|
147
|
+
* @throws {InvalidExpiryError} If the duration is not a positive whole number of blocks' seconds.
|
|
148
|
+
*/
|
|
149
|
+
export function toBlocks(seconds: number): bigint {
|
|
150
|
+
if (!Number.isInteger(seconds) || seconds <= 0) {
|
|
151
|
+
throw new InvalidExpiryError(
|
|
152
|
+
`a lifetime must be a positive whole number of seconds, got ${seconds}. Use the ` +
|
|
153
|
+
"ExpirationTime helpers, e.g. ExpirationTime.fromDays(30).",
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
if (seconds % BLOCK_TIME !== 0) {
|
|
157
|
+
throw new InvalidExpiryError(
|
|
158
|
+
`a lifetime must be a multiple of the ${BLOCK_TIME}s block time, got ${seconds}, which is ` +
|
|
159
|
+
`${seconds / BLOCK_TIME} blocks. Try ${seconds - (seconds % BLOCK_TIME)} or ` +
|
|
160
|
+
`${seconds + BLOCK_TIME - (seconds % BLOCK_TIME)}, or ExpirationTime.fromBlocks(n).`,
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
return BigInt(seconds / BLOCK_TIME)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The block a wall-clock instant lands on.
|
|
168
|
+
*
|
|
169
|
+
* Rounds **up**: a date almost never falls on a block boundary, and unlike a duration — which the
|
|
170
|
+
* caller states and can state exactly — there is nothing here to reject, so the entity lives until
|
|
171
|
+
* at least the instant asked for.
|
|
172
|
+
*/
|
|
173
|
+
function blockAtDate(date: Date, currentBlock: bigint): bigint {
|
|
174
|
+
const secondsAway = (date.getTime() - Date.now()) / 1000
|
|
175
|
+
if (secondsAway <= 0) {
|
|
176
|
+
throw new InvalidExpiryError(
|
|
177
|
+
`the deadline ${date.toISOString()} is in the past, so the entity would be dead on arrival`,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
return currentBlock + BigInt(Math.ceil(secondsAway / BLOCK_TIME))
|
|
181
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { InvalidCreationFlagsError } from "./errors"
|
|
2
|
+
|
|
3
|
+
/** Bit positions in the `creationFlags` byte. Bits 2-7 are reserved and must be zero. */
|
|
4
|
+
const READONLY_BIT = 0b0000_0001
|
|
5
|
+
const PERMISSIONLESS_EXTENSION_BIT = 0b0000_0010
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The immutable properties an entity is created with. Both default to `false`.
|
|
9
|
+
*
|
|
10
|
+
* Creation flags are fixed at creation: there is no operation that changes them, so an entity is
|
|
11
|
+
* whatever it was created as for its whole life.
|
|
12
|
+
*/
|
|
13
|
+
export type CreationFlags = {
|
|
14
|
+
/**
|
|
15
|
+
* The entity's attributes and payload can never be changed — only its expiry extended, its
|
|
16
|
+
* ownership transferred, and the entity deleted.
|
|
17
|
+
*/
|
|
18
|
+
readonly?: boolean
|
|
19
|
+
/**
|
|
20
|
+
* Anyone, not just the owner, may extend the entity's expiry. Lets a third party keep something
|
|
21
|
+
* alive without being able to change it.
|
|
22
|
+
*/
|
|
23
|
+
permissionlessExtension?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** {@link CreationFlags} as read back, with every flag resolved and the raw byte alongside. */
|
|
27
|
+
export type ResolvedCreationFlags = {
|
|
28
|
+
readonly: boolean
|
|
29
|
+
permissionlessExtension: boolean
|
|
30
|
+
raw: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Packs creation flags into the `creationFlags` byte.
|
|
35
|
+
*
|
|
36
|
+
* @throws {InvalidCreationFlagsError} If a value is not a boolean.
|
|
37
|
+
*/
|
|
38
|
+
export function encodeCreationFlags(flags: CreationFlags = {}): number {
|
|
39
|
+
for (const [name, value] of Object.entries(flags)) {
|
|
40
|
+
if (value !== undefined && typeof value !== "boolean") {
|
|
41
|
+
throw new InvalidCreationFlagsError(`"${name}" must be a boolean, got ${typeof value}`)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return (
|
|
45
|
+
(flags.readonly ? READONLY_BIT : 0) |
|
|
46
|
+
(flags.permissionlessExtension ? PERMISSIONLESS_EXTENSION_BIT : 0)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Reads the `creationFlags` byte back.
|
|
52
|
+
*
|
|
53
|
+
* @param raw - The `creationFlags` byte.
|
|
54
|
+
* @throws {InvalidCreationFlagsError} Malformed input
|
|
55
|
+
*/
|
|
56
|
+
export function decodeCreationFlags(raw: number): ResolvedCreationFlags {
|
|
57
|
+
if (!Number.isInteger(raw) || raw < 0 || raw > 0xff) {
|
|
58
|
+
throw new InvalidCreationFlagsError(`${raw} is not a byte`)
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
readonly: (raw & READONLY_BIT) !== 0,
|
|
62
|
+
permissionlessExtension: (raw & PERMISSIONLESS_EXTENSION_BIT) !== 0,
|
|
63
|
+
raw,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
InvalidCreationFlagsError,
|
|
3
|
+
InvalidExpiryError,
|
|
4
|
+
InvalidSaltError,
|
|
5
|
+
NoRandomSourceError,
|
|
6
|
+
} from "./errors"
|
|
7
|
+
export type { EntityEventName } from "./events"
|
|
8
|
+
export { ENTITY_EVENTS_ABI } from "./events"
|
|
9
|
+
export type { EncodedExpiry, Expiry, ExpiryContext, Lifetime, ResolvedExpiry } from "./expiry"
|
|
10
|
+
export { resolveExpiry, toBlocks } from "./expiry"
|
|
11
|
+
export type { CreationFlags, ResolvedCreationFlags } from "./flags"
|
|
12
|
+
export { decodeCreationFlags, encodeCreationFlags } from "./flags"
|
|
13
|
+
export { MAX_SALT, predictEntityKey, randomSalt } from "./key"
|
|
14
|
+
export { MAX_EXPIRES_AT, OperationType } from "./params"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type Address, encodePacked, type Hex, keccak256 } from "viem"
|
|
2
|
+
import { ARKIV_ADDRESS } from "../consts"
|
|
3
|
+
import { InvalidSaltError, NoRandomSourceError } from "./errors"
|
|
4
|
+
|
|
5
|
+
/** The widest salt the `uint128` wire field carries. */
|
|
6
|
+
export const MAX_SALT = 2n ** 128n - 1n
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A cryptographically random 128-bit salt — what {@link CreateEntityParameters.salt} defaults to.
|
|
10
|
+
*
|
|
11
|
+
* The salt exists for unpredictability, not uniqueness: the per-owner nonce already guarantees
|
|
12
|
+
* distinct keys. Random bits mean only the creator can predict the key of an entity they are about
|
|
13
|
+
* to create, so nobody else can reference or squat it in advance.
|
|
14
|
+
*
|
|
15
|
+
* @throws {NoRandomSourceError} If the runtime has no `crypto.getRandomValues`.
|
|
16
|
+
*/
|
|
17
|
+
export function randomSalt(): bigint {
|
|
18
|
+
const source = globalThis.crypto
|
|
19
|
+
if (typeof source?.getRandomValues !== "function") {
|
|
20
|
+
throw new NoRandomSourceError()
|
|
21
|
+
}
|
|
22
|
+
const bytes = source.getRandomValues(new Uint8Array(16))
|
|
23
|
+
let salt = 0n
|
|
24
|
+
for (const byte of bytes) salt = (salt << 8n) | BigInt(byte)
|
|
25
|
+
return salt
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Validates a caller-supplied salt against the `uint128` wire field. */
|
|
29
|
+
export function validateSalt(salt: bigint): bigint {
|
|
30
|
+
if (typeof salt !== "bigint" || salt < 0n || salt > MAX_SALT) {
|
|
31
|
+
throw new InvalidSaltError(salt)
|
|
32
|
+
}
|
|
33
|
+
return salt
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The key an entity will be created with, derived exactly as the engine derives it:
|
|
38
|
+
* `keccak256(domain ++ owner ++ nonce ++ salt)`.
|
|
39
|
+
*
|
|
40
|
+
* Deriving it client-side is what lets a batch compose: predict the key of a create, then have a
|
|
41
|
+
* later operation in the same transaction reference it, or store it as a `key` attribute on a
|
|
42
|
+
* sibling entity.
|
|
43
|
+
*/
|
|
44
|
+
export function predictEntityKey({
|
|
45
|
+
owner,
|
|
46
|
+
nonce,
|
|
47
|
+
salt,
|
|
48
|
+
chainId,
|
|
49
|
+
}: {
|
|
50
|
+
/** The account creating the entity. */
|
|
51
|
+
owner: Address
|
|
52
|
+
/**
|
|
53
|
+
* The owner's entity-nonce at creation time. Each create in a batch consumes one, so the *n*th
|
|
54
|
+
* create in a batch uses `nonce + n`.
|
|
55
|
+
*/
|
|
56
|
+
nonce: bigint
|
|
57
|
+
/** The salt the create will carry. */
|
|
58
|
+
salt: bigint
|
|
59
|
+
/** The chain the entity is being created on — half of the derivation domain. */
|
|
60
|
+
chainId: number
|
|
61
|
+
}): Hex {
|
|
62
|
+
return keccak256(
|
|
63
|
+
encodePacked(
|
|
64
|
+
["uint256", "address", "address", "uint64", "uint128"],
|
|
65
|
+
[BigInt(chainId), ARKIV_ADDRESS, owner, nonce, validateSalt(salt)],
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { type Address, encodeAbiParameters, type Hex, parseAbi, parseAbiParameters } from "viem"
|
|
2
|
+
import type { AbiAttribute } from "../attr/codec"
|
|
3
|
+
import type { EncodedExpiry } from "./expiry"
|
|
4
|
+
import { OperationType } from "./params"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The one external entry point. A transaction is an ordered, **atomic** batch of entity operations:
|
|
8
|
+
* all of them apply or none do, operation *n+1* observes operation *n*'s effects, and each applied
|
|
9
|
+
* operation emits one event.
|
|
10
|
+
*/
|
|
11
|
+
export const EXECUTE_ABI = parseAbi([
|
|
12
|
+
"function execute((uint8 operation, bytes operationData)[] ops) external returns (bytes32[] keys)",
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The engine's one read-only view: an owner's entity-minting nonce, which is the input
|
|
17
|
+
* {@link predictEntityKey} needs and the only part of the derivation the caller cannot know.
|
|
18
|
+
*
|
|
19
|
+
* It is a `view` on the same address `execute` is called on, so it answers over `eth_call` without
|
|
20
|
+
* a transaction.
|
|
21
|
+
*/
|
|
22
|
+
export const ENTITY_NONCE_ABI = parseAbi([
|
|
23
|
+
"function entityNonce(address owner) external view returns (uint64)",
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The shared attribute shape, used by `create` (attributes) and `patch` (mutations). Written out
|
|
28
|
+
* once and reused by every payload that carries attributes.
|
|
29
|
+
*/
|
|
30
|
+
const ATTRIBUTE_TUPLE = "(bytes32 name, uint8 typeId, bytes value)[]"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The payload struct for each operation tag. `operationData` is `abi.encode` of the struct the tag
|
|
34
|
+
* selects — a tagged union, so a new operation is additive rather than a change to `execute`.
|
|
35
|
+
*/
|
|
36
|
+
const PAYLOAD_PARAMS = {
|
|
37
|
+
[OperationType.Create]: parseAbiParameters(
|
|
38
|
+
`(uint128 salt, uint64 expiresAt, uint64 minLifetime, uint8 creationFlags, ${ATTRIBUTE_TUPLE} attributes)`,
|
|
39
|
+
),
|
|
40
|
+
[OperationType.Patch]: parseAbiParameters(`(bytes32 entityKey, ${ATTRIBUTE_TUPLE} mutations)`),
|
|
41
|
+
[OperationType.ExtendExpiry]: parseAbiParameters(
|
|
42
|
+
"(bytes32 entityKey, uint64 expiresAt, uint64 minLifetime)",
|
|
43
|
+
),
|
|
44
|
+
[OperationType.TransferOwnership]: parseAbiParameters("(bytes32 entityKey, address newOwner)"),
|
|
45
|
+
[OperationType.Delete]: parseAbiParameters("(bytes32 entityKey)"),
|
|
46
|
+
} as const
|
|
47
|
+
|
|
48
|
+
/** One entry of the `execute` batch: the operation tag and its ABI-encoded payload. */
|
|
49
|
+
export type Operation = {
|
|
50
|
+
operation: number
|
|
51
|
+
operationData: Hex
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Creates an entity. The key is derived by the engine; predict it with `predictEntityKey`. */
|
|
55
|
+
export function createOperation({
|
|
56
|
+
salt,
|
|
57
|
+
expiry,
|
|
58
|
+
creationFlags,
|
|
59
|
+
attributes,
|
|
60
|
+
}: {
|
|
61
|
+
salt: bigint
|
|
62
|
+
expiry: EncodedExpiry
|
|
63
|
+
creationFlags: number
|
|
64
|
+
attributes: AbiAttribute[]
|
|
65
|
+
}): Operation {
|
|
66
|
+
return {
|
|
67
|
+
operation: OperationType.Create,
|
|
68
|
+
operationData: encodeAbiParameters(PAYLOAD_PARAMS[OperationType.Create], [
|
|
69
|
+
{
|
|
70
|
+
salt,
|
|
71
|
+
expiresAt: expiry.expiresAt,
|
|
72
|
+
minLifetime: expiry.minLifetime,
|
|
73
|
+
creationFlags,
|
|
74
|
+
attributes,
|
|
75
|
+
},
|
|
76
|
+
]),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Applies a set of mutations to an existing entity. Tombstones unset attributes. */
|
|
81
|
+
export function patchOperation({
|
|
82
|
+
entityKey,
|
|
83
|
+
mutations,
|
|
84
|
+
}: {
|
|
85
|
+
entityKey: Hex
|
|
86
|
+
mutations: AbiAttribute[]
|
|
87
|
+
}): Operation {
|
|
88
|
+
return {
|
|
89
|
+
operation: OperationType.Patch,
|
|
90
|
+
operationData: encodeAbiParameters(PAYLOAD_PARAMS[OperationType.Patch], [
|
|
91
|
+
{ entityKey, mutations },
|
|
92
|
+
]),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Pushes an entity's expiry further out. Extending is allowed; shortening reverts. */
|
|
97
|
+
export function extendExpiryOperation({
|
|
98
|
+
entityKey,
|
|
99
|
+
expiry,
|
|
100
|
+
}: {
|
|
101
|
+
entityKey: Hex
|
|
102
|
+
expiry: EncodedExpiry
|
|
103
|
+
}): Operation {
|
|
104
|
+
return {
|
|
105
|
+
operation: OperationType.ExtendExpiry,
|
|
106
|
+
operationData: encodeAbiParameters(PAYLOAD_PARAMS[OperationType.ExtendExpiry], [
|
|
107
|
+
{ entityKey, expiresAt: expiry.expiresAt, minLifetime: expiry.minLifetime },
|
|
108
|
+
]),
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Hands an entity to a new owner. */
|
|
113
|
+
export function transferOwnershipOperation({
|
|
114
|
+
entityKey,
|
|
115
|
+
newOwner,
|
|
116
|
+
}: {
|
|
117
|
+
entityKey: Hex
|
|
118
|
+
newOwner: Address
|
|
119
|
+
}): Operation {
|
|
120
|
+
return {
|
|
121
|
+
operation: OperationType.TransferOwnership,
|
|
122
|
+
operationData: encodeAbiParameters(PAYLOAD_PARAMS[OperationType.TransferOwnership], [
|
|
123
|
+
{ entityKey, newOwner },
|
|
124
|
+
]),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Deletes an entity before its expiry. */
|
|
129
|
+
export function deleteOperation({ entityKey }: { entityKey: Hex }): Operation {
|
|
130
|
+
return {
|
|
131
|
+
operation: OperationType.Delete,
|
|
132
|
+
operationData: encodeAbiParameters(PAYLOAD_PARAMS[OperationType.Delete], [{ entityKey }]),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Max uint64 value
|
|
3
|
+
*/
|
|
4
|
+
export const MAX_EXPIRES_AT = 2n ** 64n - 1n
|
|
5
|
+
|
|
6
|
+
/** The operation tags of the `execute` ABI's tagged union. */
|
|
7
|
+
export const OperationType = {
|
|
8
|
+
Create: 1,
|
|
9
|
+
Patch: 2,
|
|
10
|
+
ExtendExpiry: 3,
|
|
11
|
+
TransferOwnership: 4,
|
|
12
|
+
Delete: 5,
|
|
13
|
+
} as const
|
|
14
|
+
|
|
15
|
+
export type OperationType = (typeof OperationType)[keyof typeof OperationType]
|