@effectstream/sm 0.103.3 → 0.104.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/package.json +10 -9
- package/primitives/src/avail-generic/avail-primitive.ts +2 -1
- package/primitives/src/bitcoin-address/bitcoin-primitive.ts +2 -2
- package/primitives/src/cardano-delayed-asset/delayed-asset-primitive.ts +2 -1
- package/primitives/src/cardano-mint-burn/mint-burn-primitive.ts +2 -1
- package/primitives/src/cardano-pool-delegation/pool-delegation-primitive.ts +2 -1
- package/primitives/src/cardano-projected-nft/projected-nft-primitive.ts +2 -1
- package/primitives/src/cardano-transfer/transfer-grammar.ts +16 -1
- package/primitives/src/cardano-transfer/transfer-primitive.test.ts +106 -0
- package/primitives/src/cardano-transfer/transfer-primitive.ts +19 -5
- package/primitives/src/cardano-utils/cardano-helpers.ts +5 -0
- package/primitives/src/celestia-generic/celestia-primitive.ts +2 -1
- package/primitives/src/evm-effectstream-l2/effectstream-l2-primitive.ts +3 -2
- package/primitives/src/evm-erc1155/erc1155-primitive.ts +2 -1
- package/primitives/src/evm-erc20/erc20-primitive.ts +2 -1
- package/primitives/src/evm-erc721/erc721-primitive.ts +2 -1
- package/primitives/src/evm-generic/evm-generic-primitive.ts +2 -1
- package/primitives/src/midnight-generic/midnight-generic.test.ts +93 -0
- package/primitives/src/midnight-generic/midnight-genetic.ts +49 -13
- package/primitives/src/midnight-nullifier-and-commitment/midnight-nullifier-and-commitment.ts +1 -1
- package/primitives/src/midnight-token-mint/midnight-token-mint.ts +1 -1
- package/primitives/src/midnight-unshielded-create/midnight-unshielded-create.ts +1 -1
- package/primitives/src/midnight-unshielded-spend/midnight-unshielded-spend.ts +1 -1
- package/primitives/src/midnight-zswap-root/midnight-zswap-root.ts +1 -1
- package/primitives/src/near-account-watch/near-account-watch-primitive.ts +2 -1
- package/primitives/src/near-generic/near-generic-primitive.ts +2 -1
- package/primitives/src/near-intent/near-intent-primitive.ts +2 -1
- package/primitives/src/near-nep141/nep141-primitive.ts +2 -1
- package/primitives/src/near-nep171/nep171-primitive.ts +2 -1
- package/primitives/src/near-nep245/nep245-primitive.ts +2 -1
- package/primitives/src/solana-account-balance/solana-account-balance-primitive.ts +2 -1
- package/primitives/src/solana-program-log/solana-program-log-primitive.ts +2 -1
- package/primitives/src/solana-token-account/solana-token-account-primitive.ts +2 -1
- package/primitives/src/utxorpc-generic/utxorpc-generic.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effectstream/sm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.104.0",
|
|
4
4
|
"license": "(MIT OR Apache-2.0)",
|
|
5
5
|
"homepage": "https://effectstream.github.io/docs/",
|
|
6
6
|
"repository": {
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
"./grammar": "./primitives/src/grammar.ts"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@effectstream/concise": "0.
|
|
21
|
-
"@effectstream/config": "0.
|
|
22
|
-
"@effectstream/coroutine": "0.
|
|
23
|
-
"@effectstream/crypto": "0.
|
|
24
|
-
"@effectstream/db": "0.
|
|
25
|
-
"@effectstream/event-client": "0.
|
|
26
|
-
"@effectstream/log": "0.
|
|
27
|
-
"@effectstream/utils": "0.
|
|
20
|
+
"@effectstream/concise": "0.104.0",
|
|
21
|
+
"@effectstream/config": "0.104.0",
|
|
22
|
+
"@effectstream/coroutine": "0.104.0",
|
|
23
|
+
"@effectstream/crypto": "0.104.0",
|
|
24
|
+
"@effectstream/db": "0.104.0",
|
|
25
|
+
"@effectstream/event-client": "0.104.0",
|
|
26
|
+
"@effectstream/log": "0.104.0",
|
|
27
|
+
"@effectstream/utils": "0.104.0",
|
|
28
|
+
"@noble/hashes": "^2.2.0",
|
|
28
29
|
"pg": "8.21.0",
|
|
29
30
|
"pg-tx": "^1.0.1",
|
|
30
31
|
"@sinclair/typebox": "0.34.41",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Primitive } from "../../Primitive.ts";
|
|
2
|
+
import type { JsonObject } from "../../types.ts";
|
|
2
3
|
import type { StaticDecode } from "@sinclair/typebox";
|
|
3
4
|
import { type CommandTuple, generateRawStmInput } from "@effectstream/concise";
|
|
4
5
|
import type {
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
type ParamToData,
|
|
@@ -110,4 +111,3 @@ export class BitcoinAddressPrimitive extends Primitive<
|
|
|
110
111
|
} as const;
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
|
-
|
|
@@ -35,7 +35,8 @@ import type {
|
|
|
35
35
|
UtxorpcTxPredicate,
|
|
36
36
|
} from "@effectstream/config";
|
|
37
37
|
import type { StateUpdateStream } from "@effectstream/coroutine";
|
|
38
|
-
import {
|
|
38
|
+
import { Primitive } from "../../Primitive.ts";
|
|
39
|
+
import type { JsonObject } from "../../types.ts";
|
|
39
40
|
import {
|
|
40
41
|
type AddressAndType,
|
|
41
42
|
AddressType,
|
|
@@ -29,7 +29,8 @@ import type {
|
|
|
29
29
|
UtxorpcTxPredicate,
|
|
30
30
|
} from "@effectstream/config";
|
|
31
31
|
import type { StateUpdateStream } from "@effectstream/coroutine";
|
|
32
|
-
import {
|
|
32
|
+
import { Primitive } from "../../Primitive.ts";
|
|
33
|
+
import type { JsonObject } from "../../types.ts";
|
|
33
34
|
import {
|
|
34
35
|
type AddressAndType,
|
|
35
36
|
AddressType,
|
|
@@ -32,7 +32,8 @@ import type {
|
|
|
32
32
|
ProtocolPrimitiveMap,
|
|
33
33
|
} from "@effectstream/config";
|
|
34
34
|
import type { StateUpdateStream } from "@effectstream/coroutine";
|
|
35
|
-
import {
|
|
35
|
+
import { Primitive } from "../../Primitive.ts";
|
|
36
|
+
import type { JsonObject } from "../../types.ts";
|
|
36
37
|
import {
|
|
37
38
|
type AddressAndType,
|
|
38
39
|
AddressType,
|
|
@@ -43,7 +43,8 @@ import type {
|
|
|
43
43
|
ProtocolPrimitiveMap,
|
|
44
44
|
} from "@effectstream/config";
|
|
45
45
|
import type { StateUpdateStream } from "@effectstream/coroutine";
|
|
46
|
-
import {
|
|
46
|
+
import { Primitive } from "../../Primitive.ts";
|
|
47
|
+
import type { JsonObject } from "../../types.ts";
|
|
47
48
|
import {
|
|
48
49
|
type AddressAndType,
|
|
49
50
|
AddressType,
|
|
@@ -3,6 +3,21 @@ import { Type } from "@sinclair/typebox";
|
|
|
3
3
|
export const transferGrammar = [
|
|
4
4
|
["txId", Type.String()],
|
|
5
5
|
["metadata", Type.String()],
|
|
6
|
-
[
|
|
6
|
+
[
|
|
7
|
+
"inputCredentials",
|
|
8
|
+
Type.String({
|
|
9
|
+
deprecated: true,
|
|
10
|
+
description:
|
|
11
|
+
"Deprecated: JSON array of raw verification keys. Use signerKeyHashes.",
|
|
12
|
+
}),
|
|
13
|
+
],
|
|
7
14
|
["outputs", Type.String()],
|
|
15
|
+
// The default lets state machines parse historical four-field tuples.
|
|
16
|
+
[
|
|
17
|
+
"signerKeyHashes",
|
|
18
|
+
Type.String({
|
|
19
|
+
default: "[]",
|
|
20
|
+
description: "JSON array of Cardano verification key hashes.",
|
|
21
|
+
}),
|
|
22
|
+
],
|
|
8
23
|
] as const;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { parseRawStmInput, toKeyedJsonGrammar } from "@effectstream/concise";
|
|
3
|
+
import { CardanoTransferPrimitive } from "../mod.ts";
|
|
4
|
+
import { PrimitiveRegistry } from "../../PrimitiveRegistry.ts";
|
|
5
|
+
import { transferGrammar } from "./transfer-grammar.ts";
|
|
6
|
+
|
|
7
|
+
const VERIFICATION_KEY = Uint8Array.from({ length: 32 }, (_, index) => index);
|
|
8
|
+
const VERIFICATION_KEY_HEX =
|
|
9
|
+
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
|
|
10
|
+
const CREDENTIAL_HASH =
|
|
11
|
+
"491112dd01155c07dab485f71b572e0cae759e2cd38b1c0e97554297";
|
|
12
|
+
|
|
13
|
+
function makePrimitive(stateMachinePrefix?: string) {
|
|
14
|
+
PrimitiveRegistry.primitives = {};
|
|
15
|
+
return new CardanoTransferPrimitive({
|
|
16
|
+
instanceName: "CardanoTransfer",
|
|
17
|
+
startBlockHeight: 0,
|
|
18
|
+
stateMachinePrefix,
|
|
19
|
+
predicate: { match: { cardano: {} } },
|
|
20
|
+
} as any);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function payloadFor(
|
|
24
|
+
vkeys: Uint8Array[] | undefined,
|
|
25
|
+
stateMachinePrefix?: string,
|
|
26
|
+
) {
|
|
27
|
+
const witnesses =
|
|
28
|
+
vkeys === undefined
|
|
29
|
+
? undefined
|
|
30
|
+
: { vkeywitness: vkeys.map((vkey) => ({ vkey })) };
|
|
31
|
+
const tx = {
|
|
32
|
+
hash: new Uint8Array(32),
|
|
33
|
+
outputs: [],
|
|
34
|
+
witnesses,
|
|
35
|
+
};
|
|
36
|
+
const iterator = makePrimitive(stateMachinePrefix).getPayload(
|
|
37
|
+
0 as any,
|
|
38
|
+
{
|
|
39
|
+
output: { payload: { tx } },
|
|
40
|
+
} as any,
|
|
41
|
+
);
|
|
42
|
+
const result = iterator.next().value as any;
|
|
43
|
+
return result.data[0];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test("keeps deprecated inputCredentials as raw verification keys", () => {
|
|
47
|
+
const { accountingPayload } = payloadFor([VERIFICATION_KEY]);
|
|
48
|
+
|
|
49
|
+
expect(accountingPayload.inputCredentials).toEqual([VERIFICATION_KEY_HEX]);
|
|
50
|
+
expect(accountingPayload.inputCredentials[0]).toMatch(/^[0-9a-f]{64}$/);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("emits Cardano credential hashes as signerKeyHashes", () => {
|
|
54
|
+
const { accountingPayload } = payloadFor([VERIFICATION_KEY]);
|
|
55
|
+
|
|
56
|
+
expect(accountingPayload.signerKeyHashes).toEqual([CREDENTIAL_HASH]);
|
|
57
|
+
expect(accountingPayload.signerKeyHashes[0]).toMatch(/^[0-9a-f]{56}$/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("deduplicates legacy keys and signer hashes in first-seen order", () => {
|
|
61
|
+
const secondVkey = new Uint8Array(32).fill(0xff);
|
|
62
|
+
const { accountingPayload } = payloadFor([
|
|
63
|
+
VERIFICATION_KEY,
|
|
64
|
+
secondVkey,
|
|
65
|
+
VERIFICATION_KEY,
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
expect(accountingPayload.inputCredentials).toHaveLength(2);
|
|
69
|
+
expect(accountingPayload.inputCredentials[0]).toBe(VERIFICATION_KEY_HEX);
|
|
70
|
+
expect(accountingPayload.signerKeyHashes).toHaveLength(2);
|
|
71
|
+
expect(accountingPayload.signerKeyHashes[0]).toBe(CREDENTIAL_HASH);
|
|
72
|
+
expect(accountingPayload.signerKeyHashes[1]).toMatch(/^[0-9a-f]{56}$/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("emits empty legacy and signer lists when witnesses are absent", () => {
|
|
76
|
+
const { accountingPayload } = payloadFor(undefined);
|
|
77
|
+
|
|
78
|
+
expect(accountingPayload.inputCredentials).toEqual([]);
|
|
79
|
+
expect(accountingPayload.signerKeyHashes).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("appends signerKeyHashes without shifting legacy STM fields", () => {
|
|
83
|
+
const { stateMachinePayload } = payloadFor(
|
|
84
|
+
[VERIFICATION_KEY],
|
|
85
|
+
"cardano-transfer",
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(stateMachinePayload.slice(3, 5)).toEqual([
|
|
89
|
+
JSON.stringify([VERIFICATION_KEY_HEX]),
|
|
90
|
+
"[]",
|
|
91
|
+
]);
|
|
92
|
+
expect(JSON.parse(stateMachinePayload[5])).toEqual([CREDENTIAL_HASH]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("defaults signerKeyHashes when parsing historical STM tuples", () => {
|
|
96
|
+
const grammar = { "cardano-transfer": transferGrammar } as const;
|
|
97
|
+
const parsed = parseRawStmInput(
|
|
98
|
+
["cardano-transfer", "tx-id", "", "[]", "[]"],
|
|
99
|
+
grammar,
|
|
100
|
+
toKeyedJsonGrammar(grammar),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(parsed.data.inputCredentials).toBe("[]");
|
|
104
|
+
expect(parsed.data.outputs).toBe("[]");
|
|
105
|
+
expect(parsed.data.signerKeyHashes).toBe("[]");
|
|
106
|
+
});
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* Extraction: for each matched transaction, `getPayload()` maps `tx.outputs` into
|
|
11
11
|
* structured objects with `{index, address, coin, assets[]}`. Coin values are extracted
|
|
12
12
|
* from the protobuf BigInt (either `int` for small values or `bigUInt` for large).
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* `inputCredentials` retains the raw `tx.witnesses.vkeywitness[].vkey` values for
|
|
14
|
+
* backward compatibility, but is deprecated because those values are not credentials.
|
|
15
|
+
* `signerKeyHashes` hashes each verification key with BLAKE2b-224, yielding the
|
|
16
|
+
* Cardano key hashes that identify who authorized the transaction.
|
|
15
17
|
* Transaction metadata is extracted from `tx.auxiliary.metadata`.
|
|
16
18
|
*
|
|
17
19
|
* Predicate: user-provided (typically `has_address` to watch specific addresses).
|
|
@@ -29,7 +31,8 @@ import type {
|
|
|
29
31
|
UtxorpcTxPredicate,
|
|
30
32
|
} from "@effectstream/config";
|
|
31
33
|
import type { StateUpdateStream } from "@effectstream/coroutine";
|
|
32
|
-
import {
|
|
34
|
+
import { Primitive } from "../../Primitive.ts";
|
|
35
|
+
import type { JsonObject } from "../../types.ts";
|
|
33
36
|
import {
|
|
34
37
|
type AddressAndType,
|
|
35
38
|
AddressType,
|
|
@@ -42,6 +45,7 @@ import {
|
|
|
42
45
|
addressToHex,
|
|
43
46
|
assetQuantityToString,
|
|
44
47
|
metadataToJson,
|
|
48
|
+
verificationKeyToCredentialHex,
|
|
45
49
|
} from "../cardano-utils/cardano-helpers.ts";
|
|
46
50
|
|
|
47
51
|
export class CardanoTransferPrimitive extends Primitive<
|
|
@@ -111,10 +115,18 @@ export class CardanoTransferPrimitive extends Primitive<
|
|
|
111
115
|
});
|
|
112
116
|
|
|
113
117
|
const inputCredentials: string[] = [];
|
|
118
|
+
const signerKeyHashes: string[] = [];
|
|
114
119
|
if (tx.witnesses) {
|
|
115
120
|
for (const w of tx.witnesses.vkeywitness) {
|
|
116
|
-
const
|
|
117
|
-
if (!inputCredentials.includes(
|
|
121
|
+
const verificationKey = uint8ArrayToHexString(w.vkey);
|
|
122
|
+
if (!inputCredentials.includes(verificationKey)) {
|
|
123
|
+
inputCredentials.push(verificationKey);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const signerKeyHash = verificationKeyToCredentialHex(w.vkey);
|
|
127
|
+
if (!signerKeyHashes.includes(signerKeyHash)) {
|
|
128
|
+
signerKeyHashes.push(signerKeyHash);
|
|
129
|
+
}
|
|
118
130
|
}
|
|
119
131
|
}
|
|
120
132
|
|
|
@@ -125,6 +137,7 @@ export class CardanoTransferPrimitive extends Primitive<
|
|
|
125
137
|
metadata: metadata ? JSON.stringify(metadata) : null,
|
|
126
138
|
inputCredentials,
|
|
127
139
|
outputs,
|
|
140
|
+
signerKeyHashes,
|
|
128
141
|
};
|
|
129
142
|
|
|
130
143
|
const stateMachinePayload: any = this.stateMachinePrefix
|
|
@@ -133,6 +146,7 @@ export class CardanoTransferPrimitive extends Primitive<
|
|
|
133
146
|
metadata: metadata ? JSON.stringify(metadata) : "",
|
|
134
147
|
inputCredentials: JSON.stringify(inputCredentials),
|
|
135
148
|
outputs: JSON.stringify(outputs),
|
|
149
|
+
signerKeyHashes: JSON.stringify(signerKeyHashes),
|
|
136
150
|
})
|
|
137
151
|
: null;
|
|
138
152
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { blake2b } from "@noble/hashes/blake2.js";
|
|
1
2
|
import { uint8ArrayToHexString } from "@effectstream/utils";
|
|
2
3
|
import type { cardano } from "@utxorpc/spec";
|
|
3
4
|
|
|
@@ -31,6 +32,10 @@ export function addressToHex(address: Uint8Array): string {
|
|
|
31
32
|
return uint8ArrayToHexString(address);
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
export function verificationKeyToCredentialHex(vkey: Uint8Array): string {
|
|
36
|
+
return uint8ArrayToHexString(blake2b(vkey, { dkLen: 28 }));
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
export function metadataToJson(metadata: cardano.Metadata[]): Record<string, unknown> | null {
|
|
35
40
|
if (!metadata || metadata.length === 0) return null;
|
|
36
41
|
const result: Record<string, unknown> = {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Primitive } from "../../Primitive.ts";
|
|
2
|
+
import type { JsonObject } from "../../types.ts";
|
|
2
3
|
import type { StaticDecode } from "@sinclair/typebox";
|
|
3
4
|
import { type CommandTuple, generateRawStmInput } from "@effectstream/concise";
|
|
4
5
|
import type {
|
|
@@ -38,12 +38,13 @@ import {
|
|
|
38
38
|
account_createAccount,
|
|
39
39
|
account_linkAddress,
|
|
40
40
|
account_unlinkAddress,
|
|
41
|
-
} from "
|
|
41
|
+
} from "../../../delegate-wallet.ts";
|
|
42
42
|
import { BuiltinGrammarPrefix } from "@effectstream/concise";
|
|
43
43
|
|
|
44
44
|
import { effectstreamL2 } from "./effectstream-l2-abi.ts";
|
|
45
45
|
import { type StaticDecode, type TSchema, Type } from "@sinclair/typebox";
|
|
46
|
-
import {
|
|
46
|
+
import { Primitive } from "../../Primitive.ts";
|
|
47
|
+
import type { JsonObject } from "../../types.ts";
|
|
47
48
|
import { Value } from "@sinclair/typebox/value";
|
|
48
49
|
import type { CommandTuple } from "@effectstream/concise";
|
|
49
50
|
import { PrimitiveTypeEVMEffectstreamL2 } from "../builtin.ts";
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
type EffectstreamBlockNumber,
|
|
14
14
|
TypeboxHelpers,
|
|
15
15
|
} from "@effectstream/utils";
|
|
16
|
-
import {
|
|
16
|
+
import { Primitive } from "../../Primitive.ts";
|
|
17
|
+
import type { JsonObject } from "../../types.ts";
|
|
17
18
|
import {
|
|
18
19
|
type CommandTuple,
|
|
19
20
|
generateRawStmInput,
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
type EffectstreamBlockNumber,
|
|
13
13
|
TypeboxHelpers,
|
|
14
14
|
} from "@effectstream/utils";
|
|
15
|
-
import {
|
|
15
|
+
import { Primitive } from "../../Primitive.ts";
|
|
16
|
+
import type { JsonObject } from "../../types.ts";
|
|
16
17
|
import { Value } from "@sinclair/typebox/value";
|
|
17
18
|
import {
|
|
18
19
|
type CommandTuple,
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
type EffectstreamBlockNumber,
|
|
14
14
|
TypeboxHelpers,
|
|
15
15
|
} from "@effectstream/utils";
|
|
16
|
-
import {
|
|
16
|
+
import { Primitive } from "../../Primitive.ts";
|
|
17
|
+
import type { JsonObject } from "../../types.ts";
|
|
17
18
|
import {
|
|
18
19
|
type CommandTuple,
|
|
19
20
|
generateRawStmInput,
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
type EffectstreamBlockNumber,
|
|
13
13
|
TypeboxHelpers,
|
|
14
14
|
} from "@effectstream/utils";
|
|
15
|
-
import {
|
|
15
|
+
import { Primitive } from "../../Primitive.ts";
|
|
16
|
+
import type { JsonObject } from "../../types.ts";
|
|
16
17
|
import { Value } from "@sinclair/typebox/value";
|
|
17
18
|
import {
|
|
18
19
|
type CommandTuple,
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { StateValue } from "@effectstream/config";
|
|
3
|
+
import { MidnightGenericPrimitive } from "./midnight-genetic.ts";
|
|
4
|
+
|
|
5
|
+
function cell(...bytes: number[]): StateValue {
|
|
6
|
+
return {
|
|
7
|
+
type: () => "cell",
|
|
8
|
+
asCell: () => ({ value: [Uint8Array.from(bytes)] }),
|
|
9
|
+
} as unknown as StateValue;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function array(...values: StateValue[]): StateValue {
|
|
13
|
+
return {
|
|
14
|
+
type: () => "array",
|
|
15
|
+
asArray: () => values,
|
|
16
|
+
} as unknown as StateValue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function nullValue(): StateValue {
|
|
20
|
+
return { type: () => "null" } as unknown as StateValue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function map(entries: [number[], StateValue][]): StateValue {
|
|
24
|
+
const value = new Map(
|
|
25
|
+
entries.map(([key, item]) => [
|
|
26
|
+
{ value: [Uint8Array.from(key)] },
|
|
27
|
+
item,
|
|
28
|
+
]),
|
|
29
|
+
);
|
|
30
|
+
return {
|
|
31
|
+
type: () => "map",
|
|
32
|
+
asMap: () => value,
|
|
33
|
+
} as unknown as StateValue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function primitive(name: string, ledgerSchema?: Record<string, any>) {
|
|
37
|
+
return new MidnightGenericPrimitive({
|
|
38
|
+
instanceName: name,
|
|
39
|
+
startBlockHeight: 1,
|
|
40
|
+
contractAddress: "0".repeat(64) as any,
|
|
41
|
+
stateMachinePrefix: name,
|
|
42
|
+
ledgerSchema,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("MidnightGenericPrimitive ledger schema", () => {
|
|
47
|
+
test("decodes simple, optional, and map fields in declaration order", () => {
|
|
48
|
+
const decoder = primitive("schema-values", {
|
|
49
|
+
round: "uint128",
|
|
50
|
+
bytes: "bytes",
|
|
51
|
+
enabled: "boolean",
|
|
52
|
+
missing: { type: "option", value: "uint64" },
|
|
53
|
+
entries: { type: "map", value: "uint16" },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(decoder.parseAdditionalLedgerFields(array(
|
|
57
|
+
cell(0x01, 0x02),
|
|
58
|
+
cell(0xab, 0xcd),
|
|
59
|
+
cell(0x01),
|
|
60
|
+
nullValue(),
|
|
61
|
+
map([[[0x0a], cell(0x03)]]),
|
|
62
|
+
))).toEqual({
|
|
63
|
+
round: "513",
|
|
64
|
+
bytes: "0xabcd",
|
|
65
|
+
enabled: true,
|
|
66
|
+
missing: null,
|
|
67
|
+
entries: { "0x0a": "3" },
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("requires either generated contract code or a non-empty schema", () => {
|
|
72
|
+
expect(() => primitive("missing-decoder")).toThrow(/requires either/);
|
|
73
|
+
expect(() => primitive("empty-schema", {})).toThrow(/empty ledger schema/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("reports declaration-order and field-type mismatches", () => {
|
|
77
|
+
const decoder = primitive("schema-order", {
|
|
78
|
+
entries: { type: "map", value: "uint16" },
|
|
79
|
+
round: "uint128",
|
|
80
|
+
});
|
|
81
|
+
expect(() => decoder.parseAdditionalLedgerFields(array(
|
|
82
|
+
cell(0x01),
|
|
83
|
+
map([]),
|
|
84
|
+
))).toThrow(/field "entries" expects a map/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("rejects an incompatible ledger root", () => {
|
|
88
|
+
const decoder = primitive("schema-root", { round: "uint128" });
|
|
89
|
+
expect(() => decoder.parseAdditionalLedgerFields(cell(0x01))).toThrow(
|
|
90
|
+
/Ledger root must be an array/,
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Primitive } from "
|
|
1
|
+
import { Primitive } from "../../Primitive.ts";
|
|
2
2
|
import {
|
|
3
3
|
type AddressAndType,
|
|
4
4
|
AddressType,
|
|
@@ -33,7 +33,7 @@ export class MidnightGenericPrimitive extends Primitive<
|
|
|
33
33
|
readonly internalTypeName = PrimitiveTypeMidnightGeneric;
|
|
34
34
|
override readonly grammar = midnightGenericGrammar;
|
|
35
35
|
readonly contractAddress: string;
|
|
36
|
-
readonly contract
|
|
36
|
+
readonly contract?: {
|
|
37
37
|
ledger: (data: StateValue) => any;
|
|
38
38
|
};
|
|
39
39
|
readonly ledgerSchema?: LedgerSchema;
|
|
@@ -53,7 +53,7 @@ export class MidnightGenericPrimitive extends Primitive<
|
|
|
53
53
|
startBlockHeight: number;
|
|
54
54
|
contractAddress: MidnightAddress;
|
|
55
55
|
stateMachinePrefix: string;
|
|
56
|
-
contract
|
|
56
|
+
contract?: {
|
|
57
57
|
ledger: (data: StateValue) => any;
|
|
58
58
|
};
|
|
59
59
|
ledgerSchema?: LedgerSchema;
|
|
@@ -61,6 +61,16 @@ export class MidnightGenericPrimitive extends Primitive<
|
|
|
61
61
|
genesisHash?: string;
|
|
62
62
|
}) {
|
|
63
63
|
super(config);
|
|
64
|
+
if (!config.contract && !config.ledgerSchema) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Midnight primitive "${config.instanceName}" requires either a generated contract decoder or a ledger schema.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (config.ledgerSchema && Object.keys(config.ledgerSchema).length === 0) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Midnight primitive "${config.instanceName}" received an empty ledger schema.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
64
74
|
this.contractAddress = config.contractAddress;
|
|
65
75
|
this.contract = config.contract;
|
|
66
76
|
this.ledgerSchema = config.ledgerSchema;
|
|
@@ -76,13 +86,24 @@ export class MidnightGenericPrimitive extends Primitive<
|
|
|
76
86
|
parseAdditionalLedgerFields(stateValue: StateValue): Record<string, any> {
|
|
77
87
|
if (!this.ledgerSchema) return {};
|
|
78
88
|
|
|
79
|
-
const stateArray: StateValue[] = stateValue.asArray?.()
|
|
89
|
+
const stateArray: StateValue[] | undefined = stateValue.asArray?.();
|
|
80
90
|
const result: Record<string, any> = {};
|
|
81
91
|
const schemaEntries = Object.entries(this.ledgerSchema);
|
|
82
92
|
|
|
83
|
-
|
|
93
|
+
if (!stateArray) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Ledger root must be an array; received ${stateValue.type()}.`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (stateArray.length < schemaEntries.length) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Ledger schema declares ${schemaEntries.length} field(s), but the ledger contains only ${stateArray.length}.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < schemaEntries.length; i++) {
|
|
84
105
|
const [fieldName, fieldType] = schemaEntries[i];
|
|
85
|
-
result[fieldName] = parseLedgerField(stateArray[i], fieldType);
|
|
106
|
+
result[fieldName] = parseLedgerField(stateArray[i], fieldType, fieldName);
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
return result;
|
|
@@ -156,6 +177,7 @@ export class MidnightGenericPrimitive extends Primitive<
|
|
|
156
177
|
// TODO This should be optional
|
|
157
178
|
scheduledPrefix: this.stateMachinePrefix ?? "",
|
|
158
179
|
contract: this.contract,
|
|
180
|
+
ledgerSchema: this.ledgerSchema,
|
|
159
181
|
parseAdditionalLedgerFields: this.ledgerSchema
|
|
160
182
|
? this.parseAdditionalLedgerFields.bind(this)
|
|
161
183
|
: undefined,
|
|
@@ -193,30 +215,44 @@ function parseUintCellLittleEndian(chunks: Uint8Array[]): string {
|
|
|
193
215
|
return result.toString();
|
|
194
216
|
}
|
|
195
217
|
|
|
196
|
-
function parseLedgerField(
|
|
218
|
+
function parseLedgerField(
|
|
219
|
+
sv: StateValue,
|
|
220
|
+
type: LedgerFieldType,
|
|
221
|
+
path: string,
|
|
222
|
+
): any {
|
|
197
223
|
if (typeof type === "object" && type.type === "option") {
|
|
198
224
|
if (sv.type() === "null") return null;
|
|
199
|
-
return parseLedgerField(sv, type.value);
|
|
225
|
+
return parseLedgerField(sv, type.value, path);
|
|
200
226
|
}
|
|
201
227
|
|
|
202
228
|
if (typeof type === "object" && type.type === "map") {
|
|
203
|
-
const map = sv.asMap();
|
|
204
|
-
if (!map)
|
|
229
|
+
const map = sv.asMap?.();
|
|
230
|
+
if (!map) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Ledger field "${path}" expects a map; received ${sv.type()}.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
205
235
|
|
|
206
236
|
const result: Record<string, any> = {};
|
|
207
237
|
for (const key of map.keys()) {
|
|
208
238
|
const val = map.get(key);
|
|
209
239
|
if (val !== undefined) {
|
|
210
|
-
|
|
240
|
+
const mapKey = alignedValueToHex(key as { value: Uint8Array[] });
|
|
241
|
+
result[mapKey] = parseLedgerField(
|
|
211
242
|
val,
|
|
212
243
|
type.value,
|
|
244
|
+
`${path}[${mapKey}]`,
|
|
213
245
|
);
|
|
214
246
|
}
|
|
215
247
|
}
|
|
216
248
|
return result;
|
|
217
249
|
}
|
|
218
250
|
|
|
219
|
-
if (sv.type() !== "cell")
|
|
251
|
+
if (sv.type() !== "cell") {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Ledger field "${path}" expects ${String(type)}; received ${sv.type()}.`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
220
256
|
const cell = sv.asCell();
|
|
221
257
|
|
|
222
258
|
if (
|
|
@@ -239,7 +275,7 @@ function parseLedgerField(sv: StateValue, type: LedgerFieldType): any {
|
|
|
239
275
|
);
|
|
240
276
|
}
|
|
241
277
|
|
|
242
|
-
|
|
278
|
+
throw new Error(`Unsupported ledger type for field "${path}": ${String(type)}.`);
|
|
243
279
|
}
|
|
244
280
|
|
|
245
281
|
function isCompactMap(data: object): data is Iterable<[unknown, unknown]> {
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -9,7 +9,8 @@ import {
|
|
|
9
9
|
AddressType,
|
|
10
10
|
type EffectstreamBlockNumber,
|
|
11
11
|
} from "@effectstream/utils";
|
|
12
|
-
import {
|
|
12
|
+
import { Primitive } from "../../Primitive.ts";
|
|
13
|
+
import type { JsonObject } from "../../types.ts";
|
|
13
14
|
import {
|
|
14
15
|
type CommandTuple,
|
|
15
16
|
generateRawStmInput,
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
} from "@effectstream/concise";
|
|
7
7
|
import type { ConfigSyncProtocolType, FlattenSyncProtocolIOFor, ProtocolPrimitiveMap, UtxorpcTxPredicate } from '@effectstream/config';
|
|
8
8
|
import type { StateUpdateStream } from "@effectstream/coroutine";
|
|
9
|
-
import {
|
|
9
|
+
import { Primitive } from "../../Primitive.ts";
|
|
10
|
+
import type { JsonObject } from "../../types.ts";
|
|
10
11
|
import { type AddressAndType, AddressType, type EffectstreamBlockNumber, uint8ArrayToHexString } from '@effectstream/utils';
|
|
11
12
|
import { utxorpcGenericGrammar } from './utxorpc-generic-grammar.ts';
|
|
12
13
|
import { PrimitiveTypeUtxorpcGeneric } from '../builtin.ts';
|