@meshsdk/core-cst 1.6.0-alpha.11
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/README.md +5 -0
- package/jest.config.js +5 -0
- package/package.json +41 -0
- package/src/cip8/index.ts +24 -0
- package/src/index.ts +16 -0
- package/src/resolvers/index.ts +141 -0
- package/src/serializer/index.ts +678 -0
- package/src/stricahq/index.ts +24 -0
- package/src/types/cardano-sdk.ts +243 -0
- package/src/types/index.ts +2 -0
- package/src/types/signer.ts +7 -0
- package/src/utils/builder.ts +130 -0
- package/src/utils/converter.ts +371 -0
- package/src/utils/deserializer.ts +76 -0
- package/src/utils/encoding.ts +12 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/script-data-hash.ts +87 -0
- package/src/utils/value.ts +61 -0
- package/test/resolvers.test.ts +140 -0
- package/test/utils/converter.test.ts +18 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
import { Serialization, TxCBOR } from "@cardano-sdk/core";
|
|
2
|
+
import { HexBlob } from "@cardano-sdk/util";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
BuilderData,
|
|
6
|
+
NativeScript as CommonNativeScript,
|
|
7
|
+
Data,
|
|
8
|
+
DEFAULT_V1_COST_MODEL_LIST,
|
|
9
|
+
DEFAULT_V2_COST_MODEL_LIST,
|
|
10
|
+
DeserializedAddress,
|
|
11
|
+
DeserializedScript,
|
|
12
|
+
IDeserializer,
|
|
13
|
+
IMeshTxSerializer,
|
|
14
|
+
IResolver,
|
|
15
|
+
MeshTxBuilderBody,
|
|
16
|
+
PlutusScript,
|
|
17
|
+
Protocol,
|
|
18
|
+
PubKeyTxIn,
|
|
19
|
+
RequiredWith,
|
|
20
|
+
ScriptTxIn,
|
|
21
|
+
SimpleScriptTxIn,
|
|
22
|
+
TxIn,
|
|
23
|
+
} from "@meshsdk/common";
|
|
24
|
+
|
|
25
|
+
import { PrivateKey } from "../stricahq";
|
|
26
|
+
import {
|
|
27
|
+
Address,
|
|
28
|
+
CredentialType,
|
|
29
|
+
Ed25519PublicKeyHex,
|
|
30
|
+
Ed25519SignatureHex,
|
|
31
|
+
ExUnits,
|
|
32
|
+
NativeScript,
|
|
33
|
+
PlutusData,
|
|
34
|
+
PlutusLanguageVersion,
|
|
35
|
+
PlutusV1Script,
|
|
36
|
+
PlutusV2Script,
|
|
37
|
+
PlutusV3Script,
|
|
38
|
+
Redeemer,
|
|
39
|
+
Redeemers,
|
|
40
|
+
RedeemerTag,
|
|
41
|
+
Script,
|
|
42
|
+
Transaction,
|
|
43
|
+
TransactionBody,
|
|
44
|
+
TransactionId,
|
|
45
|
+
TransactionInput,
|
|
46
|
+
TransactionOutput,
|
|
47
|
+
TransactionWitnessSet,
|
|
48
|
+
Value,
|
|
49
|
+
VkeyWitness,
|
|
50
|
+
} from "../types";
|
|
51
|
+
import { toAddress, toNativeScript, toPlutusData, toValue } from "../utils";
|
|
52
|
+
import { hashScriptData } from "../utils/script-data-hash";
|
|
53
|
+
import { empty, mergeValue, negatives, subValue } from "../utils/value";
|
|
54
|
+
|
|
55
|
+
export class CardanoSDKSerializer implements IMeshTxSerializer {
|
|
56
|
+
private txBody: TransactionBody;
|
|
57
|
+
private txWitnessSet: TransactionWitnessSet;
|
|
58
|
+
|
|
59
|
+
private utxoContext: Map<TransactionInput, TransactionOutput> = new Map<
|
|
60
|
+
TransactionInput,
|
|
61
|
+
TransactionOutput
|
|
62
|
+
>();
|
|
63
|
+
|
|
64
|
+
private redeemerContext: Map<TransactionInput, Redeemer> = new Map<
|
|
65
|
+
TransactionInput,
|
|
66
|
+
Redeemer
|
|
67
|
+
>();
|
|
68
|
+
|
|
69
|
+
private scriptsProvided: Set<Script> = new Set<Script>();
|
|
70
|
+
private datumsProvided: Set<PlutusData> = new Set<PlutusData>();
|
|
71
|
+
private usedLanguages: Record<PlutusLanguageVersion, boolean> = {
|
|
72
|
+
[0]: false,
|
|
73
|
+
[1]: false,
|
|
74
|
+
[2]: false,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
constructor() {
|
|
78
|
+
this.txBody = new TransactionBody(
|
|
79
|
+
Serialization.CborSet.fromCore([], TransactionInput.fromCore),
|
|
80
|
+
[],
|
|
81
|
+
BigInt(0),
|
|
82
|
+
undefined,
|
|
83
|
+
);
|
|
84
|
+
this.txWitnessSet = new TransactionWitnessSet();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
serializeAddress(address: DeserializedAddress, networkId?: 0 | 1): string {
|
|
88
|
+
throw new Error("Method not implemented.");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
serializeData(data: BuilderData): string {
|
|
92
|
+
throw new Error("Method not implemented.");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
deserializer: IDeserializer = {
|
|
96
|
+
key: {
|
|
97
|
+
deserializeAddress: function (bech32: string): DeserializedAddress {
|
|
98
|
+
throw new Error("Function not implemented.");
|
|
99
|
+
|
|
100
|
+
// return {
|
|
101
|
+
// pubKeyHash: this.resolvePaymentKeyHash(address),
|
|
102
|
+
// scriptHash: this.resolvePlutusScriptHash(address),
|
|
103
|
+
// stakeCredentialHash: this.resolveStakeKeyHash(address),
|
|
104
|
+
// stakeScriptCredentialHash: this.resolveStakeScriptHash(address),
|
|
105
|
+
// };
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
script: {
|
|
109
|
+
deserializeNativeScript: function (
|
|
110
|
+
script: CommonNativeScript,
|
|
111
|
+
): DeserializedScript {
|
|
112
|
+
throw new Error("Function not implemented.");
|
|
113
|
+
},
|
|
114
|
+
deserializePlutusScript: function (
|
|
115
|
+
script: PlutusScript,
|
|
116
|
+
): DeserializedScript {
|
|
117
|
+
throw new Error("Function not implemented.");
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
resolver: IResolver = {
|
|
123
|
+
keys: {
|
|
124
|
+
// resolvePaymentKeyHash: function (bech32: string): string {
|
|
125
|
+
// const cardanoAddress = toAddress(bech32);
|
|
126
|
+
// return cardanoAddress.asEnterprise()?.getPaymentCredential().type ===
|
|
127
|
+
// CredentialType.KeyHash
|
|
128
|
+
// ? cardanoAddress.asEnterprise()!.getPaymentCredential().hash
|
|
129
|
+
// : "";
|
|
130
|
+
// },
|
|
131
|
+
// resolvePlutusScriptHash: function (bech32: string): string {
|
|
132
|
+
// const cardanoAddress = toAddress(bech32);
|
|
133
|
+
// return cardanoAddress.asEnterprise()?.getPaymentCredential().type ===
|
|
134
|
+
// CredentialType.ScriptHash
|
|
135
|
+
// ? cardanoAddress.asEnterprise()!.getPaymentCredential().hash
|
|
136
|
+
// : "";
|
|
137
|
+
// },
|
|
138
|
+
resolveStakeKeyHash: function (bech32: string): string {
|
|
139
|
+
const cardanoAddress = toAddress(bech32);
|
|
140
|
+
return cardanoAddress.asReward()?.getPaymentCredential().type ===
|
|
141
|
+
CredentialType.KeyHash
|
|
142
|
+
? cardanoAddress.asReward()!.getPaymentCredential().hash
|
|
143
|
+
: "";
|
|
144
|
+
},
|
|
145
|
+
// resolveStakeScriptHash(bech32: string): string {
|
|
146
|
+
// const cardanoAddress = toAddress(bech32);
|
|
147
|
+
// return cardanoAddress.asReward()?.getPaymentCredential().type ===
|
|
148
|
+
// CredentialType.ScriptHash
|
|
149
|
+
// ? cardanoAddress.asReward()!.getPaymentCredential().hash
|
|
150
|
+
// : "";
|
|
151
|
+
// },
|
|
152
|
+
resolvePrivateKey: function (words: string[]): string {
|
|
153
|
+
throw new Error("Function not implemented.");
|
|
154
|
+
},
|
|
155
|
+
resolveRewardAddress: function (bech32: string): string {
|
|
156
|
+
throw new Error("Function not implemented.");
|
|
157
|
+
},
|
|
158
|
+
resolveEd25519KeyHash: function (bech32: string): string {
|
|
159
|
+
throw new Error("Function not implemented.");
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
tx: {
|
|
163
|
+
resolveTxHash: function (txHex: string): string {
|
|
164
|
+
return Transaction.fromCbor(TxCBOR(txHex)).getId();
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
data: {
|
|
168
|
+
resolveDataHash: function (data: Data): string {
|
|
169
|
+
throw new Error("Function not implemented.");
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
script: {
|
|
173
|
+
// resolveNativeScript: function (script: CommonNativeScript): string {
|
|
174
|
+
// return toNativeScript(script).toCbor();
|
|
175
|
+
// },
|
|
176
|
+
resolveScriptRef: function (
|
|
177
|
+
script: CommonNativeScript | PlutusScript,
|
|
178
|
+
): string {
|
|
179
|
+
throw new Error("Function not implemented.");
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
pool: {
|
|
183
|
+
resolvePoolId: function (hash: string): string {
|
|
184
|
+
throw new Error("Function not implemented.");
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
serializeTxBody = (
|
|
190
|
+
txBuilderBody: MeshTxBuilderBody,
|
|
191
|
+
protocolParams: Protocol,
|
|
192
|
+
): string => {
|
|
193
|
+
const {
|
|
194
|
+
inputs,
|
|
195
|
+
outputs,
|
|
196
|
+
extraInputs,
|
|
197
|
+
selectionThreshold,
|
|
198
|
+
collaterals,
|
|
199
|
+
referenceInputs,
|
|
200
|
+
mints,
|
|
201
|
+
changeAddress,
|
|
202
|
+
certificates,
|
|
203
|
+
validityRange,
|
|
204
|
+
requiredSignatures,
|
|
205
|
+
metadata,
|
|
206
|
+
} = txBuilderBody;
|
|
207
|
+
|
|
208
|
+
mints.sort((a, b) => a.policyId.localeCompare(b.policyId));
|
|
209
|
+
inputs.sort((a, b) => {
|
|
210
|
+
if (a.txIn.txHash === b.txIn.txHash) {
|
|
211
|
+
return a.txIn.txIndex - b.txIn.txIndex;
|
|
212
|
+
} else {
|
|
213
|
+
return a.txIn.txHash.localeCompare(b.txIn.txHash);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
this.addAllInputs(inputs);
|
|
218
|
+
this.addAllCollateralInputs(collaterals);
|
|
219
|
+
this.buildWitnessSet();
|
|
220
|
+
this.balanceTx(changeAddress, requiredSignatures.length, protocolParams);
|
|
221
|
+
return new Transaction(this.txBody, this.txWitnessSet).toCbor();
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
addSigningKeys = (txHex: string, signingKeys: string[]): string => {
|
|
225
|
+
let cardanoTx = Transaction.fromCbor(TxCBOR(txHex));
|
|
226
|
+
let currentWitnessSet = cardanoTx.witnessSet();
|
|
227
|
+
let currentWitnessSetVkeys = currentWitnessSet.vkeys();
|
|
228
|
+
let currentWitnessSetVkeysValues: Serialization.VkeyWitness[] =
|
|
229
|
+
currentWitnessSetVkeys ? [...currentWitnessSetVkeys.values()] : [];
|
|
230
|
+
for (let i = 0; i < signingKeys.length; i++) {
|
|
231
|
+
let keyHex = signingKeys[i];
|
|
232
|
+
if (keyHex) {
|
|
233
|
+
if (keyHex.length === 68 && keyHex.substring(0, 4) === "5820") {
|
|
234
|
+
keyHex = keyHex.substring(4);
|
|
235
|
+
}
|
|
236
|
+
const cardanoSigner = new PrivateKey(Buffer.from(keyHex, "hex"), false);
|
|
237
|
+
const signature = cardanoSigner.sign(
|
|
238
|
+
Buffer.from(cardanoTx.getId(), "hex"),
|
|
239
|
+
);
|
|
240
|
+
currentWitnessSetVkeysValues.push(
|
|
241
|
+
new VkeyWitness(
|
|
242
|
+
Ed25519PublicKeyHex(
|
|
243
|
+
cardanoSigner.toPublicKey().toBytes().toString("hex"),
|
|
244
|
+
),
|
|
245
|
+
Ed25519SignatureHex(signature.toString("hex")),
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
currentWitnessSet.setVkeys(
|
|
251
|
+
Serialization.CborSet.fromCore(
|
|
252
|
+
currentWitnessSetVkeysValues.map((vkw) => vkw.toCore()),
|
|
253
|
+
VkeyWitness.fromCore,
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
cardanoTx.setWitnessSet(currentWitnessSet);
|
|
257
|
+
return cardanoTx.toCbor();
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
private addAllInputs = (inputs: TxIn[]) => {
|
|
261
|
+
for (let i = 0; i < inputs.length; i += 1) {
|
|
262
|
+
const currentTxIn = inputs[i];
|
|
263
|
+
if (!currentTxIn) continue;
|
|
264
|
+
switch (currentTxIn.type) {
|
|
265
|
+
case "PubKey":
|
|
266
|
+
this.addTxIn(currentTxIn as RequiredWith<PubKeyTxIn, "txIn">);
|
|
267
|
+
break;
|
|
268
|
+
case "Script":
|
|
269
|
+
this.addScriptTxIn(
|
|
270
|
+
currentTxIn as RequiredWith<ScriptTxIn, "txIn" | "scriptTxIn">,
|
|
271
|
+
);
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
private addTxIn = (currentTxIn: RequiredWith<PubKeyTxIn, "txIn">) => {
|
|
278
|
+
// First build Cardano tx in and add it to tx body
|
|
279
|
+
let cardanoTxIn = new TransactionInput(
|
|
280
|
+
TransactionId(currentTxIn.txIn.txHash),
|
|
281
|
+
BigInt(currentTxIn.txIn.txIndex),
|
|
282
|
+
);
|
|
283
|
+
const inputs = this.txBody.inputs();
|
|
284
|
+
const txInputsList: TransactionInput[] = [...inputs.values()];
|
|
285
|
+
if (
|
|
286
|
+
txInputsList.find((input) => {
|
|
287
|
+
input.index() == cardanoTxIn.index() &&
|
|
288
|
+
input.transactionId == cardanoTxIn.transactionId;
|
|
289
|
+
})
|
|
290
|
+
) {
|
|
291
|
+
throw new Error("Duplicate input added to tx body");
|
|
292
|
+
}
|
|
293
|
+
txInputsList.push(cardanoTxIn);
|
|
294
|
+
inputs.setValues(txInputsList);
|
|
295
|
+
// We save the output to a mapping so that we can calculate change
|
|
296
|
+
const cardanoTxOut = new TransactionOutput(
|
|
297
|
+
Address.fromBech32(currentTxIn.txIn.address),
|
|
298
|
+
toValue(currentTxIn.txIn.amount),
|
|
299
|
+
);
|
|
300
|
+
this.utxoContext.set(cardanoTxIn, cardanoTxOut);
|
|
301
|
+
this.txBody.setInputs(inputs);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
private addScriptTxIn = (
|
|
305
|
+
currentTxIn: RequiredWith<ScriptTxIn, "txIn" | "scriptTxIn">,
|
|
306
|
+
) => {
|
|
307
|
+
// we can add the input in first, and handle the script info after
|
|
308
|
+
this.addTxIn({
|
|
309
|
+
type: "PubKey",
|
|
310
|
+
txIn: currentTxIn.txIn,
|
|
311
|
+
});
|
|
312
|
+
if (!currentTxIn.scriptTxIn.scriptSource) {
|
|
313
|
+
throw new Error("A script input had no script source");
|
|
314
|
+
}
|
|
315
|
+
if (!currentTxIn.scriptTxIn.datumSource) {
|
|
316
|
+
throw new Error("A script input had no datum source");
|
|
317
|
+
}
|
|
318
|
+
if (!currentTxIn.scriptTxIn.redeemer) {
|
|
319
|
+
throw new Error("A script input had no redeemer");
|
|
320
|
+
}
|
|
321
|
+
// Handle script info based on whether it's inlined or provided
|
|
322
|
+
if (currentTxIn.scriptTxIn.scriptSource.type === "Provided") {
|
|
323
|
+
switch (currentTxIn.scriptTxIn.scriptSource.script.version) {
|
|
324
|
+
case "V1": {
|
|
325
|
+
this.scriptsProvided.add(
|
|
326
|
+
Script.newPlutusV1Script(
|
|
327
|
+
PlutusV1Script.fromCbor(
|
|
328
|
+
HexBlob(currentTxIn.scriptTxIn.scriptSource.script.code),
|
|
329
|
+
),
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
this.usedLanguages[PlutusLanguageVersion.V1] = true;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case "V2": {
|
|
336
|
+
this.scriptsProvided.add(
|
|
337
|
+
Script.newPlutusV2Script(
|
|
338
|
+
PlutusV2Script.fromCbor(
|
|
339
|
+
HexBlob(currentTxIn.scriptTxIn.scriptSource.script.code),
|
|
340
|
+
),
|
|
341
|
+
),
|
|
342
|
+
);
|
|
343
|
+
this.usedLanguages[PlutusLanguageVersion.V2] = true;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case "V3": {
|
|
347
|
+
this.scriptsProvided.add(
|
|
348
|
+
Script.newPlutusV3Script(
|
|
349
|
+
PlutusV3Script.fromCbor(
|
|
350
|
+
HexBlob(currentTxIn.scriptTxIn.scriptSource.script.code),
|
|
351
|
+
),
|
|
352
|
+
),
|
|
353
|
+
);
|
|
354
|
+
this.usedLanguages[PlutusLanguageVersion.V3] = true;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} else if (currentTxIn.scriptTxIn.scriptSource.type === "Inline") {
|
|
359
|
+
let referenceInputs = this.txBody.referenceInputs()
|
|
360
|
+
? this.txBody.referenceInputs()!
|
|
361
|
+
: Serialization.CborSet.fromCore([], TransactionInput.fromCore);
|
|
362
|
+
|
|
363
|
+
let referenceInputsList = [...referenceInputs.values()];
|
|
364
|
+
|
|
365
|
+
referenceInputsList.push(
|
|
366
|
+
new TransactionInput(
|
|
367
|
+
TransactionId(currentTxIn.scriptTxIn.scriptSource.txHash),
|
|
368
|
+
BigInt(currentTxIn.scriptTxIn.scriptSource.txIndex),
|
|
369
|
+
),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
referenceInputs.setValues(referenceInputsList);
|
|
373
|
+
|
|
374
|
+
this.txBody.setReferenceInputs(referenceInputs);
|
|
375
|
+
switch (currentTxIn.scriptTxIn.scriptSource.version) {
|
|
376
|
+
case "V1": {
|
|
377
|
+
this.usedLanguages[PlutusLanguageVersion.V1] = true;
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case "V2": {
|
|
381
|
+
this.usedLanguages[PlutusLanguageVersion.V2] = true;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "V3": {
|
|
385
|
+
this.usedLanguages[PlutusLanguageVersion.V3] = true;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (currentTxIn.scriptTxIn.datumSource.type === "Provided") {
|
|
391
|
+
// TODO: handle json / raw datum
|
|
392
|
+
this.datumsProvided.add(
|
|
393
|
+
toPlutusData(currentTxIn.scriptTxIn.datumSource.data.content as Data), // TODO: handle json / raw datum
|
|
394
|
+
);
|
|
395
|
+
} else if (currentTxIn.scriptTxIn.datumSource.type === "Inline") {
|
|
396
|
+
let referenceInputs = this.txBody.referenceInputs()
|
|
397
|
+
? this.txBody.referenceInputs()!
|
|
398
|
+
: Serialization.CborSet.fromCore([], TransactionInput.fromCore);
|
|
399
|
+
|
|
400
|
+
let referenceInputsList = [...referenceInputs.values()];
|
|
401
|
+
|
|
402
|
+
referenceInputsList.push(
|
|
403
|
+
new TransactionInput(
|
|
404
|
+
TransactionId(currentTxIn.txIn.txHash),
|
|
405
|
+
BigInt(currentTxIn.txIn.txIndex),
|
|
406
|
+
),
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
referenceInputs.setValues(referenceInputsList);
|
|
410
|
+
|
|
411
|
+
this.txBody.setReferenceInputs(referenceInputs);
|
|
412
|
+
}
|
|
413
|
+
let cardanoTxIn = new TransactionInput(
|
|
414
|
+
TransactionId(currentTxIn.txIn.txHash),
|
|
415
|
+
BigInt(currentTxIn.txIn.txIndex),
|
|
416
|
+
);
|
|
417
|
+
// Keep track of all redeemers mapped to their inputs
|
|
418
|
+
// TODO: handle json / raw redeemer data
|
|
419
|
+
let exUnits = currentTxIn.scriptTxIn.redeemer.exUnits;
|
|
420
|
+
|
|
421
|
+
this.redeemerContext.set(
|
|
422
|
+
cardanoTxIn,
|
|
423
|
+
new Redeemer(
|
|
424
|
+
RedeemerTag.Spend,
|
|
425
|
+
BigInt(0),
|
|
426
|
+
toPlutusData(currentTxIn.scriptTxIn.redeemer.data.content as Data), // TODO: handle json / raw datum
|
|
427
|
+
new ExUnits(BigInt(exUnits.mem), BigInt(exUnits.steps)),
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
private addSimpleScriptTxIn = (
|
|
433
|
+
currentTxIn: RequiredWith<SimpleScriptTxIn, "txIn" | "simpleScriptTxIn">,
|
|
434
|
+
) => {};
|
|
435
|
+
|
|
436
|
+
private addAllCollateralInputs = (collaterals: PubKeyTxIn[]) => {
|
|
437
|
+
for (let i = 0; i < collaterals.length; i++) {
|
|
438
|
+
this.addCollateralInput(
|
|
439
|
+
collaterals[i] as RequiredWith<PubKeyTxIn, "txIn">,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
private addCollateralInput = (
|
|
445
|
+
collateral: RequiredWith<PubKeyTxIn, "txIn">,
|
|
446
|
+
) => {
|
|
447
|
+
// First build Cardano tx in and add it to tx body
|
|
448
|
+
let cardanoTxIn = new TransactionInput(
|
|
449
|
+
TransactionId(collateral.txIn.txHash),
|
|
450
|
+
BigInt(collateral.txIn.txIndex),
|
|
451
|
+
);
|
|
452
|
+
const collateralInputs = this.txBody.collateral()
|
|
453
|
+
? this.txBody.collateral()!
|
|
454
|
+
: Serialization.CborSet.fromCore([], TransactionInput.fromCore);
|
|
455
|
+
const collateralInputsList: TransactionInput[] = [
|
|
456
|
+
...collateralInputs.values(),
|
|
457
|
+
];
|
|
458
|
+
if (
|
|
459
|
+
collateralInputsList.find((input) => {
|
|
460
|
+
input.index() == cardanoTxIn.index() &&
|
|
461
|
+
input.transactionId == cardanoTxIn.transactionId;
|
|
462
|
+
})
|
|
463
|
+
) {
|
|
464
|
+
throw new Error("Duplicate input added to tx body");
|
|
465
|
+
}
|
|
466
|
+
collateralInputsList.push(cardanoTxIn);
|
|
467
|
+
collateralInputs.setValues(collateralInputsList);
|
|
468
|
+
// We save the output to a mapping so that we can calculate collateral return later
|
|
469
|
+
// TODO: set collateral return
|
|
470
|
+
const cardanoTxOut = new TransactionOutput(
|
|
471
|
+
Address.fromBech32(collateral.txIn.address),
|
|
472
|
+
toValue(collateral.txIn.amount),
|
|
473
|
+
);
|
|
474
|
+
this.utxoContext.set(cardanoTxIn, cardanoTxOut);
|
|
475
|
+
this.txBody.setCollateral(collateralInputs);
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
private buildWitnessSet = () => {
|
|
479
|
+
const inputs = this.txBody.inputs();
|
|
480
|
+
// Search through the inputs, and set each redeemer index to the correct one
|
|
481
|
+
for (let i = 0; i < inputs.size(); i += 1) {
|
|
482
|
+
const input = inputs.values().at(i);
|
|
483
|
+
if (input) {
|
|
484
|
+
let redeemer = this.redeemerContext.get(input);
|
|
485
|
+
if (redeemer) {
|
|
486
|
+
redeemer.setIndex(BigInt(i));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// Add redeemers to tx witness set
|
|
491
|
+
let redeemers = this.txWitnessSet.redeemers()
|
|
492
|
+
? this.txWitnessSet.redeemers()!
|
|
493
|
+
: Redeemers.fromCore([]);
|
|
494
|
+
let redeemersList = [...redeemers.values()];
|
|
495
|
+
this.redeemerContext.forEach((redeemer) => {
|
|
496
|
+
redeemersList.push(redeemer);
|
|
497
|
+
});
|
|
498
|
+
redeemers.setValues(redeemersList);
|
|
499
|
+
this.txWitnessSet.setRedeemers(redeemers);
|
|
500
|
+
|
|
501
|
+
// Add provided scripts to tx witness set
|
|
502
|
+
let nativeScripts = this.txWitnessSet.nativeScripts()
|
|
503
|
+
? this.txWitnessSet.nativeScripts()!
|
|
504
|
+
: Serialization.CborSet.fromCore([], NativeScript.fromCore);
|
|
505
|
+
|
|
506
|
+
let v1Scripts = this.txWitnessSet.plutusV1Scripts()
|
|
507
|
+
? this.txWitnessSet.plutusV1Scripts()!
|
|
508
|
+
: Serialization.CborSet.fromCore([], PlutusV1Script.fromCore);
|
|
509
|
+
|
|
510
|
+
let v2Scripts = this.txWitnessSet.plutusV2Scripts()
|
|
511
|
+
? this.txWitnessSet.plutusV2Scripts()!
|
|
512
|
+
: Serialization.CborSet.fromCore([], PlutusV2Script.fromCore);
|
|
513
|
+
|
|
514
|
+
let v3Scripts = this.txWitnessSet.plutusV3Scripts()
|
|
515
|
+
? this.txWitnessSet.plutusV3Scripts()!
|
|
516
|
+
: Serialization.CborSet.fromCore([], PlutusV3Script.fromCore);
|
|
517
|
+
|
|
518
|
+
this.scriptsProvided.forEach((script) => {
|
|
519
|
+
if (script.asNative() !== undefined) {
|
|
520
|
+
let nativeScriptsList = [...nativeScripts.values()];
|
|
521
|
+
nativeScriptsList.push(script.asNative()!);
|
|
522
|
+
nativeScripts.setValues(nativeScriptsList);
|
|
523
|
+
} else if (script.asPlutusV1() !== undefined) {
|
|
524
|
+
let v1ScriptsList = [...v1Scripts.values()];
|
|
525
|
+
v1ScriptsList.push(script.asPlutusV1()!);
|
|
526
|
+
v1Scripts.setValues(v1ScriptsList);
|
|
527
|
+
} else if (script.asPlutusV2() !== undefined) {
|
|
528
|
+
let v2ScriptsList = [...v2Scripts.values()];
|
|
529
|
+
v2ScriptsList.push(script.asPlutusV2()!);
|
|
530
|
+
v2Scripts.setValues(v2ScriptsList);
|
|
531
|
+
} else if (script.asPlutusV3() !== undefined) {
|
|
532
|
+
let v3ScriptsList = [...v3Scripts.values()];
|
|
533
|
+
v3ScriptsList.push(script.asPlutusV3()!);
|
|
534
|
+
v3Scripts.setValues(v3ScriptsList);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
this.txWitnessSet.setNativeScripts(nativeScripts);
|
|
538
|
+
this.txWitnessSet.setPlutusV1Scripts(v1Scripts);
|
|
539
|
+
this.txWitnessSet.setPlutusV2Scripts(v2Scripts);
|
|
540
|
+
this.txWitnessSet.setPlutusV3Scripts(v3Scripts);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// Add provided datums to tx witness set
|
|
544
|
+
let datums = this.txWitnessSet.plutusData()
|
|
545
|
+
? this.txWitnessSet.plutusData()!
|
|
546
|
+
: Serialization.CborSet.fromCore([], PlutusData.fromCore);
|
|
547
|
+
|
|
548
|
+
this.datumsProvided.forEach((datum) => {
|
|
549
|
+
let datumsList = [...datums.values()];
|
|
550
|
+
datumsList.push(datum);
|
|
551
|
+
datums.setValues(datumsList);
|
|
552
|
+
});
|
|
553
|
+
this.txWitnessSet.setPlutusData(datums);
|
|
554
|
+
|
|
555
|
+
// After building tx witness set, we must hash it with the cost models
|
|
556
|
+
// and put the hash in the tx body
|
|
557
|
+
let costModelV1 = Serialization.CostModel.newPlutusV1(
|
|
558
|
+
DEFAULT_V1_COST_MODEL_LIST,
|
|
559
|
+
);
|
|
560
|
+
let costModelV2 = Serialization.CostModel.newPlutusV2(
|
|
561
|
+
DEFAULT_V2_COST_MODEL_LIST,
|
|
562
|
+
);
|
|
563
|
+
let costModels = new Serialization.Costmdls();
|
|
564
|
+
|
|
565
|
+
if (this.usedLanguages[PlutusLanguageVersion.V1]) {
|
|
566
|
+
costModels.insert(costModelV1);
|
|
567
|
+
}
|
|
568
|
+
if (this.usedLanguages[PlutusLanguageVersion.V2]) {
|
|
569
|
+
costModels.insert(costModelV2);
|
|
570
|
+
}
|
|
571
|
+
if (this.usedLanguages[PlutusLanguageVersion.V3]) {
|
|
572
|
+
// TODO: insert v3 cost models after conway HF
|
|
573
|
+
}
|
|
574
|
+
let scriptDataHash = hashScriptData(
|
|
575
|
+
costModels,
|
|
576
|
+
redeemers.size() > 0 ? [...redeemers.values()] : undefined,
|
|
577
|
+
datums.size() > 0 ? [...datums.values()] : undefined,
|
|
578
|
+
);
|
|
579
|
+
if (scriptDataHash) {
|
|
580
|
+
this.txBody.setScriptDataHash(scriptDataHash);
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
private balanceTx = (
|
|
585
|
+
changeAddress: string,
|
|
586
|
+
numberOfRequiredWitnesses: number,
|
|
587
|
+
protocolParams: Protocol,
|
|
588
|
+
) => {
|
|
589
|
+
if (changeAddress === "") {
|
|
590
|
+
throw new Error("Can't balance tx without a change address");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// First we add up all input values
|
|
594
|
+
const inputs = this.txBody.inputs().values();
|
|
595
|
+
let remainingValue = new Value(BigInt(0));
|
|
596
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
597
|
+
let input = inputs[i];
|
|
598
|
+
if (!input) {
|
|
599
|
+
throw new Error("Invalid input found");
|
|
600
|
+
}
|
|
601
|
+
const output = this.utxoContext.get(input);
|
|
602
|
+
if (!output) {
|
|
603
|
+
throw new Error(`Unable to resolve input: ${input.toCbor()}`);
|
|
604
|
+
}
|
|
605
|
+
remainingValue = mergeValue(remainingValue, output.amount());
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Then we add all withdrawal values
|
|
609
|
+
const withdrawals = this.txBody.withdrawals();
|
|
610
|
+
if (withdrawals) {
|
|
611
|
+
withdrawals.forEach((coin) => {
|
|
612
|
+
remainingValue = mergeValue(remainingValue, new Value(coin));
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Then we add all mint values
|
|
617
|
+
remainingValue = mergeValue(
|
|
618
|
+
remainingValue,
|
|
619
|
+
new Value(BigInt(0), this.txBody.mint()),
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
// We then take away any current outputs
|
|
623
|
+
const currentOutputs = this.txBody.outputs();
|
|
624
|
+
for (let i = 0; i < currentOutputs.length; i++) {
|
|
625
|
+
let output = currentOutputs.at(i);
|
|
626
|
+
if (output) {
|
|
627
|
+
remainingValue = subValue(remainingValue, output.amount());
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Add an initial change output, this is needed to generate dummy tx
|
|
632
|
+
// If inputs - outputs is negative, then throw error
|
|
633
|
+
if (remainingValue.coin() < 0 || !empty(negatives(remainingValue))) {
|
|
634
|
+
throw new Error(`Not enough funds to satisfy outputs`);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
currentOutputs.push(
|
|
638
|
+
new TransactionOutput(Address.fromBech32(changeAddress), remainingValue),
|
|
639
|
+
);
|
|
640
|
+
this.txBody.setOutputs(currentOutputs);
|
|
641
|
+
|
|
642
|
+
// Create a dummy tx that we will use to calculate fees
|
|
643
|
+
this.txBody.setFee(BigInt("10000000"));
|
|
644
|
+
const dummyTx = this.createDummyTx(numberOfRequiredWitnesses);
|
|
645
|
+
const fee =
|
|
646
|
+
protocolParams.minFeeB +
|
|
647
|
+
(dummyTx.toCbor().length / 2) * Number(protocolParams.coinsPerUtxoSize);
|
|
648
|
+
this.txBody.setFee(BigInt(fee));
|
|
649
|
+
|
|
650
|
+
// The change output should be the last element in outputs
|
|
651
|
+
// so we can simply take away the calculated fees from it
|
|
652
|
+
const changeOutput = currentOutputs.pop();
|
|
653
|
+
if (!changeOutput) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
"Somehow the output length was 0 after attempting to calculate fees",
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
changeOutput.amount().setCoin(changeOutput.amount().coin() - BigInt(fee));
|
|
659
|
+
currentOutputs.push(changeOutput);
|
|
660
|
+
this.txBody.setOutputs(currentOutputs);
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
private createDummyTx = (numberOfRequiredWitnesses: number): Transaction => {
|
|
664
|
+
let dummyWitnessSet = new TransactionWitnessSet();
|
|
665
|
+
const dummyVkeyWitnesses: [Ed25519PublicKeyHex, Ed25519SignatureHex][] = [];
|
|
666
|
+
for (let i = 0; i < numberOfRequiredWitnesses; i++) {
|
|
667
|
+
dummyVkeyWitnesses.push([
|
|
668
|
+
Ed25519PublicKeyHex("0".repeat(64)),
|
|
669
|
+
Ed25519SignatureHex("0".repeat(128)),
|
|
670
|
+
]);
|
|
671
|
+
}
|
|
672
|
+
dummyWitnessSet.setVkeys(
|
|
673
|
+
Serialization.CborSet.fromCore(dummyVkeyWitnesses, VkeyWitness.fromCore),
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
return new Transaction(this.txBody, dummyWitnessSet);
|
|
677
|
+
};
|
|
678
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Bip32PrivateKey,
|
|
3
|
+
Bip32PublicKey,
|
|
4
|
+
PublicKey,
|
|
5
|
+
PrivateKey as StricahqPrivateKey,
|
|
6
|
+
} from "@stricahq/bip32ed25519";
|
|
7
|
+
import hash from "hash.js";
|
|
8
|
+
|
|
9
|
+
class PrivateKey extends StricahqPrivateKey {
|
|
10
|
+
constructor(privKey: Buffer, extended: Boolean = true) {
|
|
11
|
+
if (!extended) {
|
|
12
|
+
let extendedSecret = hash.sha512().update(privKey).digest();
|
|
13
|
+
if (extendedSecret[0] && extendedSecret[31]) {
|
|
14
|
+
extendedSecret[0] &= 0b1111_1000;
|
|
15
|
+
extendedSecret[31] &= 0b0011_1111;
|
|
16
|
+
extendedSecret[31] |= 0b0100_0000;
|
|
17
|
+
}
|
|
18
|
+
privKey = Buffer.from(extendedSecret);
|
|
19
|
+
}
|
|
20
|
+
super(privKey);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { PrivateKey, PublicKey, Bip32PrivateKey, Bip32PublicKey };
|