@wiimdy/openfunderse-sdk 0.1.2 → 1.0.0

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.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Canonical weight scale for allocation claims (1e18).
3
+ * Every claim's `targetWeights` MUST sum to exactly this value.
4
+ * Example: 50/50 split = [500000000000000000n, 500000000000000000n]
5
+ */
6
+ export declare const CLAIM_WEIGHT_SCALE = 1000000000000000000n;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Canonical weight scale for allocation claims (1e18).
3
+ * Every claim's `targetWeights` MUST sum to exactly this value.
4
+ * Example: 50/50 split = [500000000000000000n, 500000000000000000n]
5
+ */
6
+ export const CLAIM_WEIGHT_SCALE = 1000000000000000000n;
package/dist/hash.d.ts CHANGED
@@ -4,8 +4,21 @@ export declare function intentHash(intent: TradeIntent): Hex;
4
4
  export declare function snapshotHash(epochId: bigint, orderedClaimHashes: Hex[]): Hex;
5
5
  export declare function canonicalOrderedClaimHashes(claimHashes: Hex[]): Hex[];
6
6
  export declare function snapshotHashFromUnordered(epochId: bigint, claimHashes: Hex[]): Hex;
7
- export declare const epochStateHash: typeof snapshotHash;
8
- export declare const epochStateHashFromUnordered: typeof snapshotHashFromUnordered;
7
+ /**
8
+ * Deterministic Merkle root for strictly-sorted bytes32 leaves using commutative keccak256.
9
+ * - Leaves must be strictly sorted ascending with no duplicates.
10
+ * - If a layer has an odd number of nodes, the last node is duplicated.
11
+ */
12
+ export declare function merkleRoot(orderedLeaves: Hex[]): Hex;
13
+ export declare function merkleRootFromUnorderedLeaves(leaves: Hex[]): Hex;
14
+ /**
15
+ * Epoch state hash used as the onchain/offchain snapshot root.
16
+ * Compatible with {SnapshotBook} + {IntentBook} snapshot gating.
17
+ */
18
+ export declare function epochStateHash(epochId: bigint, orderedClaimHashes: Hex[]): Hex;
19
+ export declare function epochStateHashFromUnordered(epochId: bigint, claimHashes: Hex[]): Hex;
20
+ export declare function merkleProof(orderedLeaves: Hex[], leaf: Hex): Hex[];
21
+ export declare function merkleProofFromUnorderedLeaves(leaves: Hex[], leaf: Hex): Hex[];
9
22
  export declare function reasonHash(reason: string): Hex;
