@arkiv-network/sdk 0.7.0 → 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 +39 -77
- 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 +675 -357
- package/dist/index.d.cts +251 -203
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +251 -203
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +626 -355
- 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 -149
- package/dist/query/index.d.cts +5 -3
- package/dist/query/index.d.ts +5 -3
- package/dist/query/index.js +6 -137
- 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 +4 -1
- package/dist/types/index.d.cts +7 -2
- package/dist/types/index.d.ts +7 -2
- package/dist/types/index.js +4 -1
- 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 +8 -8
- package/src/actions/wallet/createEntity.ts +104 -26
- package/src/actions/wallet/deleteEntity.ts +2 -3
- package/src/actions/wallet/extendEntity.ts +39 -16
- package/src/actions/wallet/mutateEntities.test.ts +76 -0
- package/src/actions/wallet/mutateEntities.ts +29 -74
- 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 +91 -95
- package/src/consts.ts +2 -1
- 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 +33 -28
- package/src/index.ts +6 -0
- package/src/query/engine.ts +59 -134
- 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 +62 -44
- package/src/types/events.ts +64 -23
- package/src/types/index.ts +20 -16
- package/src/types/rpcSchema.ts +70 -33
- package/src/utils/arkivTransactions.test.ts +681 -38
- package/src/utils/arkivTransactions.ts +249 -104
- 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 +111 -29
- package/src/utils/expirationTime.ts +114 -10
- package/src/utils/logger.ts +2 -2
- package/src/utils/payload.ts +4 -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-AUoL-v42.cjs +0 -18
- package/dist/consts-BV4PR-de.js +0 -7
- package/dist/consts-BV4PR-de.js.map +0 -1
- package/dist/entity-B6g_EPm1.d.cts +0 -382
- package/dist/entity-B6g_EPm1.d.cts.map +0 -1
- package/dist/entity-qoLMAuuk.d.ts +0 -382
- package/dist/entity-qoLMAuuk.d.ts.map +0 -1
- package/dist/index-DdspxNAA.d.ts +0 -481
- package/dist/index-DdspxNAA.d.ts.map +0 -1
- package/dist/index-Ds9dwqb5.d.cts +0 -481
- package/dist/index-Ds9dwqb5.d.cts.map +0 -1
- package/dist/payload-B2kFJyah.d.ts +0 -6
- package/dist/payload-B2kFJyah.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-Claf2jUO.js +0 -13
- package/dist/payload-Claf2jUO.js.map +0 -1
- package/dist/payload-DpA5PA-w.cjs +0 -23
- package/dist/query/index.js.map +0 -1
- package/dist/queryBuilder-D2uwP9jI.js +0 -704
- package/dist/queryBuilder-D2uwP9jI.js.map +0 -1
- package/dist/queryBuilder-UW2MCaX4.cjs +0 -792
- 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 -119
- package/src/actions/wallet/updateEntity.ts +0 -60
- package/src/chains/braga.ts +0 -26
- package/src/chains/kaolin.ts +0 -29
- package/src/query/engine.test.ts +0 -526
- 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 -210
- package/src/types/attributes.ts +0 -16
- package/src/utils/validation.test.ts +0 -60
- package/src/utils/validation.ts +0 -32
- /package/dist/{types-H-TSmRpm.js → types-Bx1EKe0v.js} +0 -0
- /package/dist/{types-o3zWarRp.cjs → types-CKPddlfS.cjs} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { bytesToString } from "viem";
|
|
2
|
+
|
|
3
|
+
//#region src/types/entity.ts
|
|
4
|
+
/**
|
|
5
|
+
* An entity as it comes back from a query.
|
|
6
|
+
*
|
|
7
|
+
* Which fields are populated is decided by the query's selection — `select({ key: true })` gives
|
|
8
|
+
* back an entity carrying only its key. The static type of a `select()` result is narrowed to
|
|
9
|
+
* exactly the selected fields, so in typed code the `undefined`s here are not reachable.
|
|
10
|
+
*/
|
|
11
|
+
var Entity = class {
|
|
12
|
+
key;
|
|
13
|
+
owner;
|
|
14
|
+
creator;
|
|
15
|
+
createdAt;
|
|
16
|
+
updatedAt;
|
|
17
|
+
expiresAt;
|
|
18
|
+
creationFlags;
|
|
19
|
+
contentType;
|
|
20
|
+
payload;
|
|
21
|
+
attributeSchema;
|
|
22
|
+
attributes;
|
|
23
|
+
constructor(fields = {}) {
|
|
24
|
+
this.key = fields.key;
|
|
25
|
+
this.owner = fields.owner;
|
|
26
|
+
this.creator = fields.creator;
|
|
27
|
+
this.createdAt = fields.createdAt;
|
|
28
|
+
this.updatedAt = fields.updatedAt;
|
|
29
|
+
this.expiresAt = fields.expiresAt;
|
|
30
|
+
this.creationFlags = fields.creationFlags;
|
|
31
|
+
this.contentType = fields.contentType;
|
|
32
|
+
this.payload = fields.payload;
|
|
33
|
+
this.attributeSchema = fields.attributeSchema;
|
|
34
|
+
this.attributes = fields.attributes;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The payload decoded as UTF-8 text.
|
|
38
|
+
*
|
|
39
|
+
* @throws If the payload was not selected, or is not valid UTF-8.
|
|
40
|
+
*/
|
|
41
|
+
toText() {
|
|
42
|
+
if (this.payload === void 0) throw new Error("Entity has no payload — the query did not select it. Use select({ payload: true }).");
|
|
43
|
+
try {
|
|
44
|
+
return bytesToString(this.payload);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error(`Failed to convert entity payload to text: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The payload parsed as JSON.
|
|
51
|
+
*
|
|
52
|
+
* @throws If the payload was not selected, is empty, or is not valid JSON.
|
|
53
|
+
*/
|
|
54
|
+
toJson() {
|
|
55
|
+
const text = this.toText();
|
|
56
|
+
if (!text) throw new Error("Entity has empty payload, cannot parse as JSON");
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(text);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
throw new Error(`Failed to parse entity payload as JSON: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { Entity as t };
|
|
67
|
+
//# sourceMappingURL=entity-BIp_df9u.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-BIp_df9u.js","names":[],"sources":["../src/types/entity.ts"],"sourcesContent":["import { bytesToString, type Hex } from \"viem\"\nimport type { AttributeSchema, Attributes } from \"../attr\"\nimport type { ResolvedCreationFlags } from \"../entity/flags\"\n\n/**\n * The fields an {@link Entity} can carry. Every one is optional: a query returns exactly the parts\n * it selected, and `undefined` means \"not asked for\" rather than \"empty\".\n */\nexport type EntityFields = {\n key?: Hex | undefined\n owner?: Hex | undefined\n creator?: Hex | undefined\n /** Block the entity was created at. */\n createdAt?: bigint | undefined\n /** Block the entity was last patched at. */\n updatedAt?: bigint | undefined\n /** Block the entity expires at. */\n expiresAt?: bigint | undefined\n /** The immutable properties the entity was created with. */\n creationFlags?: ResolvedCreationFlags | undefined\n contentType?: string | undefined\n payload?: Uint8Array | undefined\n /** Attribute names and types, with no values. */\n attributeSchema?: AttributeSchema | undefined\n /** Attribute names, types and values. */\n attributes?: Attributes | undefined\n}\n\n/**\n * An entity as it comes back from a query.\n *\n * Which fields are populated is decided by the query's selection — `select({ key: true })` gives\n * back an entity carrying only its key. The static type of a `select()` result is narrowed to\n * exactly the selected fields, so in typed code the `undefined`s here are not reachable.\n */\nexport class Entity implements EntityFields {\n key: Hex | undefined\n owner: Hex | undefined\n creator: Hex | undefined\n createdAt: bigint | undefined\n updatedAt: bigint | undefined\n expiresAt: bigint | undefined\n creationFlags: ResolvedCreationFlags | undefined\n contentType: string | undefined\n payload: Uint8Array | undefined\n attributeSchema: AttributeSchema | undefined\n attributes: Attributes | undefined\n\n constructor(fields: EntityFields = {}) {\n this.key = fields.key\n this.owner = fields.owner\n this.creator = fields.creator\n this.createdAt = fields.createdAt\n this.updatedAt = fields.updatedAt\n this.expiresAt = fields.expiresAt\n this.creationFlags = fields.creationFlags\n this.contentType = fields.contentType\n this.payload = fields.payload\n this.attributeSchema = fields.attributeSchema\n this.attributes = fields.attributes\n }\n\n /**\n * The payload decoded as UTF-8 text.\n *\n * @throws If the payload was not selected, or is not valid UTF-8.\n */\n toText(): string {\n if (this.payload === undefined) {\n throw new Error(\n \"Entity has no payload — the query did not select it. Use select({ payload: true }).\",\n )\n }\n try {\n return bytesToString(this.payload)\n } catch (e) {\n throw new Error(\n `Failed to convert entity payload to text: ${e instanceof Error ? e.message : String(e)}`,\n { cause: e },\n )\n }\n }\n\n /**\n * The payload parsed as JSON.\n *\n * @throws If the payload was not selected, is empty, or is not valid JSON.\n */\n // biome-ignore lint/suspicious/noExplicitAny: JSON.parse returns whatever the payload holds.\n toJson(): any {\n const text = this.toText()\n if (!text) {\n throw new Error(\"Entity has empty payload, cannot parse as JSON\")\n }\n try {\n return JSON.parse(text)\n } catch (e) {\n throw new Error(\n `Failed to parse entity payload as JSON: ${e instanceof Error ? e.message : String(e)}`,\n { cause: e },\n )\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAmCA,IAAa,SAAb,MAA4C;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,SAAuB,EAAE,EAAE;AACrC,OAAK,MAAM,OAAO;AAClB,OAAK,QAAQ,OAAO;AACpB,OAAK,UAAU,OAAO;AACtB,OAAK,YAAY,OAAO;AACxB,OAAK,YAAY,OAAO;AACxB,OAAK,YAAY,OAAO;AACxB,OAAK,gBAAgB,OAAO;AAC5B,OAAK,cAAc,OAAO;AAC1B,OAAK,UAAU,OAAO;AACtB,OAAK,kBAAkB,OAAO;AAC9B,OAAK,aAAa,OAAO;;;;;;;CAQ3B,SAAiB;AACf,MAAI,KAAK,YAAY,OACnB,OAAM,IAAI,MACR,sFACD;AAEH,MAAI;AACF,UAAO,cAAc,KAAK,QAAQ;WAC3B,GAAG;AACV,SAAM,IAAI,MACR,6CAA6C,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,IACvF,EAAE,OAAO,GAAG,CACb;;;;;;;;CAUL,SAAc;EACZ,MAAM,OAAO,KAAK,QAAQ;AAC1B,MAAI,CAAC,KACH,OAAM,IAAI,MAAM,iDAAiD;AAEnE,MAAI;AACF,UAAO,KAAK,MAAM,KAAK;WAChB,GAAG;AACV,SAAM,IAAI,MACR,2CAA2C,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,IACrF,EAAE,OAAO,GAAG,CACb"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { i as NoRandomSourceError, r as InvalidSaltError } from "./errors-DGB8AgT2.js";
|
|
2
|
+
import { o as ARKIV_ADDRESS } from "./expiry-DGRzrSan.js";
|
|
3
|
+
import { encodePacked, keccak256, parseAbi } from "viem";
|
|
4
|
+
|
|
5
|
+
//#region src/entity/key.ts
|
|
6
|
+
/** The widest salt the `uint128` wire field carries. */
|
|
7
|
+
const MAX_SALT = 2n ** 128n - 1n;
|
|
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
|
+
function randomSalt() {
|
|
18
|
+
const source = globalThis.crypto;
|
|
19
|
+
if (typeof source?.getRandomValues !== "function") throw new NoRandomSourceError();
|
|
20
|
+
const bytes = source.getRandomValues(new Uint8Array(16));
|
|
21
|
+
let salt = 0n;
|
|
22
|
+
for (const byte of bytes) salt = salt << 8n | BigInt(byte);
|
|
23
|
+
return salt;
|
|
24
|
+
}
|
|
25
|
+
/** Validates a caller-supplied salt against the `uint128` wire field. */
|
|
26
|
+
function validateSalt(salt) {
|
|
27
|
+
if (typeof salt !== "bigint" || salt < 0n || salt > MAX_SALT) throw new InvalidSaltError(salt);
|
|
28
|
+
return salt;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The key an entity will be created with, derived exactly as the engine derives it:
|
|
32
|
+
* `keccak256(domain ++ owner ++ nonce ++ salt)`.
|
|
33
|
+
*
|
|
34
|
+
* Deriving it client-side is what lets a batch compose: predict the key of a create, then have a
|
|
35
|
+
* later operation in the same transaction reference it, or store it as a `key` attribute on a
|
|
36
|
+
* sibling entity.
|
|
37
|
+
*/
|
|
38
|
+
function predictEntityKey({ owner, nonce, salt, chainId }) {
|
|
39
|
+
return keccak256(encodePacked([
|
|
40
|
+
"uint256",
|
|
41
|
+
"address",
|
|
42
|
+
"address",
|
|
43
|
+
"uint64",
|
|
44
|
+
"uint128"
|
|
45
|
+
], [
|
|
46
|
+
BigInt(chainId),
|
|
47
|
+
ARKIV_ADDRESS,
|
|
48
|
+
owner,
|
|
49
|
+
nonce,
|
|
50
|
+
validateSalt(salt)
|
|
51
|
+
]));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/entity/events.ts
|
|
56
|
+
/**
|
|
57
|
+
* The events the engine emits: one per applied operation, in application order, so an off-chain
|
|
58
|
+
* replica can replay a transaction operation by operation.
|
|
59
|
+
*/
|
|
60
|
+
const ENTITY_EVENTS_ABI = parseAbi([
|
|
61
|
+
"event EntityCreated(bytes32 indexed entityKey, address indexed owner, uint64 expiresAt, uint8 creationFlags)",
|
|
62
|
+
"event EntityPatched(bytes32 indexed entityKey, address indexed owner)",
|
|
63
|
+
"event ExpiryExtended(bytes32 indexed entityKey, address indexed owner, uint64 expiresAt)",
|
|
64
|
+
"event OwnershipTransferred(bytes32 indexed entityKey, address indexed previousOwner, address indexed newOwner)",
|
|
65
|
+
"event EntityDeleted(bytes32 indexed entityKey, address indexed owner)"
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
export { validateSalt as a, randomSalt as i, MAX_SALT as n, predictEntityKey as r, ENTITY_EVENTS_ABI as t };
|
|
70
|
+
//# sourceMappingURL=entity-CFhbAv0R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-CFhbAv0R.js","names":[],"sources":["../src/entity/key.ts","../src/entity/events.ts"],"sourcesContent":["import { type Address, encodePacked, type Hex, keccak256 } from \"viem\"\nimport { ARKIV_ADDRESS } from \"../consts\"\nimport { InvalidSaltError, NoRandomSourceError } from \"./errors\"\n\n/** The widest salt the `uint128` wire field carries. */\nexport const MAX_SALT = 2n ** 128n - 1n\n\n/**\n * A cryptographically random 128-bit salt — what {@link CreateEntityParameters.salt} defaults to.\n *\n * The salt exists for unpredictability, not uniqueness: the per-owner nonce already guarantees\n * distinct keys. Random bits mean only the creator can predict the key of an entity they are about\n * to create, so nobody else can reference or squat it in advance.\n *\n * @throws {NoRandomSourceError} If the runtime has no `crypto.getRandomValues`.\n */\nexport function randomSalt(): bigint {\n const source = globalThis.crypto\n if (typeof source?.getRandomValues !== \"function\") {\n throw new NoRandomSourceError()\n }\n const bytes = source.getRandomValues(new Uint8Array(16))\n let salt = 0n\n for (const byte of bytes) salt = (salt << 8n) | BigInt(byte)\n return salt\n}\n\n/** Validates a caller-supplied salt against the `uint128` wire field. */\nexport function validateSalt(salt: bigint): bigint {\n if (typeof salt !== \"bigint\" || salt < 0n || salt > MAX_SALT) {\n throw new InvalidSaltError(salt)\n }\n return salt\n}\n\n/**\n * The key an entity will be created with, derived exactly as the engine derives it:\n * `keccak256(domain ++ owner ++ nonce ++ salt)`.\n *\n * Deriving it client-side is what lets a batch compose: predict the key of a create, then have a\n * later operation in the same transaction reference it, or store it as a `key` attribute on a\n * sibling entity.\n */\nexport function predictEntityKey({\n owner,\n nonce,\n salt,\n chainId,\n}: {\n /** The account creating the entity. */\n owner: Address\n /**\n * The owner's entity-nonce at creation time. Each create in a batch consumes one, so the *n*th\n * create in a batch uses `nonce + n`.\n */\n nonce: bigint\n /** The salt the create will carry. */\n salt: bigint\n /** The chain the entity is being created on — half of the derivation domain. */\n chainId: number\n}): Hex {\n return keccak256(\n encodePacked(\n [\"uint256\", \"address\", \"address\", \"uint64\", \"uint128\"],\n [BigInt(chainId), ARKIV_ADDRESS, owner, nonce, validateSalt(salt)],\n ),\n )\n}\n","import { parseAbi } from \"viem\"\n\n/**\n * The events the engine emits: one per applied operation, in application order, so an off-chain\n * replica can replay a transaction operation by operation.\n */\nexport const ENTITY_EVENTS_ABI = parseAbi([\n \"event EntityCreated(bytes32 indexed entityKey, address indexed owner, uint64 expiresAt, uint8 creationFlags)\",\n \"event EntityPatched(bytes32 indexed entityKey, address indexed owner)\",\n \"event ExpiryExtended(bytes32 indexed entityKey, address indexed owner, uint64 expiresAt)\",\n \"event OwnershipTransferred(bytes32 indexed entityKey, address indexed previousOwner, address indexed newOwner)\",\n \"event EntityDeleted(bytes32 indexed entityKey, address indexed owner)\",\n])\n\n/** The name of each event in {@link ENTITY_EVENTS_ABI}. */\nexport type EntityEventName = (typeof ENTITY_EVENTS_ABI)[number][\"name\"]\n"],"mappings":";;;;;;AAKA,MAAa,WAAW,MAAM,OAAO;;;;;;;;;;AAWrC,SAAgB,aAAqB;CACnC,MAAM,SAAS,WAAW;AAC1B,KAAI,OAAO,QAAQ,oBAAoB,WACrC,OAAM,IAAI,qBAAqB;CAEjC,MAAM,QAAQ,OAAO,gBAAgB,IAAI,WAAW,GAAG,CAAC;CACxD,IAAI,OAAO;AACX,MAAK,MAAM,QAAQ,MAAO,QAAQ,QAAQ,KAAM,OAAO,KAAK;AAC5D,QAAO;;;AAIT,SAAgB,aAAa,MAAsB;AACjD,KAAI,OAAO,SAAS,YAAY,OAAO,MAAM,OAAO,SAClD,OAAM,IAAI,iBAAiB,KAAK;AAElC,QAAO;;;;;;;;;;AAWT,SAAgB,iBAAiB,EAC/B,OACA,OACA,MACA,WAaM;AACN,QAAO,UACL,aACE;EAAC;EAAW;EAAW;EAAW;EAAU;EAAU,EACtD;EAAC,OAAO,QAAQ;EAAE;EAAe;EAAO;EAAO,aAAa,KAAK;EAAC,CACnE,CACF;;;;;;;;;AC5DH,MAAa,oBAAoB,SAAS;CACxC;CACA;CACA;CACA;CACA;CACD,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const require_errors = require('./errors-D-eJkGVc.cjs');
|
|
2
|
+
const require_expiry = require('./expiry-BUYIXTGb.cjs');
|
|
3
|
+
let viem = require("viem");
|
|
4
|
+
|
|
5
|
+
//#region src/entity/key.ts
|
|
6
|
+
/** The widest salt the `uint128` wire field carries. */
|
|
7
|
+
const MAX_SALT = 2n ** 128n - 1n;
|
|
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
|
+
function randomSalt() {
|
|
18
|
+
const source = globalThis.crypto;
|
|
19
|
+
if (typeof source?.getRandomValues !== "function") throw new require_errors.NoRandomSourceError();
|
|
20
|
+
const bytes = source.getRandomValues(new Uint8Array(16));
|
|
21
|
+
let salt = 0n;
|
|
22
|
+
for (const byte of bytes) salt = salt << 8n | BigInt(byte);
|
|
23
|
+
return salt;
|
|
24
|
+
}
|
|
25
|
+
/** Validates a caller-supplied salt against the `uint128` wire field. */
|
|
26
|
+
function validateSalt(salt) {
|
|
27
|
+
if (typeof salt !== "bigint" || salt < 0n || salt > MAX_SALT) throw new require_errors.InvalidSaltError(salt);
|
|
28
|
+
return salt;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The key an entity will be created with, derived exactly as the engine derives it:
|
|
32
|
+
* `keccak256(domain ++ owner ++ nonce ++ salt)`.
|
|
33
|
+
*
|
|
34
|
+
* Deriving it client-side is what lets a batch compose: predict the key of a create, then have a
|
|
35
|
+
* later operation in the same transaction reference it, or store it as a `key` attribute on a
|
|
36
|
+
* sibling entity.
|
|
37
|
+
*/
|
|
38
|
+
function predictEntityKey({ owner, nonce, salt, chainId }) {
|
|
39
|
+
return (0, viem.keccak256)((0, viem.encodePacked)([
|
|
40
|
+
"uint256",
|
|
41
|
+
"address",
|
|
42
|
+
"address",
|
|
43
|
+
"uint64",
|
|
44
|
+
"uint128"
|
|
45
|
+
], [
|
|
46
|
+
BigInt(chainId),
|
|
47
|
+
require_expiry.ARKIV_ADDRESS,
|
|
48
|
+
owner,
|
|
49
|
+
nonce,
|
|
50
|
+
validateSalt(salt)
|
|
51
|
+
]));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/entity/events.ts
|
|
56
|
+
/**
|
|
57
|
+
* The events the engine emits: one per applied operation, in application order, so an off-chain
|
|
58
|
+
* replica can replay a transaction operation by operation.
|
|
59
|
+
*/
|
|
60
|
+
const ENTITY_EVENTS_ABI = (0, viem.parseAbi)([
|
|
61
|
+
"event EntityCreated(bytes32 indexed entityKey, address indexed owner, uint64 expiresAt, uint8 creationFlags)",
|
|
62
|
+
"event EntityPatched(bytes32 indexed entityKey, address indexed owner)",
|
|
63
|
+
"event ExpiryExtended(bytes32 indexed entityKey, address indexed owner, uint64 expiresAt)",
|
|
64
|
+
"event OwnershipTransferred(bytes32 indexed entityKey, address indexed previousOwner, address indexed newOwner)",
|
|
65
|
+
"event EntityDeleted(bytes32 indexed entityKey, address indexed owner)"
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
Object.defineProperty(exports, 'ENTITY_EVENTS_ABI', {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
get: function () {
|
|
72
|
+
return ENTITY_EVENTS_ABI;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
Object.defineProperty(exports, 'MAX_SALT', {
|
|
76
|
+
enumerable: true,
|
|
77
|
+
get: function () {
|
|
78
|
+
return MAX_SALT;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
Object.defineProperty(exports, 'predictEntityKey', {
|
|
82
|
+
enumerable: true,
|
|
83
|
+
get: function () {
|
|
84
|
+
return predictEntityKey;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
Object.defineProperty(exports, 'randomSalt', {
|
|
88
|
+
enumerable: true,
|
|
89
|
+
get: function () {
|
|
90
|
+
return randomSalt;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
Object.defineProperty(exports, 'validateSalt', {
|
|
94
|
+
enumerable: true,
|
|
95
|
+
get: function () {
|
|
96
|
+
return validateSalt;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/entity/errors.ts
|
|
3
|
+
/** An expiry could not be expressed, or would be rejected by the engine. */
|
|
4
|
+
var InvalidExpiryError = class extends Error {
|
|
5
|
+
constructor(reason) {
|
|
6
|
+
super(`Invalid expiry: ${reason}`);
|
|
7
|
+
this.name = "InvalidExpiryError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
/** A `creationFlags` byte could not be built or read. */
|
|
11
|
+
var InvalidCreationFlagsError = class extends Error {
|
|
12
|
+
constructor(reason) {
|
|
13
|
+
super(`Invalid creation flags: ${reason}`);
|
|
14
|
+
this.name = "InvalidCreationFlagsError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/** A key salt was outside the `uint128` the wire carries. */
|
|
18
|
+
var InvalidSaltError = class extends Error {
|
|
19
|
+
constructor(salt) {
|
|
20
|
+
super(`Invalid salt ${salt}. A salt is a uint128, so it must be between 0 and ${2n ** 128n - 1n}. Omit it to get 128 random bits, or pass 0 for a predictable key.`);
|
|
21
|
+
this.name = "InvalidSaltError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/** No cryptographic randomness was available to generate a salt. */
|
|
25
|
+
var NoRandomSourceError = class extends Error {
|
|
26
|
+
constructor() {
|
|
27
|
+
super("No cryptographic random source is available (globalThis.crypto.getRandomValues is missing), so a key salt cannot be generated. Upgrade to Node 18+ or a modern browser, or pass an explicit salt.");
|
|
28
|
+
this.name = "NoRandomSourceError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
Object.defineProperty(exports, 'InvalidCreationFlagsError', {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
get: function () {
|
|
36
|
+
return InvalidCreationFlagsError;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(exports, 'InvalidExpiryError', {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
get: function () {
|
|
42
|
+
return InvalidExpiryError;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(exports, 'InvalidSaltError', {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
get: function () {
|
|
48
|
+
return InvalidSaltError;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
Object.defineProperty(exports, 'NoRandomSourceError', {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
get: function () {
|
|
54
|
+
return NoRandomSourceError;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/entity/errors.ts
|
|
2
|
+
/** An expiry could not be expressed, or would be rejected by the engine. */
|
|
3
|
+
var InvalidExpiryError = class extends Error {
|
|
4
|
+
constructor(reason) {
|
|
5
|
+
super(`Invalid expiry: ${reason}`);
|
|
6
|
+
this.name = "InvalidExpiryError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
/** A `creationFlags` byte could not be built or read. */
|
|
10
|
+
var InvalidCreationFlagsError = class extends Error {
|
|
11
|
+
constructor(reason) {
|
|
12
|
+
super(`Invalid creation flags: ${reason}`);
|
|
13
|
+
this.name = "InvalidCreationFlagsError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/** A key salt was outside the `uint128` the wire carries. */
|
|
17
|
+
var InvalidSaltError = class extends Error {
|
|
18
|
+
constructor(salt) {
|
|
19
|
+
super(`Invalid salt ${salt}. A salt is a uint128, so it must be between 0 and ${2n ** 128n - 1n}. Omit it to get 128 random bits, or pass 0 for a predictable key.`);
|
|
20
|
+
this.name = "InvalidSaltError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** No cryptographic randomness was available to generate a salt. */
|
|
24
|
+
var NoRandomSourceError = class extends Error {
|
|
25
|
+
constructor() {
|
|
26
|
+
super("No cryptographic random source is available (globalThis.crypto.getRandomValues is missing), so a key salt cannot be generated. Upgrade to Node 18+ or a modern browser, or pass an explicit salt.");
|
|
27
|
+
this.name = "NoRandomSourceError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { NoRandomSourceError as i, InvalidExpiryError as n, InvalidSaltError as r, InvalidCreationFlagsError as t };
|
|
33
|
+
//# sourceMappingURL=errors-DGB8AgT2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-DGB8AgT2.js","names":[],"sources":["../src/entity/errors.ts"],"sourcesContent":["/** An expiry could not be expressed, or would be rejected by the engine. */\nexport class InvalidExpiryError extends Error {\n constructor(reason: string) {\n super(`Invalid expiry: ${reason}`)\n this.name = \"InvalidExpiryError\"\n }\n}\n\n/** A `creationFlags` byte could not be built or read. */\nexport class InvalidCreationFlagsError extends Error {\n constructor(reason: string) {\n super(`Invalid creation flags: ${reason}`)\n this.name = \"InvalidCreationFlagsError\"\n }\n}\n\n/** A key salt was outside the `uint128` the wire carries. */\nexport class InvalidSaltError extends Error {\n constructor(salt: bigint) {\n super(\n `Invalid salt ${salt}. A salt is a uint128, so it must be between 0 and ` +\n `${2n ** 128n - 1n}. Omit it to get 128 random bits, or pass 0 for a predictable key.`,\n )\n this.name = \"InvalidSaltError\"\n }\n}\n\n/** No cryptographic randomness was available to generate a salt. */\nexport class NoRandomSourceError extends Error {\n constructor() {\n super(\n \"No cryptographic random source is available (globalThis.crypto.getRandomValues is \" +\n \"missing), so a key salt cannot be generated. Upgrade to Node 18+ or a modern browser, \" +\n \"or pass an explicit salt.\",\n )\n this.name = \"NoRandomSourceError\"\n }\n}\n"],"mappings":";;AACA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,QAAgB;AAC1B,QAAM,mBAAmB,SAAS;AAClC,OAAK,OAAO;;;;AAKhB,IAAa,4BAAb,cAA+C,MAAM;CACnD,YAAY,QAAgB;AAC1B,QAAM,2BAA2B,SAAS;AAC1C,OAAK,OAAO;;;;AAKhB,IAAa,mBAAb,cAAsC,MAAM;CAC1C,YAAY,MAAc;AACxB,QACE,gBAAgB,KAAK,qDAChB,MAAM,OAAO,GAAG,oEACtB;AACD,OAAK,OAAO;;;;AAKhB,IAAa,sBAAb,cAAyC,MAAM;CAC7C,cAAc;AACZ,QACE,oMAGD;AACD,OAAK,OAAO"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const require_errors = require('./errors-D-eJkGVc.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/consts.ts
|
|
4
|
+
const ARKIV_ADDRESS = "0x4400000000000000000000000000000000000044";
|
|
5
|
+
const BLOCK_TIME = 2;
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/entity/params.ts
|
|
9
|
+
/**
|
|
10
|
+
* Max uint64 value
|
|
11
|
+
*/
|
|
12
|
+
const MAX_EXPIRES_AT = 2n ** 64n - 1n;
|
|
13
|
+
/** The operation tags of the `execute` ABI's tagged union. */
|
|
14
|
+
const OperationType = {
|
|
15
|
+
Create: 1,
|
|
16
|
+
Patch: 2,
|
|
17
|
+
ExtendExpiry: 3,
|
|
18
|
+
TransferOwnership: 4,
|
|
19
|
+
Delete: 5
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/entity/expiry.ts
|
|
24
|
+
/**
|
|
25
|
+
* Builds a branded {@link Expiry}.
|
|
26
|
+
*
|
|
27
|
+
* Internal seam for {@link ExpirationTime}, which is the only thing that should call it — it is not
|
|
28
|
+
* part of the package's public surface.
|
|
29
|
+
*/
|
|
30
|
+
function makeExpiry(minLifetime, expiresAt) {
|
|
31
|
+
return {
|
|
32
|
+
minLifetime,
|
|
33
|
+
expiresAt
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves an expiry into the wire pair and the block the entity will expire at.
|
|
38
|
+
*
|
|
39
|
+
* Durations are converted to blocks at the nominal `BLOCK_TIME` (2 seconds), which makes them
|
|
40
|
+
* **approximate**: block production is not a clock, so a lifetime asked for in days drifts if the
|
|
41
|
+
* chain runs fast or slow. The block it was converted to comes back as
|
|
42
|
+
* {@link ResolvedExpiry.target}, so the approximation is always observable rather than assumed.
|
|
43
|
+
*
|
|
44
|
+
* @throws {InvalidExpiryError} If the expiry is malformed, does not fit the `uint64` the wire
|
|
45
|
+
* carries, or would leave the entity dead on arrival.
|
|
46
|
+
*/
|
|
47
|
+
function resolveExpiry(expiry, context) {
|
|
48
|
+
const { currentBlock } = context;
|
|
49
|
+
if (typeof expiry?.minLifetime !== "bigint" || expiry.expiresAt === void 0) throw new require_errors.InvalidExpiryError("expires must be built with ExpirationTime, e.g. ExpirationTime.fromDays(30) or ExpirationTime.atBlock(1_200_000n)");
|
|
50
|
+
const { minLifetime } = expiry;
|
|
51
|
+
const absolute = typeof expiry.expiresAt === "bigint" ? expiry.expiresAt : blockAtDate(expiry.expiresAt, currentBlock);
|
|
52
|
+
if (minLifetime === 0n && absolute === 0n) throw new require_errors.InvalidExpiryError("no expiry given. Pass ExpirationTime.fromDays(30) for a lifetime, ExpirationTime.atBlock(n) for an absolute deadline, or a deadline with { atLeast } to take whichever is later.");
|
|
53
|
+
if (absolute > MAX_EXPIRES_AT) throw new require_errors.InvalidExpiryError(`expiresAt resolves to block ${absolute}, which does not fit the uint64 the wire carries (max ${MAX_EXPIRES_AT}). Use ExpirationTime.permanent() for an entity that should not expire.`);
|
|
54
|
+
if (minLifetime > MAX_EXPIRES_AT) throw new require_errors.InvalidExpiryError(`the lifetime is ${minLifetime} blocks, which does not fit the uint64 the wire carries (max ${MAX_EXPIRES_AT})`);
|
|
55
|
+
const relative = minLifetime === 0n ? 0n : currentBlock + minLifetime;
|
|
56
|
+
const target = absolute > relative ? absolute : relative;
|
|
57
|
+
if (target <= currentBlock) throw new require_errors.InvalidExpiryError(`the entity would expire at block ${target}, which is not after the current block ${currentBlock}, so it would be dead on arrival`);
|
|
58
|
+
if (target > MAX_EXPIRES_AT) throw new require_errors.InvalidExpiryError(`a lifetime of ${minLifetime} blocks from block ${currentBlock} lands at ${target}, past the largest expiry the wire carries (${MAX_EXPIRES_AT})`);
|
|
59
|
+
return {
|
|
60
|
+
expiresAt: absolute,
|
|
61
|
+
minLifetime,
|
|
62
|
+
target
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A duration in seconds as a whole number of blocks.
|
|
67
|
+
*
|
|
68
|
+
* Arkiv counts lifetimes in blocks, so a duration that is not a whole number of them does not
|
|
69
|
+
* describe anything the chain can store. Rather than silently round a caller's `3` up to two blocks
|
|
70
|
+
* — four seconds, not what they asked for — this rejects it: a duration must be a positive multiple
|
|
71
|
+
* of `BLOCK_TIME` (2 seconds). The {@link ExpirationTime} helpers all produce such multiples.
|
|
72
|
+
*
|
|
73
|
+
* @throws {InvalidExpiryError} If the duration is not a positive whole number of blocks' seconds.
|
|
74
|
+
*/
|
|
75
|
+
function toBlocks(seconds) {
|
|
76
|
+
if (!Number.isInteger(seconds) || seconds <= 0) throw new require_errors.InvalidExpiryError(`a lifetime must be a positive whole number of seconds, got ${seconds}. Use the ExpirationTime helpers, e.g. ExpirationTime.fromDays(30).`);
|
|
77
|
+
if (seconds % BLOCK_TIME !== 0) throw new require_errors.InvalidExpiryError(`a lifetime must be a multiple of the ${BLOCK_TIME}s block time, got ${seconds}, which is ${seconds / BLOCK_TIME} blocks. Try ${seconds - seconds % BLOCK_TIME} or ${seconds + BLOCK_TIME - seconds % BLOCK_TIME}, or ExpirationTime.fromBlocks(n).`);
|
|
78
|
+
return BigInt(seconds / BLOCK_TIME);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The block a wall-clock instant lands on.
|
|
82
|
+
*
|
|
83
|
+
* Rounds **up**: a date almost never falls on a block boundary, and unlike a duration — which the
|
|
84
|
+
* caller states and can state exactly — there is nothing here to reject, so the entity lives until
|
|
85
|
+
* at least the instant asked for.
|
|
86
|
+
*/
|
|
87
|
+
function blockAtDate(date, currentBlock) {
|
|
88
|
+
const secondsAway = (date.getTime() - Date.now()) / 1e3;
|
|
89
|
+
if (secondsAway <= 0) throw new require_errors.InvalidExpiryError(`the deadline ${date.toISOString()} is in the past, so the entity would be dead on arrival`);
|
|
90
|
+
return currentBlock + BigInt(Math.ceil(secondsAway / BLOCK_TIME));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
Object.defineProperty(exports, 'ARKIV_ADDRESS', {
|
|
95
|
+
enumerable: true,
|
|
96
|
+
get: function () {
|
|
97
|
+
return ARKIV_ADDRESS;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
Object.defineProperty(exports, 'BLOCK_TIME', {
|
|
101
|
+
enumerable: true,
|
|
102
|
+
get: function () {
|
|
103
|
+
return BLOCK_TIME;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
Object.defineProperty(exports, 'MAX_EXPIRES_AT', {
|
|
107
|
+
enumerable: true,
|
|
108
|
+
get: function () {
|
|
109
|
+
return MAX_EXPIRES_AT;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
Object.defineProperty(exports, 'OperationType', {
|
|
113
|
+
enumerable: true,
|
|
114
|
+
get: function () {
|
|
115
|
+
return OperationType;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
Object.defineProperty(exports, 'makeExpiry', {
|
|
119
|
+
enumerable: true,
|
|
120
|
+
get: function () {
|
|
121
|
+
return makeExpiry;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
Object.defineProperty(exports, 'resolveExpiry', {
|
|
125
|
+
enumerable: true,
|
|
126
|
+
get: function () {
|
|
127
|
+
return resolveExpiry;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
Object.defineProperty(exports, 'toBlocks', {
|
|
131
|
+
enumerable: true,
|
|
132
|
+
get: function () {
|
|
133
|
+
return toBlocks;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//#region src/entity/expiry.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The brand. Module-private and never exported, so the only way to obtain an {@link Expiry} is to
|
|
4
|
+
* build one with {@link ExpirationTime} — a hand-written `{ minLifetime, expiresAt }` literal will
|
|
5
|
+
* not typecheck, and neither will a bare number of seconds.
|
|
6
|
+
*/
|
|
7
|
+
declare const validated: unique symbol;
|
|
8
|
+
/**
|
|
9
|
+
* How long an entity should live.
|
|
10
|
+
*
|
|
11
|
+
* This is the pair the ABI carries, and the engine resolves it as
|
|
12
|
+
* `max(expiresAt, currentBlock + minLifetime)` — whichever is later. Build one with
|
|
13
|
+
* {@link ExpirationTime}; there is no other way to make one.
|
|
14
|
+
*
|
|
15
|
+
* Which slot a value lands in also decides which rules govern it. `minLifetime` is a duration, so
|
|
16
|
+
* it must come to a whole number of blocks. `expiresAt` is a block height, which is a whole number
|
|
17
|
+
* by construction, so a wall-clock date rounds into it rather than being rejected.
|
|
18
|
+
*/
|
|
19
|
+
type Expiry = {
|
|
20
|
+
/** Lifetime floor in blocks, counted from the block the transaction lands in. `0n` if unset. */
|
|
21
|
+
readonly minLifetime: bigint;
|
|
22
|
+
/**
|
|
23
|
+
* Absolute deadline: a block height, or a `Date` still to be placed against the current block.
|
|
24
|
+
* `0n` if unset.
|
|
25
|
+
*/
|
|
26
|
+
readonly expiresAt: bigint | Date;
|
|
27
|
+
readonly [validated]: true;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* An expiry that is nothing but a duration — what the `ExpirationTime.from*` helpers return.
|
|
31
|
+
*
|
|
32
|
+
* Distinguished from a full {@link Expiry} by its `expiresAt` being the literal `0n`, which is what
|
|
33
|
+
* keeps `atBlock(x, { atLeast: atBlock(y) })` from compiling: a deadline is not a floor.
|
|
34
|
+
*/
|
|
35
|
+
type Lifetime = Expiry & {
|
|
36
|
+
readonly expiresAt: 0n;
|
|
37
|
+
};
|
|
38
|
+
/** The two `uint256` values the ABI carries, with every `Date` already placed. */
|
|
39
|
+
type EncodedExpiry = {
|
|
40
|
+
/** Absolute target block height, or `0n` for a purely relative lifetime. */
|
|
41
|
+
expiresAt: bigint;
|
|
42
|
+
/** Relative floor in blocks, or `0n` for a purely absolute expiry. */
|
|
43
|
+
minLifetime: bigint;
|
|
44
|
+
};
|
|
45
|
+
/** What an expiry resolves to, given the chain's current block. */
|
|
46
|
+
type ResolvedExpiry = EncodedExpiry & {
|
|
47
|
+
/** The block the entity will expire at — `max(expiresAt, currentBlock + minLifetime)`. */
|
|
48
|
+
target: bigint;
|
|
49
|
+
};
|
|
50
|
+
/** What the SDK needs to know about the chain to resolve an expiry. */
|
|
51
|
+
type ExpiryContext = {
|
|
52
|
+
currentBlock: bigint;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Resolves an expiry into the wire pair and the block the entity will expire at.
|
|
56
|
+
*
|
|
57
|
+
* Durations are converted to blocks at the nominal `BLOCK_TIME` (2 seconds), which makes them
|
|
58
|
+
* **approximate**: block production is not a clock, so a lifetime asked for in days drifts if the
|
|
59
|
+
* chain runs fast or slow. The block it was converted to comes back as
|
|
60
|
+
* {@link ResolvedExpiry.target}, so the approximation is always observable rather than assumed.
|
|
61
|
+
*
|
|
62
|
+
* @throws {InvalidExpiryError} If the expiry is malformed, does not fit the `uint64` the wire
|
|
63
|
+
* carries, or would leave the entity dead on arrival.
|
|
64
|
+
*/
|
|
65
|
+
declare function resolveExpiry(expiry: Expiry, context: ExpiryContext): ResolvedExpiry;
|
|
66
|
+
/**
|
|
67
|
+
* A duration in seconds as a whole number of blocks.
|
|
68
|
+
*
|
|
69
|
+
* Arkiv counts lifetimes in blocks, so a duration that is not a whole number of them does not
|
|
70
|
+
* describe anything the chain can store. Rather than silently round a caller's `3` up to two blocks
|
|
71
|
+
* — four seconds, not what they asked for — this rejects it: a duration must be a positive multiple
|
|
72
|
+
* of `BLOCK_TIME` (2 seconds). The {@link ExpirationTime} helpers all produce such multiples.
|
|
73
|
+
*
|
|
74
|
+
* @throws {InvalidExpiryError} If the duration is not a positive whole number of blocks' seconds.
|
|
75
|
+
*/
|
|
76
|
+
declare function toBlocks(seconds: number): bigint;
|
|
77
|
+
//#endregion
|
|
78
|
+
export { ResolvedExpiry as a, Lifetime as i, Expiry as n, resolveExpiry as o, ExpiryContext as r, toBlocks as s, EncodedExpiry as t };
|
|
79
|
+
//# sourceMappingURL=expiry-BxOlvVWC.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry-BxOlvVWC.d.ts","names":[],"sources":["../src/entity/expiry.ts"],"sourcesContent":[],"mappings":";;;AAsBA;AAiBA;AAaA;AAQA,cAnDc,SAmDY,EAAA,OAAA,MAAG;AAM7B;AAeA;;;;;AAmEA;;;;;KA9HY,MAAA;;;;;;;+BAOmB;YACnB,SAAA;;;;;;;;KASA,QAAA,GAAW;;;;KAaX,aAAA;;;;;;;KAQA,cAAA,GAAiB;;;;;KAMjB,aAAA;;;;;;;;;;;;;;iBAeI,aAAA,SAAsB,iBAAiB,gBAAgB;;;;;;;;;;;iBAmEvD,QAAA"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { n as InvalidExpiryError } from "./errors-DGB8AgT2.js";
|
|
2
|
+
|
|
3
|
+
//#region src/consts.ts
|
|
4
|
+
const ARKIV_ADDRESS = "0x4400000000000000000000000000000000000044";
|
|
5
|
+
const BLOCK_TIME = 2;
|
|
6
|
+
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/entity/params.ts
|
|
9
|
+
/**
|
|
10
|
+
* Max uint64 value
|
|
11
|
+
*/
|
|
12
|
+
const MAX_EXPIRES_AT = 2n ** 64n - 1n;
|
|
13
|
+
/** The operation tags of the `execute` ABI's tagged union. */
|
|
14
|
+
const OperationType = {
|
|
15
|
+
Create: 1,
|
|
16
|
+
Patch: 2,
|
|
17
|
+
ExtendExpiry: 3,
|
|
18
|
+
TransferOwnership: 4,
|
|
19
|
+
Delete: 5
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/entity/expiry.ts
|
|
24
|
+
/**
|
|
25
|
+
* Builds a branded {@link Expiry}.
|
|
26
|
+
*
|
|
27
|
+
* Internal seam for {@link ExpirationTime}, which is the only thing that should call it — it is not
|
|
28
|
+
* part of the package's public surface.
|
|
29
|
+
*/
|
|
30
|
+
function makeExpiry(minLifetime, expiresAt) {
|
|
31
|
+
return {
|
|
32
|
+
minLifetime,
|
|
33
|
+
expiresAt
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves an expiry into the wire pair and the block the entity will expire at.
|
|
38
|
+
*
|
|
39
|
+
* Durations are converted to blocks at the nominal `BLOCK_TIME` (2 seconds), which makes them
|
|
40
|
+
* **approximate**: block production is not a clock, so a lifetime asked for in days drifts if the
|
|
41
|
+
* chain runs fast or slow. The block it was converted to comes back as
|
|
42
|
+
* {@link ResolvedExpiry.target}, so the approximation is always observable rather than assumed.
|
|
43
|
+
*
|
|
44
|
+
* @throws {InvalidExpiryError} If the expiry is malformed, does not fit the `uint64` the wire
|
|
45
|
+
* carries, or would leave the entity dead on arrival.
|
|
46
|
+
*/
|
|
47
|
+
function resolveExpiry(expiry, context) {
|
|
48
|
+
const { currentBlock } = context;
|
|
49
|
+
if (typeof expiry?.minLifetime !== "bigint" || expiry.expiresAt === void 0) throw new InvalidExpiryError("expires must be built with ExpirationTime, e.g. ExpirationTime.fromDays(30) or ExpirationTime.atBlock(1_200_000n)");
|
|
50
|
+
const { minLifetime } = expiry;
|
|
51
|
+
const absolute = typeof expiry.expiresAt === "bigint" ? expiry.expiresAt : blockAtDate(expiry.expiresAt, currentBlock);
|
|
52
|
+
if (minLifetime === 0n && absolute === 0n) throw new InvalidExpiryError("no expiry given. Pass ExpirationTime.fromDays(30) for a lifetime, ExpirationTime.atBlock(n) for an absolute deadline, or a deadline with { atLeast } to take whichever is later.");
|
|
53
|
+
if (absolute > MAX_EXPIRES_AT) throw new InvalidExpiryError(`expiresAt resolves to block ${absolute}, which does not fit the uint64 the wire carries (max ${MAX_EXPIRES_AT}). Use ExpirationTime.permanent() for an entity that should not expire.`);
|
|
54
|
+
if (minLifetime > MAX_EXPIRES_AT) throw new InvalidExpiryError(`the lifetime is ${minLifetime} blocks, which does not fit the uint64 the wire carries (max ${MAX_EXPIRES_AT})`);
|
|
55
|
+
const relative = minLifetime === 0n ? 0n : currentBlock + minLifetime;
|
|
56
|
+
const target = absolute > relative ? absolute : relative;
|
|
57
|
+
if (target <= currentBlock) throw new InvalidExpiryError(`the entity would expire at block ${target}, which is not after the current block ${currentBlock}, so it would be dead on arrival`);
|
|
58
|
+
if (target > MAX_EXPIRES_AT) throw new InvalidExpiryError(`a lifetime of ${minLifetime} blocks from block ${currentBlock} lands at ${target}, past the largest expiry the wire carries (${MAX_EXPIRES_AT})`);
|
|
59
|
+
return {
|
|
60
|
+
expiresAt: absolute,
|
|
61
|
+
minLifetime,
|
|
62
|
+
target
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A duration in seconds as a whole number of blocks.
|
|
67
|
+
*
|
|
68
|
+
* Arkiv counts lifetimes in blocks, so a duration that is not a whole number of them does not
|
|
69
|
+
* describe anything the chain can store. Rather than silently round a caller's `3` up to two blocks
|
|
70
|
+
* — four seconds, not what they asked for — this rejects it: a duration must be a positive multiple
|
|
71
|
+
* of `BLOCK_TIME` (2 seconds). The {@link ExpirationTime} helpers all produce such multiples.
|
|
72
|
+
*
|
|
73
|
+
* @throws {InvalidExpiryError} If the duration is not a positive whole number of blocks' seconds.
|
|
74
|
+
*/
|
|
75
|
+
function toBlocks(seconds) {
|
|
76
|
+
if (!Number.isInteger(seconds) || seconds <= 0) throw new InvalidExpiryError(`a lifetime must be a positive whole number of seconds, got ${seconds}. Use the ExpirationTime helpers, e.g. ExpirationTime.fromDays(30).`);
|
|
77
|
+
if (seconds % BLOCK_TIME !== 0) throw new InvalidExpiryError(`a lifetime must be a multiple of the ${BLOCK_TIME}s block time, got ${seconds}, which is ${seconds / BLOCK_TIME} blocks. Try ${seconds - seconds % BLOCK_TIME} or ${seconds + BLOCK_TIME - seconds % BLOCK_TIME}, or ExpirationTime.fromBlocks(n).`);
|
|
78
|
+
return BigInt(seconds / BLOCK_TIME);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The block a wall-clock instant lands on.
|
|
82
|
+
*
|
|
83
|
+
* Rounds **up**: a date almost never falls on a block boundary, and unlike a duration — which the
|
|
84
|
+
* caller states and can state exactly — there is nothing here to reject, so the entity lives until
|
|
85
|
+
* at least the instant asked for.
|
|
86
|
+
*/
|
|
87
|
+
function blockAtDate(date, currentBlock) {
|
|
88
|
+
const secondsAway = (date.getTime() - Date.now()) / 1e3;
|
|
89
|
+
if (secondsAway <= 0) throw new InvalidExpiryError(`the deadline ${date.toISOString()} is in the past, so the entity would be dead on arrival`);
|
|
90
|
+
return currentBlock + BigInt(Math.ceil(secondsAway / BLOCK_TIME));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { OperationType as a, MAX_EXPIRES_AT as i, resolveExpiry as n, ARKIV_ADDRESS as o, toBlocks as r, BLOCK_TIME as s, makeExpiry as t };
|
|
95
|
+
//# sourceMappingURL=expiry-DGRzrSan.js.map
|