@effectstream/sm 0.100.20 → 0.100.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectstream/sm",
3
- "version": "0.100.20",
3
+ "version": "0.100.22",
4
4
  "license": "MIT",
5
5
  "homepage": "https://effectstream.github.io/docs/",
6
6
  "repository": {
@@ -17,21 +17,20 @@
17
17
  "./grammar": "./primitives/src/grammar.ts"
18
18
  },
19
19
  "dependencies": {
20
- "@effectstream/concise": "0.100.20",
21
- "@effectstream/config": "0.100.20",
22
- "@effectstream/coroutine": "0.100.20",
23
- "@effectstream/crypto": "0.100.20",
24
- "@effectstream/db": "0.100.20",
25
- "@effectstream/event-client": "0.100.20",
26
- "@effectstream/log": "0.100.20",
27
- "@effectstream/utils": "0.100.20",
28
- "pg": "8.17.2",
20
+ "@effectstream/concise": "0.100.22",
21
+ "@effectstream/config": "0.100.22",
22
+ "@effectstream/coroutine": "0.100.22",
23
+ "@effectstream/crypto": "0.100.22",
24
+ "@effectstream/db": "0.100.22",
25
+ "@effectstream/event-client": "0.100.22",
26
+ "@effectstream/log": "0.100.22",
27
+ "@effectstream/utils": "0.100.22",
28
+ "pg": "8.21.0",
29
29
  "pg-tx": "^1.0.1",
30
30
  "@sinclair/typebox": "0.34.41",
31
31
  "assert-never": "^1.4.0",
32
32
  "viem": "2.37.3",
33
33
  "@pgtyped/runtime": "^2.4.2",
34
- "@midnight-ntwrk/onchain-runtime": "npm:@midnight-ntwrk/onchain-runtime-v2@2.0.0",
35
34
  "effection": "^3.5.0"
36
35
  },
37
36
  "description": "State machine DSL for EffectStream"
@@ -9,6 +9,7 @@ import type {
9
9
  ProtocolPrimitiveMap,
10
10
  } from "@effectstream/config";
11
11
  import type { AnyPrimitiveType } from "./src/builtin.ts";
12
+ import type { MaterializedViewStrategy } from "@effectstream/db";
12
13
 
