@effectstream/sm 0.100.12 → 0.100.14

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.
Files changed (23) hide show
  1. package/package.json +9 -8
  2. package/primitives/src/builtin.ts +12 -0
  3. package/primitives/src/cardano-delayed-asset/delayed-asset-grammar.ts +11 -0
  4. package/primitives/src/cardano-delayed-asset/delayed-asset-ivm.ts +89 -0
  5. package/primitives/src/cardano-delayed-asset/delayed-asset-primitive.ts +228 -0
  6. package/primitives/src/cardano-mint-burn/mint-burn-grammar.ts +9 -0
  7. package/primitives/src/cardano-mint-burn/mint-burn-primitive.ts +160 -0
  8. package/primitives/src/cardano-pool-delegation/pool-delegation-grammar.ts +7 -0
  9. package/primitives/src/cardano-pool-delegation/pool-delegation-ivm.ts +65 -0
  10. package/primitives/src/cardano-pool-delegation/pool-delegation-primitive.ts +150 -0
  11. package/primitives/src/cardano-projected-nft/projected-nft-datum.ts +107 -0
  12. package/primitives/src/cardano-projected-nft/projected-nft-grammar.ts +13 -0
  13. package/primitives/src/cardano-projected-nft/projected-nft-ivm.ts +108 -0
  14. package/primitives/src/cardano-projected-nft/projected-nft-primitive.ts +242 -0
  15. package/primitives/src/cardano-transfer/transfer-grammar.ts +8 -0
  16. package/primitives/src/cardano-transfer/transfer-primitive.ts +150 -0
  17. package/primitives/src/cardano-utils/cardano-helpers.ts +84 -0
  18. package/primitives/src/grammar.ts +10 -0
  19. package/primitives/src/midnight-generic/midnight-genetic.ts +15 -0
  20. package/primitives/src/midnight-unshielded-spend/midnight-unshielded-spend-grammar.ts +8 -0
  21. package/primitives/src/midnight-unshielded-spend/midnight-unshielded-spend.ts +115 -0
  22. package/primitives/src/mod.ts +24 -0
  23. package/types.ts +27 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectstream/sm",
3
- "version": "0.100.12",
3
+ "version": "0.100.14",
4
4
  "license": "MIT",
5
5
  "homepage": "https://effectstream.github.io/docs/",
6
6
  "repository": {
@@ -17,13 +17,14 @@
17
17
  "./grammar": "./primitives/src/grammar.ts"
18
18
  },
