@effectstream/sm 0.101.1 → 0.103.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.
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/package.json +10 -10
- package/primitives/Primitive.ts +4 -1
- package/primitives/src/builtin.ts +24 -3
- package/primitives/src/grammar.ts +8 -0
- package/primitives/src/midnight-nullifier-and-commitment/midnight-nullifier-and-commitment-grammar.ts +37 -0
- package/primitives/src/{midnight-nullifier/midnight-nullifier.ts → midnight-nullifier-and-commitment/midnight-nullifier-and-commitment.ts} +37 -14
- package/primitives/src/midnight-token-mint/midnight-token-mint-grammar.ts +12 -4
- package/primitives/src/midnight-token-mint/midnight-token-mint-ivm.ts +105 -0
- package/primitives/src/midnight-token-mint/midnight-token-mint.test.ts +150 -0
- package/primitives/src/midnight-token-mint/midnight-token-mint.ts +80 -23
- package/primitives/src/midnight-unshielded-create/midnight-unshielded-create-grammar.ts +14 -1
- package/primitives/src/midnight-unshielded-create/midnight-unshielded-create.ts +4 -1
- package/primitives/src/midnight-unshielded-spend/midnight-unshielded-spend-grammar.ts +14 -1
- package/primitives/src/midnight-unshielded-spend/midnight-unshielded-spend.ts +4 -1
- package/primitives/src/mod.ts +17 -4
- package/primitives/src/solana-account-balance/solana-account-balance-grammar.ts +7 -0
- package/primitives/src/solana-account-balance/solana-account-balance-primitive.ts +108 -0
- package/primitives/src/solana-program-log/solana-program-log-grammar.ts +7 -0
- package/primitives/src/solana-program-log/solana-program-log-primitive.test.ts +128 -0
- package/primitives/src/solana-program-log/solana-program-log-primitive.ts +108 -0
- package/primitives/src/solana-token-account/solana-token-account-grammar.ts +13 -0
- package/primitives/src/solana-token-account/solana-token-account-primitive.test.ts +117 -0
- package/primitives/src/solana-token-account/solana-token-account-primitive.ts +156 -0
- package/primitives/src/midnight-nullifier/midnight-nullifier-grammar.ts +0 -8
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
import { run } from "effection";
|
|
3
|
+
import type {
|
|
4
|
+
ConfigSyncProtocolType,
|
|
5
|
+
FlattenSyncProtocolIOFor,
|
|
6
|
+
} from "@effectstream/config";
|
|
7
|
+
// Import via mod.ts (not the primitive file directly) to avoid the
|
|
8
|
+
// primitive ⇄ @effectstream/sm ⇄ mod.ts init cycle — same as erc20-primitive.test.ts.
|
|
9
|
+
import { MidnightTokenMintPrimitive } from "../mod.ts";
|
|
10
|
+
import { PrimitiveRegistry } from "../../PrimitiveRegistry.ts";
|
|
11
|
+
|
|
12
|
+
// Minimal view strategy — the IVM only calls strategy.createView(...).
|
|
13
|
+
const STRATEGY = {
|
|
14
|
+
migrationSuffix: "",
|
|
15
|
+
createView: (name: string, sql: string) => `CREATE VIEW ${name} AS ${sql};`,
|
|
16
|
+
} as any;
|
|
17
|
+
|
|
18
|
+
const MOCK_TX_DATA = {
|
|
19
|
+
output: {
|
|
20
|
+
payload: {
|
|
21
|
+
contractAddress: "0200abcd",
|
|
22
|
+
domainSep: "ab".repeat(32),
|
|
23
|
+
rawTokenType: "cd".repeat(32),
|
|
24
|
+
kind: "shielded",
|
|
25
|
+
amount: "18446744073709551615", // u64 max — exceeds MAX_SAFE_INTEGER
|
|
26
|
+
txHash: "ef".repeat(32),
|
|
27
|
+
entryPoint: "mint",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
} as unknown as FlattenSyncProtocolIOFor<
|
|
31
|
+
ConfigSyncProtocolType.MIDNIGHT_PARALLEL
|
|
32
|
+
>;
|
|
33
|
+
|
|
34
|
+
function cleanup() {
|
|
35
|
+
// The base constructor registers `this` by instanceName and throws on dupes.
|
|
36
|
+
PrimitiveRegistry.primitives = {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function getPayloadOnce(p: MidnightTokenMintPrimitive) {
|
|
40
|
+
let item: any;
|
|
41
|
+
await run(function* () {
|
|
42
|
+
const result = p.getPayload(123, MOCK_TX_DATA).next().value;
|
|
43
|
+
if (!result || !("isBatched" in result)) {
|
|
44
|
+
throw new Error("No payload generated");
|
|
45
|
+
}
|
|
46
|
+
item = result.data[0];
|
|
47
|
+
});
|
|
48
|
+
return item;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test("MidnightTokenMint - persist defaults to true and owns its table", () => {
|
|
52
|
+
cleanup();
|
|
53
|
+
const p = new MidnightTokenMintPrimitive({
|
|
54
|
+
instanceName: "tok",
|
|
55
|
+
startBlockHeight: 1,
|
|
56
|
+
stateMachinePrefix: undefined,
|
|
57
|
+
});
|
|
58
|
+
expect(p.persist).toBe(true);
|
|
59
|
+
|
|
60
|
+
const ddl = p.getDynamicTables("tok", STRATEGY);
|
|
61
|
+
expect(ddl).toBeDefined();
|
|
62
|
+
// Owned intermediate table + the trigger on primitive_accounting.
|
|
63
|
+
expect(ddl).toContain("primitives.midnight_token_mint_intermediate_tok");
|
|
64
|
+
expect(ddl).toContain("trigger_update_midnight_token_mint_tok");
|
|
65
|
+
expect(ddl).toContain("effectstream.primitive_accounting");
|
|
66
|
+
expect(p.getViewPrefix()).toContain("midnight_token_mint_view_");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("MidnightTokenMint - persist:false disables the owned table", () => {
|
|
70
|
+
cleanup();
|
|
71
|
+
const p = new MidnightTokenMintPrimitive({
|
|
72
|
+
instanceName: "tok",
|
|
73
|
+
startBlockHeight: 1,
|
|
74
|
+
stateMachinePrefix: undefined,
|
|
75
|
+
persist: false,
|
|
76
|
+
});
|
|
77
|
+
expect(p.persist).toBe(false);
|
|
78
|
+
// No DDL emitted → createDynamicTables skips it → no table/trigger.
|
|
79
|
+
expect(p.getDynamicTables("tok", STRATEGY)).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("MidnightTokenMint - hyphenated instance names are sanitized, not rejected", () => {
|
|
83
|
+
cleanup();
|
|
84
|
+
// "Midnight-TokenMint" is the repo's own naming convention. Throwing here
|
|
85
|
+
// killed the sync node at startup ("Invalid name: Midnight-TokenMint").
|
|
86
|
+
const p = new MidnightTokenMintPrimitive({
|
|
87
|
+
instanceName: "Midnight-TokenMint",
|
|
88
|
+
startBlockHeight: 1,
|
|
89
|
+
stateMachinePrefix: "midnightTokenMintState",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const ddl = p.getDynamicTables("Midnight-TokenMint", STRATEGY)!;
|
|
93
|
+
expect(ddl).toContain(
|
|
94
|
+
"primitives.midnight_token_mint_view_midnighttokenmint",
|
|
95
|
+
);
|
|
96
|
+
expect(ddl).toContain(
|
|
97
|
+
"primitives.midnight_token_mint_intermediate_midnighttokenmint",
|
|
98
|
+
);
|
|
99
|
+
// No hyphen may survive into an SQL identifier.
|
|
100
|
+
expect(ddl).not.toContain("midnight_token_mint_view_midnight-tokenmint");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("MidnightTokenMint - a quote in the name is escaped in the trigger literal", () => {
|
|
104
|
+
cleanup();
|
|
105
|
+
// The name is stripped for identifiers but compared as a string literal in
|
|
106
|
+
// the trigger body, where a bare `'` would emit unparseable DDL.
|
|
107
|
+
const p = new MidnightTokenMintPrimitive({
|
|
108
|
+
instanceName: "Bob's-Mints",
|
|
109
|
+
startBlockHeight: 1,
|
|
110
|
+
stateMachinePrefix: undefined,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const ddl = p.getDynamicTables("Bob's-Mints", STRATEGY)!;
|
|
114
|
+
expect(ddl).toContain("NEW.primitive_name = 'Bob''s-Mints'");
|
|
115
|
+
expect(ddl).toContain("midnight_token_mint_view_bobsmints");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("MidnightTokenMint - owning a table does not suppress the STM input", async () => {
|
|
119
|
+
cleanup();
|
|
120
|
+
const p = new MidnightTokenMintPrimitive({
|
|
121
|
+
instanceName: "tok",
|
|
122
|
+
startBlockHeight: 1,
|
|
123
|
+
stateMachinePrefix: "midnightTokenMintState",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Both paths are live at once: owned table DDL *and* an STM payload.
|
|
127
|
+
expect(p.getDynamicTables("tok", STRATEGY)).toBeDefined();
|
|
128
|
+
expect(p.getConfig().scheduledPrefix).toBe("midnightTokenMintState");
|
|
129
|
+
|
|
130
|
+
const item = await getPayloadOnce(p);
|
|
131
|
+
expect(Array.isArray(item.stateMachinePayload)).toBe(true);
|
|
132
|
+
expect(item.stateMachinePayload[0]).toBe("midnightTokenMintState");
|
|
133
|
+
// Flat fields, in grammar order, so the STM handler reads them by name.
|
|
134
|
+
expect(item.accountingPayload.rawTokenType).toBe("cd".repeat(32));
|
|
135
|
+
expect(item.accountingPayload.amount).toBe("18446744073709551615");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("MidnightTokenMint - no prefix means accounting only (opt-out is explicit)", async () => {
|
|
139
|
+
cleanup();
|
|
140
|
+
const p = new MidnightTokenMintPrimitive({
|
|
141
|
+
instanceName: "tok",
|
|
142
|
+
startBlockHeight: 1,
|
|
143
|
+
stateMachinePrefix: undefined,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const item = await getPayloadOnce(p);
|
|
147
|
+
expect(item.stateMachinePayload).toBe(null);
|
|
148
|
+
// The accounting row (and therefore the owned table) is still written.
|
|
149
|
+
expect(item.accountingPayload.rawTokenType).toBe("cd".repeat(32));
|
|
150
|
+
});
|
|
@@ -18,31 +18,50 @@ import type {
|
|
|
18
18
|
import type { SyncStateUpdateStream } from "@effectstream/coroutine";
|
|
19
19
|
import { PrimitiveTypeMidnightTokenMint } from "../builtin.ts";
|
|
20
20
|
import { midnightTokenMintGrammar } from "./midnight-token-mint-grammar.ts";
|
|
21
|
+
import {
|
|
22
|
+
MIDNIGHT_TOKEN_MINT_INTERMEDIATE_PREFIX,
|
|
23
|
+
MIDNIGHT_TOKEN_MINT_VIEW_PREFIX,
|
|
24
|
+
midnightTokenMintIvm,
|
|
25
|
+
} from "./midnight-token-mint-ivm.ts";
|
|
21
26
|
|
|
22
27
|
/**
|
|
23
28
|
* MidnightTokenMintPrimitive
|
|
24
29
|
*
|
|
25
|
-
* Watches custom token *mints* on the Midnight ledger
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* emits one state-machine input per `(tx, call, domainSep, kind)` — only
|
|
33
|
-
* for effects that actually applied on chain (failed segments roll back).
|
|
30
|
+
* Watches custom token *mints* on the Midnight ledger and maps a token id
|
|
31
|
+
* ("color") back to the contract that minted it — the mapping wallets cannot
|
|
32
|
+
* provide. A contract call's effects record mints as `domain_sep → amount`
|
|
33
|
+
* maps (shielded and unshielded); the token type is
|
|
34
|
+
* `rawTokenType(domain_sep, contract_address)`. The fetcher deserializes each
|
|
35
|
+
* regular transaction with ledger-v8, walks the contract calls, and emits one
|
|
36
|
+
* event per `(tx, call, domainSep, kind)`.
|
|
34
37
|
*
|
|
35
|
-
* The
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
+
* The primitive OWNS its registry table: `dynamicTables` declares
|
|
39
|
+
* `primitives.midnight_token_mint_view_<instance>` (maintained by a trigger on
|
|
40
|
+
* effectstream.primitive_accounting), so a consumer gets the registry with
|
|
41
|
+
* only an `.addPrimitive(...)` line — no state-machine handler, grammar entry,
|
|
42
|
+
* or migration. Set `persist: false` in config to skip the owned table on a
|
|
43
|
+
* fresh database (the accounting row is still written; useful when a consumer
|
|
44
|
+
* wants to handle the data itself or skip it) — see the flag's own docs for why
|
|
45
|
+
* it does not tear down a table an earlier run already created.
|
|
38
46
|
*
|
|
39
|
-
*
|
|
47
|
+
* The owned table does NOT replace the state machine: the two paths are
|
|
48
|
+
* independent and run together. Whenever `stateMachinePrefix` is set the
|
|
49
|
+
* primitive still emits an STM input for every mint, exactly as it would
|
|
50
|
+
* without an owned table — the registry is the read model, the STM handler is
|
|
51
|
+
* where app-specific logic lives. Pass `stateMachinePrefix: undefined` to opt
|
|
52
|
+
* out explicitly (the flag is a required config key so the choice is never
|
|
53
|
+
* made by omission).
|
|
54
|
+
*
|
|
55
|
+
* Usage (STM path, in addition to the owned view):
|
|
56
|
+
* grammar: { myPrefix: builtinGrammars.midnightTokenMint }
|
|
40
57
|
* stm.addStateTransition("myPrefix", function* (data) {
|
|
41
|
-
* const {
|
|
42
|
-
*
|
|
43
|
-
* // kind: "shielded" | "unshielded", amount, entryPoint }
|
|
58
|
+
* const { rawTokenType, kind, contractAddress, domainSep, amount, txHash }
|
|
59
|
+
* = data.parsedInput;
|
|
44
60
|
* // amount is a decimal string (u64 mints can exceed MAX_SAFE_INTEGER)
|
|
45
61
|
* });
|
|
62
|
+
*
|
|
63
|
+
* The mint nonce is NOT part of token identity (it only randomizes coin
|
|
64
|
+
* commitments) and is never public for shielded mints, so it is not recorded.
|
|
46
65
|
*/
|
|
47
66
|
export class MidnightTokenMintPrimitive extends Primitive<
|
|
48
67
|
ConfigSyncProtocolType.MIDNIGHT_PARALLEL,
|
|
@@ -51,20 +70,42 @@ export class MidnightTokenMintPrimitive extends Primitive<
|
|
|
51
70
|
readonly internalTypeName = PrimitiveTypeMidnightTokenMint;
|
|
52
71
|
override readonly grammar = midnightTokenMintGrammar;
|
|
53
72
|
|
|
54
|
-
|
|
73
|
+
/**
|
|
74
|
+
* When true (default) the primitive owns + populates its registry table.
|
|
75
|
+
*
|
|
76
|
+
* Only affects whether the DDL is emitted, and the DDL is applied once per
|
|
77
|
+
* instance behind a migration row with no down path — so flipping this to
|
|
78
|
+
* `false` on a database that already ran with `true` leaves the table and its
|
|
79
|
+
* trigger in place, still accumulating. It skips the table on a fresh
|
|
80
|
+
* database; it is not a runtime off switch.
|
|
81
|
+
*/
|
|
82
|
+
readonly persist: boolean;
|
|
83
|
+
|
|
84
|
+
// Owned table — gated by `persist`. When disabled, no DDL is emitted, so no
|
|
85
|
+
// table/trigger is created and nothing is consolidated (accounting only).
|
|
86
|
+
override dynamicTables = (
|
|
87
|
+
name: string,
|
|
88
|
+
strategy: Parameters<typeof midnightTokenMintIvm>[1],
|
|
89
|
+
): string | undefined =>
|
|
90
|
+
this.persist ? midnightTokenMintIvm(name, strategy) : undefined;
|
|
91
|
+
|
|
55
92
|
override getIntermediatePrefix(): string[] {
|
|
56
|
-
return [];
|
|
93
|
+
return [MIDNIGHT_TOKEN_MINT_INTERMEDIATE_PREFIX];
|
|
57
94
|
}
|
|
58
95
|
override getViewPrefix(): string[] {
|
|
59
|
-
return [];
|
|
96
|
+
return [MIDNIGHT_TOKEN_MINT_VIEW_PREFIX];
|
|
60
97
|
}
|
|
61
98
|
|
|
62
99
|
constructor(config: {
|
|
63
100
|
instanceName: string;
|
|
64
101
|
startBlockHeight: number;
|
|
65
|
-
|
|
102
|
+
// Required key (may be `undefined`) like every other primitive: owning a
|
|
103
|
+
// table is not a reason to skip the STM, so opting out has to be deliberate.
|
|
104
|
+
stateMachinePrefix: string | undefined;
|
|
105
|
+
persist?: boolean;
|
|
66
106
|
}) {
|
|
67
107
|
super(config);
|
|
108
|
+
this.persist = config.persist ?? true;
|
|
68
109
|
}
|
|
69
110
|
|
|
70
111
|
override *getPayload(
|
|
@@ -82,10 +123,26 @@ export class MidnightTokenMintPrimitive extends Primitive<
|
|
|
82
123
|
accountingPayload: ParamToData<typeof midnightTokenMintGrammar>;
|
|
83
124
|
}[];
|
|
84
125
|
}> {
|
|
85
|
-
const
|
|
126
|
+
const p = primitiveTransactionData.output.payload as {
|
|
127
|
+
contractAddress: string;
|
|
128
|
+
domainSep: string;
|
|
129
|
+
rawTokenType: string;
|
|
130
|
+
kind: string;
|
|
131
|
+
amount: string;
|
|
132
|
+
txHash: string;
|
|
133
|
+
entryPoint?: string;
|
|
134
|
+
};
|
|
86
135
|
|
|
136
|
+
// Flat accounting payload so the owned-table trigger reads columns via
|
|
137
|
+
// payload->>'field' (matches ERC20/NEP141).
|
|
87
138
|
const accountingPayload: ParamToData<typeof this.grammar> = {
|
|
88
|
-
|
|
139
|
+
contractAddress: p.contractAddress,
|
|
140
|
+
domainSep: p.domainSep,
|
|
141
|
+
rawTokenType: p.rawTokenType,
|
|
142
|
+
kind: p.kind,
|
|
143
|
+
amount: p.amount,
|
|
144
|
+
txHash: p.txHash,
|
|
145
|
+
entryPoint: p.entryPoint ?? "",
|
|
89
146
|
};
|
|
90
147
|
|
|
91
148
|
const stateMachinePayload:
|
|
@@ -120,7 +177,7 @@ export class MidnightTokenMintPrimitive extends Primitive<
|
|
|
120
177
|
name: this.instanceName,
|
|
121
178
|
type: this.internalTypeName,
|
|
122
179
|
startBlockHeight: this.startBlockHeight,
|
|
123
|
-
scheduledPrefix: this.stateMachinePrefix
|
|
180
|
+
scheduledPrefix: this.stateMachinePrefix,
|
|
124
181
|
} as const;
|
|
125
182
|
}
|
|
126
183
|
}
|
|
@@ -3,6 +3,19 @@ import { Type } from "@sinclair/typebox";
|
|
|
3
3
|
export const midnightUnshieldedCreateGrammar = [
|
|
4
4
|
[
|
|
5
5
|
"payload",
|
|
6
|
-
Type.
|
|
6
|
+
Type.Object({
|
|
7
|
+
/** Bech32m owner address of the created UTXO */
|
|
8
|
+
owner: Type.String(),
|
|
9
|
+
/** Hex-encoded hash of the creating intent */
|
|
10
|
+
intentHash: Type.String(),
|
|
11
|
+
/** Output index within the creating intent's offer */
|
|
12
|
+
outputIndex: Type.Number(),
|
|
13
|
+
/** u128 value as a decimal string */
|
|
14
|
+
value: Type.String(),
|
|
15
|
+
/** Hex-encoded serialized token type */
|
|
16
|
+
tokenType: Type.String(),
|
|
17
|
+
/** Hash of the creating transaction */
|
|
18
|
+
txHash: Type.String(),
|
|
19
|
+
}),
|
|
7
20
|
],
|
|
8
21
|
] as const;
|
|
@@ -77,7 +77,10 @@ export class MidnightUnshieldedCreatePrimitive extends Primitive<
|
|
|
77
77
|
accountingPayload: ParamToData<typeof midnightUnshieldedCreateGrammar>;
|
|
78
78
|
}[];
|
|
79
79
|
}> {
|
|
80
|
-
|
|
80
|
+
// The sync-protocol payload map types Midnight payloads as Record<string, any>;
|
|
81
|
+
// the fetcher's fetchUnshieldedCreates builds exactly the grammar's payload shape.
|
|
82
|
+
const payload = primitiveTransactionData.output
|
|
83
|
+
.payload as ParamToData<typeof this.grammar>["payload"];
|
|
81
84
|
|
|
82
85
|
const accountingPayload: ParamToData<typeof this.grammar> = {
|
|
83
86
|
payload,
|
|
@@ -3,6 +3,19 @@ import { Type } from "@sinclair/typebox";
|
|
|
3
3
|
export const midnightUnshieldedSpendGrammar = [
|
|
4
4
|
[
|
|
5
5
|
"payload",
|
|
6
|
-
Type.
|
|
6
|
+
Type.Object({
|
|
7
|
+
/** Bech32m owner address of the spent UTXO */
|
|
8
|
+
owner: Type.String(),
|
|
9
|
+
/** Hex-encoded hash of the intent that CREATED the spent UTXO */
|
|
10
|
+
intentHash: Type.String(),
|
|
11
|
+
/** Output index within the creating intent's offer */
|
|
12
|
+
outputIndex: Type.Number(),
|
|
13
|
+
/** u128 value as a decimal string */
|
|
14
|
+
value: Type.String(),
|
|
15
|
+
/** Hex-encoded serialized token type */
|
|
16
|
+
tokenType: Type.String(),
|
|
17
|
+
/** Hash of the spending transaction */
|
|
18
|
+
txHash: Type.String(),
|
|
19
|
+
}),
|
|
7
20
|
],
|
|
8
21
|
] as const;
|
|
@@ -71,7 +71,10 @@ export class MidnightUnshieldedSpendPrimitive extends Primitive<
|
|
|
71
71
|
accountingPayload: ParamToData<typeof midnightUnshieldedSpendGrammar>;
|
|
72
72
|
}[];
|
|
73
73
|
}> {
|
|
74
|
-
|
|
74
|
+
// The sync-protocol payload map types Midnight payloads as Record<string, any>;
|
|
75
|
+
// the fetcher's fetchUnshieldedSpends builds exactly the grammar's payload shape.
|
|
76
|
+
const payload = primitiveTransactionData.output
|
|
77
|
+
.payload as ParamToData<typeof this.grammar>["payload"];
|
|
75
78
|
|
|
76
79
|
const accountingPayload: ParamToData<typeof this.grammar> = {
|
|
77
80
|
payload,
|
package/primitives/src/mod.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PrimitiveTypeMidnightGeneric,
|
|
3
|
-
|
|
3
|
+
PrimitiveTypeMidnightNullifierAndCommitment,
|
|
4
4
|
PrimitiveTypeMidnightUnshieldedSpend,
|
|
5
5
|
PrimitiveTypeMidnightUnshieldedCreate,
|
|
6
6
|
PrimitiveTypeMidnightZswapRoot,
|
|
@@ -19,6 +19,9 @@ import {
|
|
|
19
19
|
PrimitiveTypeNEARIntent,
|
|
20
20
|
PrimitiveTypeNEARGeneric,
|
|
21
21
|
PrimitiveTypeNEARAccountWatch,
|
|
22
|
+
PrimitiveTypeSolanaProgramLog,
|
|
23
|
+
PrimitiveTypeSolanaAccountBalance,
|
|
24
|
+
PrimitiveTypeSolanaTokenAccount,
|
|
22
25
|
PrimitiveTypeCardanoMintBurn,
|
|
23
26
|
PrimitiveTypeCardanoTransfer,
|
|
24
27
|
PrimitiveTypeCardanoPoolDelegation,
|
|
@@ -28,7 +31,8 @@ import {
|
|
|
28
31
|
} from "./builtin.ts";
|
|
29
32
|
|
|
30
33
|
import { MidnightGenericPrimitive } from "./midnight-generic/midnight-genetic.ts";
|
|
31
|
-
import {
|
|
34
|
+
import { MidnightNullifierAndCommitmentPrimitive } from "./midnight-nullifier-and-commitment/midnight-nullifier-and-commitment.ts";
|
|
35
|
+
export type { MidnightZswapCapture } from "./midnight-nullifier-and-commitment/midnight-nullifier-and-commitment.ts";
|
|
32
36
|
import { MidnightUnshieldedSpendPrimitive } from "./midnight-unshielded-spend/midnight-unshielded-spend.ts";
|
|
33
37
|
import { MidnightUnshieldedCreatePrimitive } from "./midnight-unshielded-create/midnight-unshielded-create.ts";
|
|
34
38
|
import { MidnightZswapRootPrimitive } from "./midnight-zswap-root/midnight-zswap-root.ts";
|
|
@@ -47,6 +51,9 @@ import { Nep245Primitive } from "./near-nep245/nep245-primitive.ts";
|
|
|
47
51
|
import { NearIntentPrimitive } from "./near-intent/near-intent-primitive.ts";
|
|
48
52
|
import { NearGenericPrimitive } from "./near-generic/near-generic-primitive.ts";
|
|
49
53
|
import { NearAccountWatchPrimitive } from "./near-account-watch/near-account-watch-primitive.ts";
|
|
54
|
+
import { SolanaProgramLogPrimitive } from "./solana-program-log/solana-program-log-primitive.ts";
|
|
55
|
+
import { SolanaAccountBalancePrimitive } from "./solana-account-balance/solana-account-balance-primitive.ts";
|
|
56
|
+
import { SolanaTokenAccountPrimitive } from "./solana-token-account/solana-token-account-primitive.ts";
|
|
50
57
|
import { CardanoMintBurnPrimitive } from "./cardano-mint-burn/mint-burn-primitive.ts";
|
|
51
58
|
import { CardanoTransferPrimitive } from "./cardano-transfer/transfer-primitive.ts";
|
|
52
59
|
import { CardanoPoolDelegationPrimitive } from "./cardano-pool-delegation/pool-delegation-primitive.ts";
|
|
@@ -56,7 +63,7 @@ import { CardanoProjectedNftPrimitive } from "./cardano-projected-nft/projected-
|
|
|
56
63
|
|
|
57
64
|
const builtInPrimitivesMap = {
|
|
58
65
|
[PrimitiveTypeMidnightGeneric]: MidnightGenericPrimitive,
|
|
59
|
-
[
|
|
66
|
+
[PrimitiveTypeMidnightNullifierAndCommitment]: MidnightNullifierAndCommitmentPrimitive,
|
|
60
67
|
[PrimitiveTypeMidnightUnshieldedSpend]: MidnightUnshieldedSpendPrimitive,
|
|
61
68
|
[PrimitiveTypeMidnightUnshieldedCreate]: MidnightUnshieldedCreatePrimitive,
|
|
62
69
|
[PrimitiveTypeMidnightZswapRoot]: MidnightZswapRootPrimitive,
|
|
@@ -75,6 +82,9 @@ const builtInPrimitivesMap = {
|
|
|
75
82
|
[PrimitiveTypeNEARIntent]: NearIntentPrimitive,
|
|
76
83
|
[PrimitiveTypeNEARGeneric]: NearGenericPrimitive,
|
|
77
84
|
[PrimitiveTypeNEARAccountWatch]: NearAccountWatchPrimitive,
|
|
85
|
+
[PrimitiveTypeSolanaProgramLog]: SolanaProgramLogPrimitive,
|
|
86
|
+
[PrimitiveTypeSolanaAccountBalance]: SolanaAccountBalancePrimitive,
|
|
87
|
+
[PrimitiveTypeSolanaTokenAccount]: SolanaTokenAccountPrimitive,
|
|
78
88
|
[PrimitiveTypeCardanoMintBurn]: CardanoMintBurnPrimitive,
|
|
79
89
|
[PrimitiveTypeCardanoTransfer]: CardanoTransferPrimitive,
|
|
80
90
|
[PrimitiveTypeCardanoPoolDelegation]: CardanoPoolDelegationPrimitive,
|
|
@@ -89,7 +99,7 @@ export {
|
|
|
89
99
|
|
|
90
100
|
// Built-in Primitives
|
|
91
101
|
MidnightGenericPrimitive,
|
|
92
|
-
|
|
102
|
+
MidnightNullifierAndCommitmentPrimitive,
|
|
93
103
|
MidnightUnshieldedSpendPrimitive,
|
|
94
104
|
MidnightUnshieldedCreatePrimitive,
|
|
95
105
|
MidnightZswapRootPrimitive,
|
|
@@ -108,6 +118,9 @@ export {
|
|
|
108
118
|
NearIntentPrimitive,
|
|
109
119
|
NearGenericPrimitive,
|
|
110
120
|
NearAccountWatchPrimitive,
|
|
121
|
+
SolanaProgramLogPrimitive,
|
|
122
|
+
SolanaAccountBalancePrimitive,
|
|
123
|
+
SolanaTokenAccountPrimitive,
|
|
111
124
|
CardanoMintBurnPrimitive,
|
|
112
125
|
CardanoTransferPrimitive,
|
|
113
126
|
CardanoPoolDelegationPrimitive,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { StaticDecode } from "@sinclair/typebox";
|
|
2
|
+
import type {
|
|
3
|
+
ConfigSyncProtocolType,
|
|
4
|
+
FlattenSyncProtocolIOFor,
|
|
5
|
+
ProtocolPrimitiveMap,
|
|
6
|
+
} from "@effectstream/config";
|
|
7
|
+
import {
|
|
8
|
+
type AddressAndType,
|
|
9
|
+
AddressType,
|
|
10
|
+
type EffectstreamBlockNumber,
|
|
11
|
+
} from "@effectstream/utils";
|
|
12
|
+
import { type JsonObject, Primitive } from "@effectstream/sm";
|
|
13
|
+
import {
|
|
14
|
+
type CommandTuple,
|
|
15
|
+
generateRawStmInput,
|
|
16
|
+
type ParamToData,
|
|
17
|
+
} from "@effectstream/concise";
|
|
18
|
+
import type { StateUpdateStream } from "@effectstream/coroutine";
|
|
19
|
+
import { solanaAccountBalanceGrammar } from "./solana-account-balance-grammar.ts";
|
|
20
|
+
import { PrimitiveTypeSolanaAccountBalance } from "../builtin.ts";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Built-in primitive: tracks the lamport balance of a watched `address` per
|
|
24
|
+
* slot (derived from a transaction's postBalances by SolanaFetcher.readPrimitives).
|
|
25
|
+
*/
|
|
26
|
+
export class SolanaAccountBalancePrimitive extends Primitive<
|
|
27
|
+
ConfigSyncProtocolType.SOLANA_RPC_PARALLEL,
|
|
28
|
+
typeof solanaAccountBalanceGrammar
|
|
29
|
+
> {
|
|
30
|
+
readonly internalTypeName = PrimitiveTypeSolanaAccountBalance;
|
|
31
|
+
override grammar = solanaAccountBalanceGrammar;
|
|
32
|
+
readonly address: string;
|
|
33
|
+
|
|
34
|
+
constructor(config: {
|
|
35
|
+
instanceName: string;
|
|
36
|
+
startBlockHeight: number;
|
|
37
|
+
address: string;
|
|
38
|
+
stateMachinePrefix: string | undefined;
|
|
39
|
+
}) {
|
|
40
|
+
super(config);
|
|
41
|
+
this.address = config.address;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override *getPayload(
|
|
45
|
+
_: EffectstreamBlockNumber,
|
|
46
|
+
primitiveTransactionData: FlattenSyncProtocolIOFor<
|
|
47
|
+
ConfigSyncProtocolType.SOLANA_RPC_PARALLEL
|
|
48
|
+
>,
|
|
49
|
+
): StateUpdateStream<{
|
|
50
|
+
isBatched: boolean;
|
|
51
|
+
data: {
|
|
52
|
+
fromAddressAndType: AddressAndType;
|
|
53
|
+
stateMachinePayload:
|
|
54
|
+
| StaticDecode<CommandTuple<string, typeof solanaAccountBalanceGrammar>>
|
|
55
|
+
| null;
|
|
56
|
+
accountingPayload: JsonObject;
|
|
57
|
+
}[];
|
|
58
|
+
}> {
|
|
59
|
+
const payload = primitiveTransactionData.output.payload as {
|
|
60
|
+
address?: string;
|
|
61
|
+
lamports?: number;
|
|
62
|
+
slot?: number;
|
|
63
|
+
};
|
|
64
|
+
const address = String(payload.address ?? this.address);
|
|
65
|
+
const lamports = Number(payload.lamports ?? 0);
|
|
66
|
+
const slot = Number(payload.slot ?? 0);
|
|
67
|
+
|
|
68
|
+
const accountingPayload: ParamToData<typeof solanaAccountBalanceGrammar> = {
|
|
69
|
+
address,
|
|
70
|
+
lamports,
|
|
71
|
+
slot,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const stateMachinePayload = this.stateMachinePrefix
|
|
75
|
+
? generateRawStmInput(
|
|
76
|
+
this.grammar,
|
|
77
|
+
this.stateMachinePrefix,
|
|
78
|
+
accountingPayload,
|
|
79
|
+
)
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
isBatched: false,
|
|
84
|
+
data: [
|
|
85
|
+
{
|
|
86
|
+
fromAddressAndType: {
|
|
87
|
+
type: AddressType.SOLANA,
|
|
88
|
+
address,
|
|
89
|
+
},
|
|
90
|
+
accountingPayload,
|
|
91
|
+
stateMachinePayload,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override getConfig(): ProtocolPrimitiveMap[
|
|
98
|
+
ConfigSyncProtocolType.SOLANA_RPC_PARALLEL
|
|
99
|
+
] {
|
|
100
|
+
return {
|
|
101
|
+
name: this.instanceName,
|
|
102
|
+
type: this.internalTypeName,
|
|
103
|
+
startBlockHeight: this.startBlockHeight,
|
|
104
|
+
address: this.address,
|
|
105
|
+
scheduledPrefix: this.stateMachinePrefix,
|
|
106
|
+
} as ProtocolPrimitiveMap[ConfigSyncProtocolType.SOLANA_RPC_PARALLEL];
|
|
107
|
+
}
|
|
108
|
+
}
|