13
14
  /**
14
15
  * Abstract Class for Effectstream Primitives
@@ -44,7 +45,15 @@ export abstract class Primitive<
44
45
  abstract grammar: TGrammar;
45
46
 
46
47
  // Dynamic/ivm Table Global definitions
47
- dynamicTables: undefined | ((name: string) => string) = undefined;
48
+ //
49
+ // The `strategy` argument lets the engine pick between an incrementally
50
+ // maintained `pg_ivm` view (when the extension is available) and a plain
51
+ // SQL view fallback (when it isn't). All other DDL — intermediate table,
52
+ // trigger function, trigger — is identical across strategies.
53
+ dynamicTables:
54
+ | undefined
55
+ | ((name: string, strategy: MaterializedViewStrategy) => string) =
56
+ undefined;
48
57
  getIntermediatePrefix(): string[] {
49
58
  return [];
50
59
  }
@@ -56,8 +65,11 @@ export abstract class Primitive<
56
65
  abstract getConfig(): ProtocolPrimitiveMap[SyncProtocol];
57
66
 
58
67
  // Arrow function to bind 'this', as this function is passed as a reference
59
- public getDynamicTables = (name: string): string | undefined => {
60
- return this.dynamicTables?.(name);
68
+ public getDynamicTables = (
69
+ name: string,
70
+ strategy: MaterializedViewStrategy,
71
+ ): string | undefined => {
72
+ return this.dynamicTables?.(name, strategy);
61
73
  };
62
74
 
63
75
  // This returns the payload in the state machine format.
@@ -3,6 +3,8 @@
3
3
  export const PrimitiveTypeMidnightGeneric = "Midnight:Generic" as const;
4
4
  export const PrimitiveTypeMidnightNullifier = "Midnight:Nullifier" as const;
5
5
  export const PrimitiveTypeMidnightUnshieldedSpend = "Midnight:UnshieldedSpend" as const;
6
+ export const PrimitiveTypeMidnightUnshieldedCreate = "Midnight:UnshieldedCreate" as const;
7
+ export const PrimitiveTypeMidnightZswapRoot = "Midnight:ZswapRoot" as const;
6
8
 
7
9
  export const PrimitiveTypeUtxorpcGeneric = "Utxorpc:Generic" as const;
8
10
  export const PrimitiveTypeCardanoMintBurn = "Cardano:MintBurn" as const;
@@ -34,6 +36,8 @@ type BuiltInPrimitives =
34
36
  typeof PrimitiveTypeMidnightGeneric |
35
37
  typeof PrimitiveTypeMidnightNullifier |
36
38
  typeof PrimitiveTypeMidnightUnshieldedSpend |
39
+ typeof PrimitiveTypeMidnightUnshieldedCreate |
40
+ typeof PrimitiveTypeMidnightZswapRoot |
37
41
  typeof PrimitiveTypeEVMEffectstreamL2 |
38
42
  typeof PrimitiveTypeEVMERC721 |
39
43
  typeof PrimitiveTypeEVMERC20 |
@@ -1,6 +1,10 @@
1
1
  import { PrimitiveTypeCardanoDelayedAsset } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
- export function delayedAssetIvm(name: string) {
4
+ export function delayedAssetIvm(
5
+ name: string,
6
+ strategy: MaterializedViewStrategy,
7
+ ) {
4
8
  const lowerName = name.toLowerCase();
5
9
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
10
  if (validSQLName !== lowerName) {
@@ -8,6 +12,9 @@ export function delayedAssetIvm(name: string) {
8
12
  }
9
13
 
10
14
  const intermediateTable = `primitives.cardano_asset_utxos_intermediate_${validSQLName}`;
15
+ const viewName = `primitives.cardano_asset_utxos_view_${validSQLName}`;
16
+ const selectSql =
17
+ `SELECT primitive_name, tx_id, output_index, address, cip14_fingerprint, policy_id, asset_name, amount, block_height FROM ${intermediateTable}`;
11
18
 
12
19
  return `
13
20
  CREATE TABLE IF NOT EXISTS ${intermediateTable} (
@@ -69,19 +76,7 @@ export function delayedAssetIvm(name: string) {
69
76
  FOR EACH ROW
70
77
  EXECUTE FUNCTION update_delayed_asset_${validSQLName}();
71
78
 
72
- SELECT pgivm.create_immv('primitives.cardano_asset_utxos_view_${validSQLName}',
73
- 'SELECT
74
- primitive_name,
75
- tx_id,
76
- output_index,
77
- address,
78
- cip14_fingerprint,
79
- policy_id,
80
- asset_name,
81
- amount,
82
- block_height
83
- FROM ${intermediateTable};
84
- ');
79
+ ${strategy.createView(viewName, selectSql)}
85
80
  `;
86
81
  }
87
82
 
@@ -1,6 +1,10 @@
1
1
  import { PrimitiveTypeCardanoPoolDelegation } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
- export function poolDelegationIvm(name: string) {
4
+ export function poolDelegationIvm(
5
+ name: string,
6
+ strategy: MaterializedViewStrategy,
7
+ ) {
4
8
  const lowerName = name.toLowerCase();
5
9
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
10
  if (validSQLName !== lowerName) {
@@ -8,6 +12,9 @@ export function poolDelegationIvm(name: string) {
8
12
  }
9
13
 
10
14
  const intermediateTable = `primitives.cardano_pool_delegation_intermediate_${validSQLName}`;
15
+ const viewName = `primitives.cardano_pool_delegation_view_${validSQLName}`;
16
+ const selectSql =
17
+ `SELECT primitive_name, staking_credential, pool_keyhash FROM ${intermediateTable}`;
11
18
 
12
19
  return `
13
20
  CREATE TABLE IF NOT EXISTS ${intermediateTable} (
@@ -51,13 +58,7 @@ export function poolDelegationIvm(name: string) {
51
58
  FOR EACH ROW
52
59
  EXECUTE FUNCTION update_pool_delegation_${validSQLName}();
53
60
 
54
- SELECT pgivm.create_immv('primitives.cardano_pool_delegation_view_${validSQLName}',
55
- 'SELECT
56
- primitive_name,
57
- staking_credential,
58
- pool_keyhash
59
- FROM ${intermediateTable};
60
- ');
61
+ ${strategy.createView(viewName, selectSql)}
61
62
  `;
62
63
  }
63
64
 
@@ -85,7 +85,15 @@ export class CardanoPoolDelegationPrimitive extends Primitive<
85
85
  type: this.internalTypeName,
86
86
  startBlockHeight: this.startBlockHeight,
87
87
  scheduledPrefix: this.stateMachinePrefix,
88
- predicate: { match: { cardano: { has_certificate: true } } },
88
+ predicate: {
89
+ match: {
90
+ cardano: {
91
+ has_certificate: this.pools.length === 1
92
+ ? { any_pool_keyhash: this.pools[0] }
93
+ : true,
94
+ },
95
+ },
96
+ },
89
97
  } as const;
90
98
  }
91
99
 
@@ -1,6 +1,10 @@
1
1
  import { PrimitiveTypeCardanoProjectedNFT } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
- export function projectedNftIvm(name: string) {
4
+ export function projectedNftIvm(
5
+ name: string,
6
+ strategy: MaterializedViewStrategy,
7
+ ) {
4
8
  const lowerName = name.toLowerCase();
5
9
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
10
  if (validSQLName !== lowerName) {
@@ -8,6 +12,9 @@ export function projectedNftIvm(name: string) {
8
12
  }
9
13
 
10
14
  const intermediateTable = `primitives.cardano_projected_nft_intermediate_${validSQLName}`;
15
+ const viewName = `primitives.cardano_projected_nft_view_${validSQLName}`;
16
+ const selectSql =
17
+ `SELECT primitive_name, owner_address, current_tx_id, current_output_index, previous_tx_id, previous_output_index, policy_id, asset_name, status, for_how_long FROM ${intermediateTable}`;
11
18
 
12
19
  return `
13
20
  CREATE TABLE IF NOT EXISTS ${intermediateTable} (
@@ -87,20 +94,7 @@ export function projectedNftIvm(name: string) {
87
94
  FOR EACH ROW
88
95
  EXECUTE FUNCTION update_projected_nft_${validSQLName}();
89
96
 
90
- SELECT pgivm.create_immv('primitives.cardano_projected_nft_view_${validSQLName}',
91
- 'SELECT
92
- primitive_name,
93
- owner_address,
94
- current_tx_id,
95
- current_output_index,
96
- previous_tx_id,
97
- previous_output_index,
98
- policy_id,
99
- asset_name,
100
- status,
101
- for_how_long
102
- FROM ${intermediateTable};
103
- ');
97
+ ${strategy.createView(viewName, selectSql)}
104
98
  `;
105
99
  }
106
100
 
@@ -1,12 +1,14 @@
1
1
  import { PrimitiveTypeEVMERC1155 } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
4
  /**
4
5
  * Creates a new IVM for the ERC1155 token with the given name.
5
6
  * This will track the current owner of each token.
6
7
  * @param name - The name of the ERC1155 token.
8
+ * @param strategy - How to publish the read-side view (pg_ivm or plain VIEW).
7
9
  * @returns A string containing the SQL script to create the IVM.
8
10
  */