19
19
  "dependencies": {
20
- "@effectstream/concise": "0.100.12",
21
- "@effectstream/config": "0.100.12",
22
- "@effectstream/coroutine": "0.100.12",
23
- "@effectstream/crypto": "0.100.12",
24
- "@effectstream/db": "0.100.12",
25
- "@effectstream/log": "0.100.12",
26
- "@effectstream/utils": "0.100.12",
20
+ "@effectstream/concise": "0.100.14",
21
+ "@effectstream/config": "0.100.14",
22
+ "@effectstream/coroutine": "0.100.14",
23
+ "@effectstream/crypto": "0.100.14",
24
+ "@effectstream/db": "0.100.14",
25
+ "@effectstream/event-client": "0.100.14",
26
+ "@effectstream/log": "0.100.14",
27
+ "@effectstream/utils": "0.100.14",
27
28
  "pg": "8.17.2",
28
29
  "pg-tx": "^1.0.1",
29
30
  "@sinclair/typebox": "0.34.41",
@@ -2,8 +2,14 @@
2
2
  // this list is exposed to the effectstream-sdk modules via the @effectstream/sm/builtin module
3
3
  export const PrimitiveTypeMidnightGeneric = "Midnight:Generic" as const;
4
4
  export const PrimitiveTypeMidnightNullifier = "Midnight:Nullifier" as const;
5
+ export const PrimitiveTypeMidnightUnshieldedSpend = "Midnight:UnshieldedSpend" as const;
5
6
 
6
7
  export const PrimitiveTypeUtxorpcGeneric = "Utxorpc:Generic" as const;
8
+ export const PrimitiveTypeCardanoMintBurn = "Cardano:MintBurn" as const;
9
+ export const PrimitiveTypeCardanoTransfer = "Cardano:Transfer" as const;
10
+ export const PrimitiveTypeCardanoPoolDelegation = "Cardano:PoolDelegation" as const;
11
+ export const PrimitiveTypeCardanoDelayedAsset = "Cardano:DelayedAsset" as const;
12
+ export const PrimitiveTypeCardanoProjectedNFT = "Cardano:ProjectedNFT" as const;
7
13
 
8
14
  export const PrimitiveTypeEVMEffectstreamL2 = "EVM:EffectstreamL2" as const;
9
15
  export const PrimitiveTypeEVMERC721 = "EVM:ERC721" as const;
@@ -27,12 +33,18 @@ export const PrimitiveTypeNEARAccountWatch = "NEAR:AccountWatch" as const;
27
33
  type BuiltInPrimitives =
28
34
  typeof PrimitiveTypeMidnightGeneric |
29
35
  typeof PrimitiveTypeMidnightNullifier |
36
+ typeof PrimitiveTypeMidnightUnshieldedSpend |
30
37
  typeof PrimitiveTypeEVMEffectstreamL2 |
31
38
  typeof PrimitiveTypeEVMERC721 |
32
39
  typeof PrimitiveTypeEVMERC20 |
33
40
  typeof PrimitiveTypeAvailGeneric |
34
41
  typeof PrimitiveTypeEVMERC1155 |
35
42
  typeof PrimitiveTypeUtxorpcGeneric |
43
+ typeof PrimitiveTypeCardanoMintBurn |
44
+ typeof PrimitiveTypeCardanoTransfer |
45
+ typeof PrimitiveTypeCardanoPoolDelegation |
46
+ typeof PrimitiveTypeCardanoDelayedAsset |
47
+ typeof PrimitiveTypeCardanoProjectedNFT |
36
48
  typeof PrimitiveTypeBitcoinAddress |
37
49
  typeof PrimitiveTypeCelestiaGeneric |
38
50
  typeof PrimitiveTypeNEARNEP141 |
@@ -0,0 +1,11 @@
1
+ import { Type } from "@sinclair/typebox";
2
+
3
+ export const delayedAssetGrammar = [
4
+ ["address", Type.String()],
5
+ ["txId", Type.String()],
6
+ ["outputIndex", Type.String()],
7
+ ["cip14Fingerprint", Type.String()],
8
+ ["amount", Type.String()],
9
+ ["policyId", Type.String()],
10
+ ["assetName", Type.String()],
11
+ ] as const;
@@ -0,0 +1,89 @@
1
+ import { PrimitiveTypeCardanoDelayedAsset } from "../builtin.ts";
2
+
3
+ export function delayedAssetIvm(name: string) {
4
+ const lowerName = name.toLowerCase();
5
+ const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
+ if (validSQLName !== lowerName) {
7
+ throw new Error(`Invalid name: ${name}`);
8
+ }
9
+
10
+ const intermediateTable = `primitives.cardano_asset_utxos_intermediate_${validSQLName}`;
11
+
12
+ return `
13
+ CREATE TABLE IF NOT EXISTS ${intermediateTable} (
14
+ primitive_name TEXT NOT NULL,
15
+ tx_id TEXT NOT NULL,
16
+ output_index INTEGER NOT NULL,
17
+ address TEXT NOT NULL,
18
+ cip14_fingerprint TEXT NOT NULL,
19
+ policy_id TEXT NOT NULL,
20
+ asset_name TEXT NOT NULL,
21
+ amount TEXT NOT NULL,
22
+ block_height INTEGER NOT NULL,
23
+ PRIMARY KEY (primitive_name, tx_id, output_index, cip14_fingerprint)
24
+ );
25
+
26
+ CREATE OR REPLACE FUNCTION update_delayed_asset_${validSQLName}() RETURNS TRIGGER AS $$
27
+ DECLARE
28
+ v_amount TEXT;
29
+ BEGIN
30
+ IF NEW.payload_type = '${PrimitiveTypeCardanoDelayedAsset}'
31
+ AND NEW.primitive_name = '${name}' THEN
32
+
33
+ v_amount := NEW.payload->>'amount';
34
+
35
+ IF v_amount IS NOT NULL AND v_amount != '' THEN
36
+ INSERT INTO ${intermediateTable} (
37
+ primitive_name, tx_id, output_index, address,
38
+ cip14_fingerprint, policy_id, asset_name, amount, block_height
39
+ ) VALUES (
40
+ NEW.primitive_name,
41
+ NEW.payload->>'txId',
42
+ (NEW.payload->>'outputIndex')::INTEGER,
43
+ NEW.payload->>'address',
44
+ NEW.payload->>'cip14Fingerprint',
45
+ NEW.payload->>'policyId',
46
+ NEW.payload->>'assetName',
47
+ v_amount,
48
+ NEW.effectstream_block_height
49
+ )
50
+ ON CONFLICT (primitive_name, tx_id, output_index, cip14_fingerprint) DO UPDATE SET
51
+ address = EXCLUDED.address,
52
+ amount = EXCLUDED.amount,
53
+ block_height = EXCLUDED.block_height;
54
+ ELSE
55
+ DELETE FROM ${intermediateTable}
56
+ WHERE primitive_name = NEW.primitive_name
57
+ AND tx_id = NEW.payload->>'txId'
58
+ AND output_index = (NEW.payload->>'outputIndex')::INTEGER
59
+ AND cip14_fingerprint = NEW.payload->>'cip14Fingerprint';
60
+ END IF;
61
+ END IF;
62
+
63
+ RETURN NEW;
64
+ END;
65
+ $$ LANGUAGE plpgsql;
66
+
67
+ CREATE TRIGGER trigger_update_delayed_asset_${validSQLName}
68
+ AFTER INSERT ON effectstream.primitive_accounting
69
+ FOR EACH ROW
70
+ EXECUTE FUNCTION update_delayed_asset_${validSQLName}();
71
+
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
+ ');
85
+ `;
86
+ }
87
+
88
+ export const DELAYED_ASSET_VIEW_PREFIX = "cardano_asset_utxos_view_" as const;
89
+ export const DELAYED_ASSET_INTERMEDIATE_PREFIX = "cardano_asset_utxos_intermediate_" as const;
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Cardano Delayed Asset Primitive
3
+ *
4
+ * Tracks the lifecycle of native asset UTxOs — creation and spending — from Dolos
5
+ * UTxORPC block data. "Delayed" because asset ownership only becomes final after the
6
+ * UTxO is confirmed on-chain (unlike account-model chains where balances update instantly).
7
+ *
8
+ * Data source: Dolos streams raw Cardano blocks via gRPC. Dolos resolves input UTxO
9
+ * references via its ledger state (Pallas `LedgerContext` trait), so `tx.inputs[].asOutput`
10
+ * contains the full resolved output including address and native assets.
11
+ *
12
+ * Extraction: `getPayload()` performs two scans per transaction:
13
+ * 1. **Creations**: iterates `tx.outputs` — for each output containing native assets
14
+ * matching configured `policyIds`, emits an entry with `amount = quantity`.
15
+ * 2. **Spendings**: iterates `tx.inputs` — for each resolved input (`asOutput`) with
16
+ * matching assets, emits an entry with `amount = null` (signals UTxO consumed).
17
+ *
18
+ * Predicate: uses `moves_asset` with configured policy IDs, so Dolos only sends
19
+ * transactions that create or spend UTxOs containing those assets.
20
+ *
21
+ * IVM: maintains a materialized view of live (unspent) asset UTxOs. The trigger INSERTs
22
+ * on creation events (amount != null) and DELETEs the matching UTxO on spending events
23
+ * (amount = null), giving a real-time view of current asset holdings.
24
+ */
25
+
26
+ import type { StaticDecode } from "@sinclair/typebox";
27
+ import {
28
+ type CommandTuple,
29
+ generateRawStmInput,
30
+ } from "@effectstream/concise";
31
+ import type {
32
+ ConfigSyncProtocolType,
33
+ FlattenSyncProtocolIOFor,
34
+ ProtocolPrimitiveMap,
35
+ UtxorpcTxPredicate,
36
+ } from "@effectstream/config";
37
+ import type { StateUpdateStream } from "@effectstream/coroutine";
38
+ import { type JsonObject, Primitive } from "@effectstream/sm";
39
+ import {
40
+ type AddressAndType,
41
+ AddressType,
42
+ type EffectstreamBlockNumber,
43
+ uint8ArrayToHexString,
44
+ } from "@effectstream/utils";
45
+ import { delayedAssetGrammar } from "./delayed-asset-grammar.ts";
46
+ import {
47
+ DELAYED_ASSET_INTERMEDIATE_PREFIX,
48
+ DELAYED_ASSET_VIEW_PREFIX,
49
+ delayedAssetIvm,
50
+ } from "./delayed-asset-ivm.ts";
51
+ import { PrimitiveTypeCardanoDelayedAsset } from "../builtin.ts";
52
+ import {
53
+ addressToHex,
54
+ assetQuantityToString,
55
+ } from "../cardano-utils/cardano-helpers.ts";
56
+
57
+ export class CardanoDelayedAssetPrimitive extends Primitive<
58
+ ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL,
59
+ typeof delayedAssetGrammar
60
+ > {
61
+ readonly internalTypeName = PrimitiveTypeCardanoDelayedAsset;
62
+ readonly policyIds: string[];
63
+ readonly fingerprints: string[] | undefined;
64
+ override grammar = delayedAssetGrammar;
65
+
66
+ override dynamicTables = delayedAssetIvm;
67
+ override getIntermediatePrefix(): string[] {
68
+ return [DELAYED_ASSET_INTERMEDIATE_PREFIX];
69
+ }
70
+ override getViewPrefix(): string[] {
71
+ return [DELAYED_ASSET_VIEW_PREFIX];
72
+ }
73
+
74
+ constructor(config: {
75
+ instanceName: string;
76
+ startBlockHeight: number;
77
+ stateMachinePrefix: string | undefined;
78
+ policyIds: string[];
79
+ fingerprints?: string[];
80
+ }) {
81
+ super(config);
82
+ this.policyIds = config.policyIds.map((p) => p.toLowerCase());
83
+ this.fingerprints = config.fingerprints?.map((f) => f.toLowerCase());
84
+ }
85
+
86
+ override getConfig(): ProtocolPrimitiveMap[ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL] {
87
+ const predicate: UtxorpcTxPredicate =
88
+ this.policyIds.length === 1
89
+ ? { match: { cardano: { moves_asset: { policy_id: this.policyIds[0] } } } }
90
+ : {
91
+ any_of: this.policyIds.map((pid) => ({
92
+ match: { cardano: { moves_asset: { policy_id: pid } } },
93
+ })),
94
+ };
95
+
96
+ return {
97
+ name: this.instanceName,
98
+ type: this.internalTypeName,
99
+ startBlockHeight: this.startBlockHeight,
100
+ scheduledPrefix: this.stateMachinePrefix,
101
+ predicate,
102
+ } as const;
103
+ }
104
+
105
+ private computeFingerprint(policyId: string, assetName: string): string {
106
+ return `${policyId}.${assetName}`;
107
+ }
108
+
109
+ override *getPayload(
110
+ _: EffectstreamBlockNumber,
111
+ primitiveTransactionData: FlattenSyncProtocolIOFor<ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL>,
112
+ ): StateUpdateStream<{
113
+ isBatched: boolean;
114
+ data: {
115
+ fromAddressAndType: AddressAndType;
116
+ stateMachinePayload: StaticDecode<CommandTuple<string, typeof delayedAssetGrammar>> | null;
117
+ accountingPayload: JsonObject;
118
+ }[];
119
+ }> {
120
+ const tx = primitiveTransactionData.output.payload.tx;
121
+ const txId = uint8ArrayToHexString(tx.hash);
122
+
123
+ const entries: {
124
+ fromAddressAndType: AddressAndType;
125
+ stateMachinePayload: any;
126
+ accountingPayload: JsonObject;
127
+ }[] = [];
128
+
129
+ // Creations: scan tx.outputs
130
+ for (let outputIndex = 0; outputIndex < tx.outputs.length; outputIndex++) {
131
+ const output = tx.outputs[outputIndex];
132
+ const address = addressToHex(output.address);
133
+
134
+ for (const ma of output.assets) {
135
+ const policyId = uint8ArrayToHexString(ma.policyId);
136
+ if (!this.policyIds.includes(policyId.toLowerCase())) continue;
137
+
138
+ for (const asset of ma.assets) {
139
+ const assetName = uint8ArrayToHexString(asset.name);
140
+ const fingerprint = this.computeFingerprint(policyId, assetName);
141
+
142
+ if (this.fingerprints && !this.fingerprints.includes(fingerprint.toLowerCase())) continue;
143
+
144
+ const amount = assetQuantityToString(asset);
145
+
146
+ const accountingPayload = {
147
+ address,
148
+ txId,
149
+ outputIndex,
150
+ cip14Fingerprint: fingerprint,
151
+ amount,
152
+ policyId,
153
+ assetName,
154
+ };
155
+
156
+ const stateMachinePayload: any = this.stateMachinePrefix
157
+ ? generateRawStmInput(this.grammar, this.stateMachinePrefix, {
158
+ address,
159
+ txId,
160
+ outputIndex: String(outputIndex),
161
+ cip14Fingerprint: fingerprint,
162
+ amount,
163
+ policyId,
164
+ assetName,
165
+ })
166
+ : null;
167
+
168
+ entries.push({
169
+ fromAddressAndType: { type: AddressType.NONE, address: "0x0" },
170
+ accountingPayload,
171
+ stateMachinePayload,
172
+ });
173
+ }
174
+ }
175
+ }
176
+
177
+ // Spendings: scan tx.inputs (resolved via Dolos)
178
+ for (const input of tx.inputs) {
179
+ if (!input.asOutput) continue;
180
+ const resolved = input.asOutput;
181
+ const address = addressToHex(resolved.address);
182
+ const inputTxHash = uint8ArrayToHexString(input.txHash);
183
+ const inputOutputIndex = input.outputIndex;
184
+
185
+ for (const ma of resolved.assets) {
186
+ const policyId = uint8ArrayToHexString(ma.policyId);
187
+ if (!this.policyIds.includes(policyId.toLowerCase())) continue;
188
+
189
+ for (const asset of ma.assets) {
190
+ const assetName = uint8ArrayToHexString(asset.name);
191
+ const fingerprint = this.computeFingerprint(policyId, assetName);
192
+
193
+ if (this.fingerprints && !this.fingerprints.includes(fingerprint.toLowerCase())) continue;
194
+
195
+ const accountingPayload = {
196
+ address,
197
+ txId: inputTxHash,
198
+ outputIndex: inputOutputIndex,
199
+ cip14Fingerprint: fingerprint,
200
+ amount: null,
201
+ policyId,
202
+ assetName,
203
+ };
204
+
205
+ const stateMachinePayload: any = this.stateMachinePrefix
206
+ ? generateRawStmInput(this.grammar, this.stateMachinePrefix, {
207
+ address,
208
+ txId: inputTxHash,
209
+ outputIndex: String(inputOutputIndex),
210
+ cip14Fingerprint: fingerprint,
211
+ amount: "",
212
+ policyId,
213
+ assetName,
214
+ })
215
+ : null;
216
+
217
+ entries.push({
218
+ fromAddressAndType: { type: AddressType.NONE, address: "0x0" },
219
+ accountingPayload,
220
+ stateMachinePayload,
221
+ });
222
+ }
223
+ }
224
+ }
225
+
226
+ return { isBatched: true, data: entries };
227
+ }
228
+ }
@@ -0,0 +1,9 @@
1
+ import { Type } from "@sinclair/typebox";
2
+
3
+ export const mintBurnGrammar = [
4
+ ["txId", Type.String()],
5
+ ["metadata", Type.String()],
6
+ ["assets", Type.String()],
7
+ ["inputAddresses", Type.String()],
8
+ ["outputAddresses", Type.String()],
9
+ ] as const;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Cardano MintBurn Primitive
3
+ *
4
+ * Detects native token minting and burning events from Dolos UTxORPC block data.
5
+ *
6
+ * Data source: Dolos streams raw Cardano blocks via gRPC (UTxORPC protocol). Each
7
+ * block contains transactions, and each transaction has a `mint` field — a list of
8
+ * `Multiasset` entries, each with a `policyId` and a list of `Asset` entries (name +
9
+ * quantity). Positive quantities = mints, negative = burns.
10
+ *
11
+ * Extraction: for each transaction matched by the predicate, `getPayload()` iterates
12
+ * `tx.mint`, filters by configured `policyIds`, and collects `{policyId, assetName, amount}`.
13
+ * It also extracts input addresses from `tx.inputs[].asOutput.address` (resolved UTxOs
14
+ * provided by Dolos) and output addresses from `tx.outputs[].address`.
15
+ *
16
+ * Predicate: uses `mints_asset` with the configured policy IDs, so Dolos only sends
17
+ * transactions that actually mint/burn tokens under those policies.
18
+ */
19
+
20
+ import type { StaticDecode } from "@sinclair/typebox";
21
+ import {
22
+ type CommandTuple,
23
+ generateRawStmInput,
24
+ } from "@effectstream/concise";
25
+ import type {
26
+ ConfigSyncProtocolType,
27
+ FlattenSyncProtocolIOFor,
28
+ ProtocolPrimitiveMap,
29
+ UtxorpcTxPredicate,
30
+ } from "@effectstream/config";
31
+ import type { StateUpdateStream } from "@effectstream/coroutine";
32
+ import { type JsonObject, Primitive } from "@effectstream/sm";
33
+ import {
34
+ type AddressAndType,
35
+ AddressType,
36
+ type EffectstreamBlockNumber,
37
+ uint8ArrayToHexString,
38
+ } from "@effectstream/utils";
39
+ import { mintBurnGrammar } from "./mint-burn-grammar.ts";
40
+ import { PrimitiveTypeCardanoMintBurn } from "../builtin.ts";
41
+ import {
42
+ addressToHex,
43
+ assetQuantityToString,
44
+ metadataToJson,
45
+ } from "../cardano-utils/cardano-helpers.ts";
46
+
47
+ export class CardanoMintBurnPrimitive extends Primitive<
48
+ ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL,
49
+ typeof mintBurnGrammar
50
+ > {
51
+ readonly internalTypeName = PrimitiveTypeCardanoMintBurn;
52
+ readonly policyIds: string[];
53
+ override grammar = mintBurnGrammar;
54
+
55
+ constructor(config: {
56
+ instanceName: string;
57
+ startBlockHeight: number;
58
+ stateMachinePrefix: string | undefined;
59
+ policyIds: string[];
60
+ }) {
61
+ super(config);
62
+ this.policyIds = config.policyIds.map((p) => p.toLowerCase());
63
+ }
64
+
65
+ override getConfig(): ProtocolPrimitiveMap[ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL] {
66
+ const predicate: UtxorpcTxPredicate =
67
+ this.policyIds.length === 1
68
+ ? { match: { cardano: { mints_asset: { policy_id: this.policyIds[0] } } } }
69
+ : {
70
+ any_of: this.policyIds.map((pid) => ({
71
+ match: { cardano: { mints_asset: { policy_id: pid } } },
72
+ })),
73
+ };
74
+
75
+ return {
76
+ name: this.instanceName,
77
+ type: this.internalTypeName,
78
+ startBlockHeight: this.startBlockHeight,
79
+ scheduledPrefix: this.stateMachinePrefix,
80
+ predicate,
81
+ } as const;
82
+ }
83
+
84
+ override *getPayload(
85
+ _: EffectstreamBlockNumber,
86
+ primitiveTransactionData: FlattenSyncProtocolIOFor<ConfigSyncProtocolType.CARDANO_UTXORPC_PARALLEL>,
87
+ ): StateUpdateStream<{
88
+ isBatched: boolean;
89
+ data: {
90
+ fromAddressAndType: AddressAndType;
91
+ stateMachinePayload: StaticDecode<CommandTuple<string, typeof mintBurnGrammar>> | null;
92
+ accountingPayload: JsonObject;
93
+ }[];
94
+ }> {
95
+ const tx = primitiveTransactionData.output.payload.tx;
96
+
97
+ const assets: { amount: string; policyId: string; assetName: string }[] = [];
98
+ for (const ma of tx.mint) {
99
+ const policyId = uint8ArrayToHexString(ma.policyId);
100
+ if (!this.policyIds.includes(policyId.toLowerCase())) continue;
101
+ for (const asset of ma.assets) {
102
+ assets.push({
103
+ amount: assetQuantityToString(asset),
104
+ policyId,
105
+ assetName: uint8ArrayToHexString(asset.name),
106
+ });
107
+ }
108
+ }
109
+
110
+ if (assets.length === 0) {
111
+ return { isBatched: false, data: [] };
112
+ }
113
+
114
+ const inputAddresses: string[] = [];
115
+ for (const input of tx.inputs) {
116
+ if (input.asOutput) {
117
+ const addr = addressToHex(input.asOutput.address);
118
+ if (!inputAddresses.includes(addr)) inputAddresses.push(addr);
119
+ }
120
+ }
121
+
122
+ const outputAddresses: string[] = [];
123
+ for (const output of tx.outputs) {
124
+ const addr = addressToHex(output.address);
125
+ if (!outputAddresses.includes(addr)) outputAddresses.push(addr);
126
+ }
127
+
128
+ const txId = uint8ArrayToHexString(tx.hash);
129
+ const metadata = metadataToJson(tx.auxiliary?.metadata ?? []);
130
+
131
+ const accountingPayload = {
132
+ txId,
133
+ metadata: metadata ? JSON.stringify(metadata) : null,
134
+ assets,
135
+ inputAddresses,
136
+ outputAddresses,
137
+ };
138
+
139
+ const stateMachinePayload: any = this.stateMachinePrefix
140
+ ? generateRawStmInput(this.grammar, this.stateMachinePrefix, {
141
+ txId,
142
+ metadata: metadata ? JSON.stringify(metadata) : "",
143
+ assets: JSON.stringify(assets),
144
+ inputAddresses: JSON.stringify(inputAddresses),
145
+ outputAddresses: JSON.stringify(outputAddresses),
146
+ })
147
+ : null;
148
+
149
+ return {
150
+ isBatched: false,
151
+ data: [
152
+ {
153
+ fromAddressAndType: { type: AddressType.NONE, address: "0x0" },
154
+ accountingPayload,
155
+ stateMachinePayload,
156
+ },
157
+ ],
158
+ };
159
+ }
160
+ }
@@ -0,0 +1,7 @@
1
+ import { Type } from "@sinclair/typebox";
2
+
3
+ export const poolDelegationGrammar = [
4
+ ["address", Type.String()],
5
+ ["pool", Type.String()],
6
+ ["epoch", Type.String()],
7
+ ] as const;
@@ -0,0 +1,65 @@
1
+ import { PrimitiveTypeCardanoPoolDelegation } from "../builtin.ts";
2
+
3
+ export function poolDelegationIvm(name: string) {
4
+ const lowerName = name.toLowerCase();
5
+ const validSQLName = lowerName.replace(/[^a-zA-Z0-9_]/g, "");
6
+ if (validSQLName !== lowerName) {
7
+ throw new Error(`Invalid name: ${name}`);
8
+ }
9
+
10
+ const intermediateTable = `primitives.cardano_pool_delegation_intermediate_${validSQLName}`;
11
+
12
+ return `
13
+ CREATE TABLE IF NOT EXISTS ${intermediateTable} (
14
+ primitive_name TEXT NOT NULL,
15
+ staking_credential TEXT NOT NULL,
16
+ pool_keyhash TEXT NOT NULL,
17
+ epoch INTEGER NOT NULL,
18
+ last_block_height INTEGER NOT NULL,
19
+ last_id INTEGER NOT NULL,
20
+ PRIMARY KEY (primitive_name, staking_credential)
21
+ );
22
+
23
+ CREATE OR REPLACE FUNCTION update_pool_delegation_${validSQLName}() RETURNS TRIGGER AS $$
24
+ BEGIN
25
+ IF NEW.payload_type = '${PrimitiveTypeCardanoPoolDelegation}'
26
+ AND NEW.primitive_name = '${name}' THEN
27
+
28
+ INSERT INTO ${intermediateTable} (
29
+ primitive_name, staking_credential, pool_keyhash, epoch, last_block_height, last_id
30
+ ) VALUES (
31
+ NEW.primitive_name,
32
+ NEW.payload->>'address',
33
+ NEW.payload->>'pool',
34
+ (NEW.payload->>'epoch')::INTEGER,
35
+ NEW.effectstream_block_height,
36
+ NEW.id
37
+ )
38
+ ON CONFLICT (primitive_name, staking_credential) DO UPDATE SET
39
+ pool_keyhash = EXCLUDED.pool_keyhash,
40
+ epoch = EXCLUDED.epoch,
41
+ last_block_height = EXCLUDED.last_block_height,
42
+ last_id = EXCLUDED.last_id;
43
+ END IF;
44
+
45
+ RETURN NEW;
46
+ END;
47
+ $$ LANGUAGE plpgsql;
48
+
49
+ CREATE TRIGGER trigger_update_pool_delegation_${validSQLName}
50
+ AFTER INSERT ON effectstream.primitive_accounting
51
+ FOR EACH ROW
52
+ EXECUTE FUNCTION update_pool_delegation_${validSQLName}();
53
+
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
+ `;
62
+ }
63
+
64
+ export const POOL_DELEGATION_VIEW_PREFIX = "cardano_pool_delegation_view_" as const;
65
+ export const POOL_DELEGATION_INTERMEDIATE_PREFIX = "cardano_pool_delegation_intermediate_" as const;