10
23
  /**
11
24
  * Canonical allowlist hash for intent execution route.
package/dist/hash.js CHANGED
@@ -59,8 +59,85 @@ export function snapshotHashFromUnordered(epochId, claimHashes) {
59
59
  const orderedClaimHashes = canonicalOrderedClaimHashes(claimHashes);
60
60
  return snapshotHash(epochId, orderedClaimHashes);
61
61
  }
62
- export const epochStateHash = snapshotHash;
63
- export const epochStateHashFromUnordered = snapshotHashFromUnordered;
62
+ const MERKLE_NODE_PAIR = parseAbiParameters("bytes32 a, bytes32 b");
63
+ const EPOCH_STATE_HASH_INPUT = parseAbiParameters("uint64 epochId,bytes32 merkleRoot");
64
+ function merkleHashPair(a, b) {
65
+ const left = a.toLowerCase() < b.toLowerCase() ? a : b;
66
+ const right = left === a ? b : a;
67
+ return keccak256(encodeAbiParameters(MERKLE_NODE_PAIR, [left, right]));
68
+ }
69
+ /**
70
+ * Deterministic Merkle root for strictly-sorted bytes32 leaves using commutative keccak256.
71
+ * - Leaves must be strictly sorted ascending with no duplicates.
72
+ * - If a layer has an odd number of nodes, the last node is duplicated.
73
+ */
74
+ export function merkleRoot(orderedLeaves) {
75
+ if (orderedLeaves.length === 0) {
76
+ throw new Error("leaves must not be empty");
77
+ }
78
+ assertStrictlySortedHex(orderedLeaves, "orderedLeaves");
79
+ let layer = [...orderedLeaves];
80
+ while (layer.length > 1) {
81
+ const next = [];
82
+ for (let i = 0; i < layer.length; i += 2) {
83
+ const left = layer[i];
84
+ const right = i + 1 < layer.length ? layer[i + 1] : layer[i];
85
+ next.push(merkleHashPair(left, right));
86
+ }
87
+ layer = next;
88
+ }
89
+ return layer[0];
90
+ }
91
+ export function merkleRootFromUnorderedLeaves(leaves) {
92
+ const ordered = uniqueSortedBytes32Hex(leaves);
93
+ return merkleRoot(ordered);
94
+ }
95
+ /**
96
+ * Epoch state hash used as the onchain/offchain snapshot root.
97
+ * Compatible with {SnapshotBook} + {IntentBook} snapshot gating.
98
+ */
99
+ export function epochStateHash(epochId, orderedClaimHashes) {
100
+ assertUint64(epochId, "epochId");
101
+ const root = merkleRoot(orderedClaimHashes);
102
+ return keccak256(encodeAbiParameters(EPOCH_STATE_HASH_INPUT, [epochId, root]));
103
+ }
104
+ export function epochStateHashFromUnordered(epochId, claimHashes) {
105
+ assertUint64(epochId, "epochId");
106
+ const ordered = canonicalOrderedClaimHashes(claimHashes);
107
+ return epochStateHash(epochId, ordered);
108
+ }
109
+ export function merkleProof(orderedLeaves, leaf) {
110
+ assertStrictlySortedHex(orderedLeaves, "orderedLeaves");
111
+ if (orderedLeaves.length === 0) {
112
+ throw new Error("orderedLeaves must not be empty");
113
+ }
114
+ const normalizedLeaf = leaf.toLowerCase();
115
+ const index0 = orderedLeaves.findIndex((entry) => entry.toLowerCase() === normalizedLeaf);
116
+ if (index0 < 0) {
117
+ throw new Error("leaf not found in orderedLeaves");
118
+ }
119
+ const proof = [];
120
+ let index = index0;
121
+ let layer = [...orderedLeaves];
122
+ while (layer.length > 1) {
123
+ const siblingIndex = index ^ 1; // toggle last bit
124
+ const sibling = siblingIndex < layer.length ? layer[siblingIndex] : layer[index];
125
+ proof.push(sibling);
126
+ const next = [];
127
+ for (let i = 0; i < layer.length; i += 2) {
128
+ const left = layer[i];
129
+ const right = i + 1 < layer.length ? layer[i + 1] : layer[i];
130
+ next.push(merkleHashPair(left, right));
131
+ }
132
+ layer = next;
133
+ index = Math.floor(index / 2);
134
+ }
135
+ return proof;
136
+ }
137
+ export function merkleProofFromUnorderedLeaves(leaves, leaf) {
138
+ const ordered = uniqueSortedBytes32Hex(leaves);
139
+ return merkleProof(ordered, leaf);
140
+ }
64
141
  export function reasonHash(reason) {
65
142
  return keccak256(toHex(reason.normalize("NFC").trim()));
66
143
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./types.js";
2
2
  export * from "./nadfun-types.js";
3
+ export * from "./constants.js";
3
4
  export * from "./canonical.js";
4
5
  export * from "./hash.js";
5
6
  export * from "./eip712.js";
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./types.js";
2
2
  export * from "./nadfun-types.js";
3
+ export * from "./constants.js";
3
4
  export * from "./canonical.js";
4
5
  export * from "./hash.js";
5
6
  export * from "./eip712.js";
@@ -1,6 +1,7 @@
1
1
  import { keccak256 } from "viem";
2
2
  import { canonicalAllocationClaim, canonicalIntent } from "./canonical.js";
3
- import { allocationClaimHash, intentExecutionAllowlistHash, intentHash, snapshotHashFromUnordered } from "./hash.js";
3
+ import { CLAIM_WEIGHT_SCALE } from "./constants.js";
4
+ import { allocationClaimHash, intentExecutionAllowlistHash, intentHash, epochStateHashFromUnordered } from "./hash.js";
4
5
  import { assertUint16, assertUint64 } from "./validate.js";
5
6
  function assertPositive(value, label) {
6
7
  if (value <= 0n) {
@@ -18,6 +19,12 @@ function assertWeightsSumPositive(weights) {
18
19
  throw new Error("targetWeights sum must be positive");
19
20
  }
20
21
  }
22
+ function assertWeightsSumEqualsScale(weights) {
23
+ const sum = weights.reduce((acc, w) => acc + w, 0n);
24
+ if (sum !== CLAIM_WEIGHT_SCALE) {
25
+ throw new Error(`targetWeights sum must equal CLAIM_WEIGHT_SCALE (${CLAIM_WEIGHT_SCALE}), got ${sum}`);
26
+ }
27
+ }
21
28
  export function buildCanonicalAllocationClaimRecord(input) {
22
29
  assertUint64(input.claim.epochId, "epochId");
23
30
  assertUint64(input.claim.horizonSec, "horizonSec");
@@ -35,6 +42,7 @@ export function buildCanonicalAllocationClaimRecord(input) {
35
42
  }
36
43
  });
37
44
  assertWeightsSumPositive(claim.targetWeights);
45
+ assertWeightsSumEqualsScale(claim.targetWeights);
38
46
  return {
39
47
  claim,
40
48
  claimHash: allocationClaimHash(claim)
@@ -78,7 +86,7 @@ export function buildEpochStateRecord(input) {
78
86
  if (input.claimHashes.length === 0) {
79
87
  throw new Error("claimHashes must not be empty");
80
88
  }
81
- const hash = snapshotHashFromUnordered(input.epochId, input.claimHashes);
89
+ const hash = epochStateHashFromUnordered(input.epochId, input.claimHashes);
82
90
  return {
83
91
  epochId: input.epochId,
84
92
  epochStateHash: hash
package/dist/scope.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Hex, ProtocolScope } from "./types.js";
2
2
  export declare function canonicalScope(input: ProtocolScope): ProtocolScope;
3
- export declare function scopeKey(input: ProtocolScope): string;
3
+ export declare function scopeKey(input: ProtocolScope): Hex;
4
4
  export declare function assertSameScope(expected: ProtocolScope, received: ProtocolScope): void;
5
5
  export declare function scopedSnapshotHash(scope: ProtocolScope, snapshotHash: Hex): Hex;
package/dist/scope.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { encodeAbiParameters, keccak256, parseAbiParameters } from "viem";
2
+ const SCOPE_KEY_INPUT = parseAbiParameters("string fundId,string roomId,uint64 epochId");
2
3
  function normalizeScopeText(value) {
3
4
  return value.normalize("NFC").trim();
4
5
  }
@@ -11,7 +12,7 @@ export function canonicalScope(input) {
11
12
  }
12
13
  export function scopeKey(input) {
13
14
  const v = canonicalScope(input);
14
- return `${v.fundId}:${v.roomId}:${v.epochId.toString(10)}`;
15
+ return keccak256(encodeAbiParameters(SCOPE_KEY_INPUT, [v.fundId, v.roomId, v.epochId]));
15
16
  }
16
17
  export function assertSameScope(expected, received) {
17
18
  const left = scopeKey(expected);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wiimdy/openfunderse-sdk",
3
- "version": "0.1.2",
3
+ "version": "1.0.0",
4
4
  "description": "Canonical hashing and EIP-712 helpers for Claw protocol components.",
5
5
  "license": "MIT",
6
6
  "type": "module",