9
- export function erc1155Ivm(name: string) {
11
+ export function erc1155Ivm(name: string, strategy: MaterializedViewStrategy) {
10
12
  const lowerName = name.toLowerCase();
11
13
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
12
14
  if (validSQLName !== lowerName) {
@@ -15,6 +17,9 @@ export function erc1155Ivm(name: string) {
15
17
 
16
18
  const ownershipTable =
17
19
  `primitives.erc1155_ownership_intermediate_${validSQLName}`;
20
+ const viewName = `primitives.erc1155_ownership_view_${validSQLName}`;
21
+ const selectSql =
22
+ `SELECT primitive_name, token_id, owner_address, amount FROM ${ownershipTable} WHERE amount > 0`;
18
23
 
19
24
  return `
20
25
  -- Intermediate table for current ERC1155 ownership (maintained by triggers)
@@ -85,16 +90,8 @@ export function erc1155Ivm(name: string) {
85
90
  FOR EACH ROW
86
91
  EXECUTE FUNCTION update_erc1155_ownership_${validSQLName}();
87
92
 
88
- -- Simple incrementally maintained view on the intermediate table
89
- SELECT pgivm.create_immv('primitives.erc1155_ownership_view_${validSQLName}',
90
- 'SELECT
91
- primitive_name,
92
- token_id,
93
- owner_address,
94
- amount
95
- FROM ${ownershipTable}
96
- WHERE amount > 0;
97
- ');
93
+ -- Publish the read-side view (incrementally maintained or plain, per strategy)
94
+ ${strategy.createView(viewName, selectSql)}
98
95
  `;
99
96
  }
100
97
 
@@ -2,6 +2,7 @@
2
2
  // This will allow to create new tables during migrations.
3
3
 
4
4
  import { PrimitiveTypeEVMERC20 } from "../builtin.ts";
5
+ import type { MaterializedViewStrategy } from "@effectstream/db";
5
6
 
6
7
  // TODO The IVM here is not relevant, as the trigger does the work.
7
8
 
@@ -9,9 +10,10 @@ import { PrimitiveTypeEVMERC20 } from "../builtin.ts";
9
10
  * Creates a new IVM for the ERC20 token with the given name.
10
11
  * This will track the current balance of each address.
11
12
  * @param name - The name of the ERC20 token.
13
+ * @param strategy - How to publish the read-side view (pg_ivm or plain VIEW).
12
14
  * @returns A string containing the SQL script to create the IVM.
13
15
  */
14
- export function erc20Ivm(name: string) {
16
+ export function erc20Ivm(name: string, strategy: MaterializedViewStrategy) {
15
17
  const lowerName = name.toLowerCase();
16
18
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
17
19
  if (validSQLName !== lowerName) {
@@ -19,6 +21,9 @@ export function erc20Ivm(name: string) {
19
21
  }
20
22
 
21
23
  const balanceTable = `primitives.erc20_balances_intermediate_${validSQLName}`;
24
+ const viewName = `primitives.erc20_balances_view_${validSQLName}`;
25
+ const selectSql =
26
+ `SELECT primitive_name, address, balance FROM ${balanceTable} WHERE balance > 0`;
22
27
 
23
28
  return `
24
29
  -- Intermediate table for current ERC20 balances (maintained by triggers)
@@ -28,7 +33,7 @@ export function erc20Ivm(name: string) {
28
33
  balance numeric(78,0) DEFAULT 0,
29
34
  PRIMARY KEY (primitive_name, address)
30
35
  );
31
-
36
+
32
37
  -- Trigger function to maintain ERC20 balances
33
38
  CREATE OR REPLACE FUNCTION update_erc20_balances_${validSQLName}() RETURNS TRIGGER AS $$
34
39
  DECLARE
@@ -37,25 +42,25 @@ export function erc20Ivm(name: string) {
37
42
  to_address TEXT;
38
43
  BEGIN
39
44
  -- Only process ERC20 transfers
40
- IF NEW.payload_type = '${PrimitiveTypeEVMERC20}'
45
+ IF NEW.payload_type = '${PrimitiveTypeEVMERC20}'
41
46
  AND NEW.primitive_name = '${name}'
42
47
  AND NEW.payload->>'value' IS NOT NULL THEN
43
-
48
+
44
49
  transfer_value := (NEW.payload->>'value')::numeric(78,0);
45
50
  from_address := lower(NEW.payload->>'from');
46
51
  to_address := lower(NEW.payload->>'to');
47
-
52
+
48
53
  -- Handle FROM address (subtract balance, but skip burn address)
49
- IF from_address IS NOT NULL
50
- AND from_address != ''
54
+ IF from_address IS NOT NULL
55
+ AND from_address != ''
51
56
  AND from_address != '0x0000000000000000000000000000000000000000' THEN
52
-
57
+
53
58
  INSERT INTO ${balanceTable} (primitive_name, address, balance)
54
59
  VALUES (NEW.primitive_name, from_address, -transfer_value)
55
60
  ON CONFLICT (primitive_name, address) DO UPDATE SET
56
61
  balance = ${balanceTable}.balance - transfer_value;
57
62
  END IF;
58
-
63
+
59
64
  -- Handle TO address (add balance)
60
65
  IF to_address IS NOT NULL AND to_address != '' THEN
61
66
  INSERT INTO ${balanceTable} (primitive_name, address, balance)
@@ -64,26 +69,19 @@ export function erc20Ivm(name: string) {
64
69
  balance = ${balanceTable}.balance + transfer_value;
65
70
  END IF;
66
71
  END IF;
67
-
72
+
68
73
  RETURN NEW;
69
74
  END;
70
75
  $$ LANGUAGE plpgsql;
71
-
76
+
72
77
  -- Create trigger on primitive_accounting for ERC20 balances
73
78
  CREATE TRIGGER trigger_update_erc20_balances_${validSQLName}
74
79
  AFTER INSERT ON effectstream.primitive_accounting -- Note: Qualify with paima schema
75
80
  FOR EACH ROW
76
81
  EXECUTE FUNCTION update_erc20_balances_${validSQLName}();
77
-
78
- -- Simple incrementally maintained view on the intermediate table
79
- SELECT pgivm.create_immv('primitives.erc20_balances_view_${validSQLName}',
80
- 'SELECT
81
- primitive_name,
82
- address,
83
- balance
84
- FROM ${balanceTable}
85
- WHERE balance > 0;
86
- ');
82
+
83
+ -- Publish the read-side view (incrementally maintained or plain, per strategy)
84
+ ${strategy.createView(viewName, selectSql)}
87
85
  `;
88
86
  }
89
87
 
@@ -1,12 +1,14 @@
1
1
  import { PrimitiveTypeEVMERC721 } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
4
  /**
4
5
  * Creates a new IVM for the ERC721 token with the given name.
5
6
  * This will track the current owner of each token.
6
7
  * @param name - The name of the ERC721 token.
8
+ * @param strategy - How to publish the read-side view (pg_ivm or plain VIEW).
7
9
  * @returns A string containing the SQL script to create the IVM.
8
10
  */
9
- export function erc721Ivm(name: string) {
11
+ export function erc721Ivm(name: string, strategy: MaterializedViewStrategy) {
10
12
  const lowerName = name.toLowerCase();
11
13
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
12
14
  if (validSQLName !== lowerName) {
@@ -15,6 +17,9 @@ export function erc721Ivm(name: string) {
15
17
 
16
18
  const ownershipTable =
17
19
  `primitives.erc721_ownership_intermediate_${validSQLName}`;
20
+ const viewName = `primitives.erc721_ownership_view_${validSQLName}`;
21
+ const selectSql =
22
+ `SELECT primitive_name, token_id, current_owner FROM ${ownershipTable}`;
18
23
 
19
24
  return `
20
25
  -- Intermediate table for current ERC721 ownership (maintained by triggers)
@@ -68,14 +73,8 @@ export function erc721Ivm(name: string) {
68
73
  FOR EACH ROW
69
74
  EXECUTE FUNCTION update_erc721_ownership_${validSQLName}();
70
75
 
71
- -- Simple incrementally maintained view on the intermediate table
72
- SELECT pgivm.create_immv('primitives.erc721_ownership_view_${validSQLName}',
73
- 'SELECT
74
- primitive_name,
75
- token_id,
76
- current_owner
77
- FROM ${ownershipTable};
78
- ');
76
+ -- Publish the read-side view (incrementally maintained or plain, per strategy)
77
+ ${strategy.createView(viewName, selectSql)}
79
78
  `;
80
79
  }
81
80
 
@@ -0,0 +1,8 @@
1
+ import { Type } from "@sinclair/typebox";
2
+
3
+ export const midnightUnshieldedCreateGrammar = [
4
+ [
5
+ "payload",
6
+ Type.Any(),
7
+ ],
8
+ ] as const;
@@ -0,0 +1,121 @@
1
+ import { Primitive } from "@effectstream/sm";
2
+ import {
3
+ type AddressAndType,
4
+ AddressType,
5
+ type EffectstreamBlockNumber,
6
+ } from "@effectstream/utils";
7
+ import type { StaticDecode } from "@sinclair/typebox";
8
+ import {
9
+ type CommandTuple,
10
+ generateRawStmInput,
11
+ type ParamToData,
12
+ } from "@effectstream/concise";
13
+ import type {
14
+ ConfigSyncProtocolType,
15
+ FlattenSyncProtocolIOFor,
16
+ ProtocolPrimitiveMap,
17
+ } from "@effectstream/config";
18
+ import type { SyncStateUpdateStream } from "@effectstream/coroutine";
19
+ import { PrimitiveTypeMidnightUnshieldedCreate } from "../builtin.ts";
20
+ import { midnightUnshieldedCreateGrammar } from "./midnight-unshielded-create-grammar.ts";
21
+
22
+ /**
23
+ * MidnightUnshieldedCreatePrimitive
24
+ *
25
+ * Watches all unshielded UTXO *creation* events on the Midnight ledger — the
26
+ * mirror of MidnightUnshieldedSpendPrimitive. The indexer's GraphQL
27
+ * `transactions.unshieldedCreatedOutputs` field reports the
28
+ * `(owner, intentHash, outputIndex)` triple for each unshielded UTXO created
29
+ * in a block (by regular AND system transactions — rewards/bridge mint
30
+ * unshielded UTXOs). One state-machine input is emitted per created output.
31
+ *
32
+ * Lets a node maintain a persistent set of UTXOs that have actually been
33
+ * created on chain, so an offer referencing a never-created unshielded UTXO
34
+ * can be rejected (existence check).
35
+ *
36
+ * Usage:
37
+ * stm.addStateTransition("myPrefix", function* (data) {
38
+ * const { payload } = data.parsedInput;
39
+ * // payload = { owner, intentHash, outputIndex, txHash }
40
+ * });
41
+ */
42
+ export class MidnightUnshieldedCreatePrimitive extends Primitive<
43
+ ConfigSyncProtocolType.MIDNIGHT_PARALLEL,
44
+ typeof midnightUnshieldedCreateGrammar
45
+ > {
46
+ readonly internalTypeName = PrimitiveTypeMidnightUnshieldedCreate;
47
+ override readonly grammar = midnightUnshieldedCreateGrammar;
48
+
49
+ override dynamicTables = undefined;
50
+ override getIntermediatePrefix(): string[] {
51
+ return [];
52
+ }
53
+ override getViewPrefix(): string[] {
54
+ return [];
55
+ }
56
+
57
+ constructor(config: {
58
+ instanceName: string;
59
+ startBlockHeight: number;
60
+ stateMachinePrefix: string;
61
+ }) {
62
+ super(config);
63
+ }
64
+
65
+ override *getPayload(
66
+ _: EffectstreamBlockNumber,
67
+ primitiveTransactionData: FlattenSyncProtocolIOFor<
68
+ ConfigSyncProtocolType.MIDNIGHT_PARALLEL
69
+ >,
70
+ ): SyncStateUpdateStream<{
71
+ isBatched: boolean;
72
+ data: {
73
+ fromAddressAndType: AddressAndType;
74
+ stateMachinePayload:
75
+ | StaticDecode<CommandTuple<string, typeof midnightUnshieldedCreateGrammar>>
76
+ | null;
77
+ accountingPayload: ParamToData<typeof midnightUnshieldedCreateGrammar>;
78
+ }[];
79
+ }> {
80
+ const payload = primitiveTransactionData.output.payload;
81
+
82
+ const accountingPayload: ParamToData<typeof this.grammar> = {
83
+ payload,
84
+ };
85
+
86
+ const stateMachinePayload:
87
+ | StaticDecode<CommandTuple<string, typeof this.grammar>>
88
+ | null = this.stateMachinePrefix
89
+ ? generateRawStmInput(
90
+ this.grammar,
91
+ this.stateMachinePrefix,
92
+ accountingPayload,
93
+ )
94
+ : null;
95
+
96
+ return {
97
+ isBatched: false,
98
+ data: [
99
+ {
100
+ fromAddressAndType: {
101
+ type: AddressType.NONE,
102
+ address: "0x0",
103
+ },
104
+ accountingPayload,
105
+ stateMachinePayload,
106
+ },
107
+ ],
108
+ };
109
+ }
110
+
111
+ override getConfig(): ProtocolPrimitiveMap[
112
+ ConfigSyncProtocolType.MIDNIGHT_PARALLEL
113
+ ] {
114
+ return {
115
+ name: this.instanceName,
116
+ type: this.internalTypeName,
117
+ startBlockHeight: this.startBlockHeight,
118
+ scheduledPrefix: this.stateMachinePrefix ?? "",
119
+ } as const;
120
+ }
121
+ }
@@ -0,0 +1,8 @@
1
+ import { Type } from "@sinclair/typebox";
2
+
3
+ export const midnightZswapRootGrammar = [
4
+ [
5
+ "payload",
6
+ Type.Any(),
7
+ ],
8
+ ] as const;
@@ -0,0 +1,121 @@
1
+ import { Primitive } from "@effectstream/sm";
2
+ import {
3
+ type AddressAndType,
4
+ AddressType,
5
+ type EffectstreamBlockNumber,
6
+ } from "@effectstream/utils";
7
+ import type { StaticDecode } from "@sinclair/typebox";
8
+ import {
9
+ type CommandTuple,
10
+ generateRawStmInput,
11
+ type ParamToData,
12
+ } from "@effectstream/concise";
13
+ import type {
14
+ ConfigSyncProtocolType,
15
+ FlattenSyncProtocolIOFor,
16
+ ProtocolPrimitiveMap,
17
+ } from "@effectstream/config";
18
+ import type { SyncStateUpdateStream } from "@effectstream/coroutine";
19
+ import { PrimitiveTypeMidnightZswapRoot } from "../builtin.ts";
20
+ import { midnightZswapRootGrammar } from "./midnight-zswap-root-grammar.ts";
21
+
22
+ /**
23
+ * MidnightZswapRootPrimitive
24
+ *
25
+ * Emits the zswap coin-commitment Merkle tree root as it advances on chain.
26
+ * The indexer's `RegularTransaction.zswapMerkleTreeRoot` is the tree root
27
+ * *after* that transaction; this primitive surfaces the latest such root per
28
+ * block (the last regular transaction's root), with the block timestamp.
29
+ *
30
+ * A node can accumulate these into a windowed "known roots" set (mirroring the
31
+ * ledger's `past_roots`, retained for the on-chain root window) and reject an
32
+ * offer that proves against a root the chain never held or that has aged out —
33
+ * the root-known liveness check. Blocks with no regular transactions emit
34
+ * nothing (the root is unchanged).
35
+ *
36
+ * Usage:
37
+ * stm.addStateTransition("myPrefix", function* (data) {
38
+ * const { payload } = data.parsedInput;
39
+ * // payload = { root, txHash } ; data.blockTimestamp is the window key
40
+ * });
41
+ */
42
+ export class MidnightZswapRootPrimitive extends Primitive<
43
+ ConfigSyncProtocolType.MIDNIGHT_PARALLEL,
44
+ typeof midnightZswapRootGrammar
45
+ > {
46
+ readonly internalTypeName = PrimitiveTypeMidnightZswapRoot;
47
+ override readonly grammar = midnightZswapRootGrammar;
48
+
49
+ override dynamicTables = undefined;
50
+ override getIntermediatePrefix(): string[] {
51
+ return [];
52
+ }
53
+ override getViewPrefix(): string[] {
54
+ return [];
55
+ }
56
+
57
+ constructor(config: {
58
+ instanceName: string;
59
+ startBlockHeight: number;
60
+ stateMachinePrefix: string;
61
+ }) {
62
+ super(config);
63
+ }
64
+
65
+ override *getPayload(
66
+ _: EffectstreamBlockNumber,
67
+ primitiveTransactionData: FlattenSyncProtocolIOFor<
68
+ ConfigSyncProtocolType.MIDNIGHT_PARALLEL
69
+ >,
70
+ ): SyncStateUpdateStream<{
71
+ isBatched: boolean;
72
+ data: {
73
+ fromAddressAndType: AddressAndType;
74
+ stateMachinePayload:
75
+ | StaticDecode<CommandTuple<string, typeof midnightZswapRootGrammar>>
76
+ | null;
77
+ accountingPayload: ParamToData<typeof midnightZswapRootGrammar>;
78
+ }[];
79
+ }> {
80
+ const payload = primitiveTransactionData.output.payload;
81
+
82
+ const accountingPayload: ParamToData<typeof this.grammar> = {
83
+ payload,
84
+ };
85
+
86
+ const stateMachinePayload:
87
+ | StaticDecode<CommandTuple<string, typeof this.grammar>>
88
+ | null = this.stateMachinePrefix
89
+ ? generateRawStmInput(
90
+ this.grammar,
91
+ this.stateMachinePrefix,
92
+ accountingPayload,
93
+ )
94
+ : null;
95
+
96
+ return {
97
+ isBatched: false,
98
+ data: [
99
+ {
100
+ fromAddressAndType: {
101
+ type: AddressType.NONE,
102
+ address: "0x0",
103
+ },
104
+ accountingPayload,
105
+ stateMachinePayload,
106
+ },
107
+ ],
108
+ };
109
+ }
110
+
111
+ override getConfig(): ProtocolPrimitiveMap[
112
+ ConfigSyncProtocolType.MIDNIGHT_PARALLEL
113
+ ] {
114
+ return {
115
+ name: this.instanceName,
116
+ type: this.internalTypeName,
117
+ startBlockHeight: this.startBlockHeight,
118
+ scheduledPrefix: this.stateMachinePrefix ?? "",
119
+ } as const;
120
+ }
121
+ }
@@ -2,6 +2,8 @@ import {
2
2
  PrimitiveTypeMidnightGeneric,
3
3
  PrimitiveTypeMidnightNullifier,
4
4
  PrimitiveTypeMidnightUnshieldedSpend,
5
+ PrimitiveTypeMidnightUnshieldedCreate,
6
+ PrimitiveTypeMidnightZswapRoot,
5
7
  PrimitiveTypeEVMEffectstreamL2,
6
8
  PrimitiveTypeEVMERC721,
7
9
  PrimitiveTypeEVMERC20,
@@ -27,6 +29,8 @@ import {
27
29
  import { MidnightGenericPrimitive } from "./midnight-generic/midnight-genetic.ts";
28
30
  import { MidnightNullifierPrimitive } from "./midnight-nullifier/midnight-nullifier.ts";
29
31
  import { MidnightUnshieldedSpendPrimitive } from "./midnight-unshielded-spend/midnight-unshielded-spend.ts";
32
+ import { MidnightUnshieldedCreatePrimitive } from "./midnight-unshielded-create/midnight-unshielded-create.ts";
33
+ import { MidnightZswapRootPrimitive } from "./midnight-zswap-root/midnight-zswap-root.ts";
30
34
  import { EffectstreamL2Primitive } from "./evm-effectstream-l2/effectstream-l2-primitive.ts";
31
35
  import { Erc721Primitive } from "./evm-erc721/erc721-primitive.ts";
32
36
  import { Erc20Primitive } from "./evm-erc20/erc20-primitive.ts";
@@ -52,6 +56,8 @@ const builtInPrimitivesMap = {
52
56
  [PrimitiveTypeMidnightGeneric]: MidnightGenericPrimitive,
53
57
  [PrimitiveTypeMidnightNullifier]: MidnightNullifierPrimitive,
54
58
  [PrimitiveTypeMidnightUnshieldedSpend]: MidnightUnshieldedSpendPrimitive,
59
+ [PrimitiveTypeMidnightUnshieldedCreate]: MidnightUnshieldedCreatePrimitive,
60
+ [PrimitiveTypeMidnightZswapRoot]: MidnightZswapRootPrimitive,
55
61
  [PrimitiveTypeEVMEffectstreamL2]: EffectstreamL2Primitive,
56
62
  [PrimitiveTypeEVMERC721]: Erc721Primitive,
57
63
  [PrimitiveTypeEVMERC20]: Erc20Primitive,
@@ -82,6 +88,8 @@ export {
82
88
  MidnightGenericPrimitive,
83
89
  MidnightNullifierPrimitive,
84
90
  MidnightUnshieldedSpendPrimitive,
91
+ MidnightUnshieldedCreatePrimitive,
92
+ MidnightZswapRootPrimitive,
85
93
  EffectstreamL2Primitive,
86
94
  Erc721Primitive,
87
95
  Erc20Primitive,
@@ -1,6 +1,7 @@
1
1
  import { PrimitiveTypeNEARIntent } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
- export function nearIntentIvm(name: string) {
4
+ export function nearIntentIvm(name: string, strategy: MaterializedViewStrategy) {
4
5
  const lowerName = name.toLowerCase();
5
6
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
7
  if (validSQLName !== lowerName) {
@@ -8,6 +9,9 @@ export function nearIntentIvm(name: string) {
8
9
  }
9
10
 
10
11
  const settlementTable = `primitives.near_intent_settlement_intermediate_${validSQLName}`;
12
+ const viewName = `primitives.near_intent_settlement_view_${validSQLName}`;
13
+ const selectSql =
14
+ `SELECT primitive_name, intent_hash, account_id, token_id, amount, block_height FROM ${settlementTable}`;
11
15
 
12
16
  return `
13
17
  CREATE TABLE IF NOT EXISTS ${settlementTable} (
@@ -54,16 +58,7 @@ export function nearIntentIvm(name: string) {
54
58
  FOR EACH ROW
55
59
  EXECUTE FUNCTION update_near_intent_settlement_${validSQLName}();
56
60
 
57
- SELECT pgivm.create_immv('primitives.near_intent_settlement_view_${validSQLName}',
58
- 'SELECT
59
- primitive_name,
60
- intent_hash,
61
- account_id,
62
- token_id,
63
- amount,
64
- block_height
65
- FROM ${settlementTable};
66
- ');
61
+ ${strategy.createView(viewName, selectSql)}
67
62
  `;
68
63
  }
69
64
 
@@ -1,6 +1,7 @@
1
1
  import { PrimitiveTypeNEARNEP141 } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
- export function nep141Ivm(name: string) {
4
+ export function nep141Ivm(name: string, strategy: MaterializedViewStrategy) {
4
5
  const lowerName = name.toLowerCase();
5
6
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
7
  if (validSQLName !== lowerName) {
@@ -8,6 +9,9 @@ export function nep141Ivm(name: string) {
8
9
  }
9
10
 
10
11
  const balanceTable = `primitives.nep141_balance_intermediate_${validSQLName}`;
12
+ const viewName = `primitives.nep141_balance_view_${validSQLName}`;
13
+ const selectSql =
14
+ `SELECT primitive_name, account_id, balance FROM ${balanceTable} WHERE balance > 0`;
11
15
 
12
16
  return `
13
17
  CREATE TABLE IF NOT EXISTS ${balanceTable} (
@@ -55,14 +59,7 @@ export function nep141Ivm(name: string) {
55
59
  FOR EACH ROW
56
60
  EXECUTE FUNCTION update_nep141_balance_${validSQLName}();
57
61
 
58
- SELECT pgivm.create_immv('primitives.nep141_balance_view_${validSQLName}',
59
- 'SELECT
60
- primitive_name,
61
- account_id,
62
- balance
63
- FROM ${balanceTable}
64
- WHERE balance > 0;
65
- ');
62
+ ${strategy.createView(viewName, selectSql)}
66
63
  `;
67
64
  }
68
65
 
@@ -1,6 +1,7 @@
1
1
  import { PrimitiveTypeNEARNEP171 } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
- export function nep171Ivm(name: string) {
4
+ export function nep171Ivm(name: string, strategy: MaterializedViewStrategy) {
4
5
  const lowerName = name.toLowerCase();
5
6
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
7
  if (validSQLName !== lowerName) {
@@ -8,6 +9,9 @@ export function nep171Ivm(name: string) {
8
9
  }
9
10
 
10
11
  const ownerTable = `primitives.nep171_owner_intermediate_${validSQLName}`;
12
+ const viewName = `primitives.nep171_owner_view_${validSQLName}`;
13
+ const selectSql =
14
+ `SELECT primitive_name, token_id, current_owner FROM ${ownerTable}`;
11
15
 
12
16
  return `
13
17
  CREATE TABLE IF NOT EXISTS ${ownerTable} (
@@ -57,13 +61,7 @@ export function nep171Ivm(name: string) {
57
61
  FOR EACH ROW
58
62
  EXECUTE FUNCTION update_nep171_owner_${validSQLName}();
59
63
 
60
- SELECT pgivm.create_immv('primitives.nep171_owner_view_${validSQLName}',
61
- 'SELECT
62
- primitive_name,
63
- token_id,
64
- current_owner
65
- FROM ${ownerTable};
66
- ');
64
+ ${strategy.createView(viewName, selectSql)}
67
65
  `;
68
66
  }
69
67
 
@@ -1,6 +1,7 @@
1
1
  import { PrimitiveTypeNEARNEP245 } from "../builtin.ts";
2
+ import type { MaterializedViewStrategy } from "@effectstream/db";
2
3
 
3
- export function nep245Ivm(name: string) {
4
+ export function nep245Ivm(name: string, strategy: MaterializedViewStrategy) {
4
5
  const lowerName = name.toLowerCase();
5
6
  const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
7
  if (validSQLName !== lowerName) {
@@ -8,6 +9,9 @@ export function nep245Ivm(name: string) {
8
9
  }
9
10
 
10
11
  const balanceTable = `primitives.nep245_balance_intermediate_${validSQLName}`;
12
+ const viewName = `primitives.nep245_balance_view_${validSQLName}`;
13
+ const selectSql =
14
+ `SELECT primitive_name, token_id, account_id, amount FROM ${balanceTable} WHERE amount > 0`;
11
15
 
12
16
  return `
13
17
  CREATE TABLE IF NOT EXISTS ${balanceTable} (
@@ -68,15 +72,7 @@ export function nep245Ivm(name: string) {
68
72
  FOR EACH ROW
69
73
  EXECUTE FUNCTION update_nep245_balance_${validSQLName}();
70
74
 
71
- SELECT pgivm.create_immv('primitives.nep245_balance_view_${validSQLName}',
72
- 'SELECT
73
- primitive_name,
74
- token_id,
75
- account_id,
76
- amount
77
- FROM ${balanceTable}
78
- WHERE amount > 0;
79
- ');
75
+ ${strategy.createView(viewName, selectSql)}
80
76
  `;
81
77
  }
82
78