@easy1staking/cip113-sdk-ts 0.3.1 → 0.5.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/README.md +22 -0
- package/blueprints/standard/v0.3.0/UPSTREAM_PIN.json +30 -0
- package/blueprints/standard/v0.5.0-alpha.2/UPSTREAM_PIN.json +33 -0
- package/blueprints/standard/v0.5.0-alpha.2/plutus.json +1010 -0
- package/blueprints/substandards/dummy/v0.1.0/UPSTREAM_PIN.json +29 -0
- package/blueprints/substandards/dummy/v0.2.0/UPSTREAM_PIN.json +38 -0
- package/blueprints/substandards/dummy/v0.2.0/plutus.json +84 -0
- package/blueprints/substandards/freeze-and-seize/v0.1.0/UPSTREAM_PIN.json +36 -0
- package/dist/core/cip171.d.ts +149 -0
- package/dist/core/cip171.d.ts.map +1 -0
- package/dist/core/cip171.js +284 -0
- package/dist/core/cip171.js.map +1 -0
- package/dist/core/evo-utils.d.ts +134 -17
- package/dist/core/evo-utils.d.ts.map +1 -1
- package/dist/core/evo-utils.js +162 -21
- package/dist/core/evo-utils.js.map +1 -1
- package/dist/core/ledger-order.d.ts +109 -0
- package/dist/core/ledger-order.d.ts.map +1 -0
- package/dist/core/ledger-order.js +185 -0
- package/dist/core/ledger-order.js.map +1 -0
- package/dist/core/registry.d.ts +16 -3
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +18 -9
- package/dist/core/registry.js.map +1 -1
- package/dist/index.d.ts +26 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -16
- package/dist/index.js.map +1 -1
- package/dist/standard/blueprint.d.ts +48 -1
- package/dist/standard/blueprint.d.ts.map +1 -1
- package/dist/standard/blueprint.js +71 -5
- package/dist/standard/blueprint.js.map +1 -1
- package/dist/standard/scripts.d.ts +124 -22
- package/dist/standard/scripts.d.ts.map +1 -1
- package/dist/standard/scripts.js +182 -55
- package/dist/standard/scripts.js.map +1 -1
- package/dist/substandards/dummy/index.d.ts.map +1 -1
- package/dist/substandards/dummy/index.js +470 -26
- package/dist/substandards/dummy/index.js.map +1 -1
- package/dist/substandards/freeze-and-seize/index.d.ts.map +1 -1
- package/dist/substandards/freeze-and-seize/index.js +314 -41
- package/dist/substandards/freeze-and-seize/index.js.map +1 -1
- package/dist/substandards/freeze-and-seize/scripts.d.ts +2 -1
- package/dist/substandards/freeze-and-seize/scripts.d.ts.map +1 -1
- package/dist/substandards/freeze-and-seize/scripts.js +7 -6
- package/dist/substandards/freeze-and-seize/scripts.js.map +1 -1
- package/dist/substandards/interface.d.ts +111 -1
- package/dist/substandards/interface.d.ts.map +1 -1
- package/dist/types.d.ts +87 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -1
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Capabilities: register, mint, burn, transfer, freeze, unfreeze, seize.
|
|
7
7
|
*/
|
|
8
|
+
import { CIP171_METADATA_LABEL, buildCip171Metadatum } from "../../core/cip171.js";
|
|
8
9
|
import { Address as EvoAddress, Assets, Data, Transaction, TransactionHash as EvoTransactionHash, TransactionInput as EvoTransactionInput, } from "@evolution-sdk/evolution";
|
|
9
10
|
import { sortTxInputs, findRefInputIndex, findRegistryNode, findCoveringNode, utxoToTxInput, } from "../../core/registry.js";
|
|
10
|
-
import { buildEvoScript, scriptAddress, rewardAddress, baseAddress, stakingCredentialHash, paymentCredentialHash, stringToHex, MAX_NEXT, voidData, registryNodeDatum, blacklistNodeDatum,
|
|
11
|
+
import { buildEvoScript, scriptAddress, rewardAddress, baseAddress, stakingCredentialHash, paymentCredentialHash, stringToHex, MAX_NEXT, voidData, registryNodeDatum, decodeRegistryNode, blacklistNodeDatum, mintingProofOutputIndex, mintingProofRefInput, registryInsertRedeemer, blacklistInitRedeemer, blacklistAddRedeemer, blacklistRemoveRedeemer, extractConstrBytesField, getInlineDatum, utxoUnitQty, utxoLovelace, utxoTxHash, utxoOutputIndex, outputAssets, mintAssetsFromMap, REGISTRY_NODE_MIN_ADA, Credential, KeyHash, InlineDatum, labeledAssetName, buildCIP68FTDatum, } from "../../core/evo-utils.js";
|
|
12
|
+
import { baseSpendRedeemer, transferRedeemer, thirdPartyRedeemer, withdrawalIndexOf, } from "../../core/ledger-order.js";
|
|
11
13
|
import { createFESScripts } from "./scripts.js";
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
13
15
|
// Helpers
|
|
@@ -16,6 +18,20 @@ import { createFESScripts } from "./scripts.js";
|
|
|
16
18
|
* Build the transaction and extract unsigned CBOR + txHash.
|
|
17
19
|
* Works with both ReadOnlyClient (TransactionResultBase) and SigningClient (SignBuilder).
|
|
18
20
|
*/
|
|
21
|
+
/**
|
|
22
|
+
* The evaluator this plugin builds with, set by `init()`.
|
|
23
|
+
*
|
|
24
|
+
* Module-scoped rather than threaded through nine call sites. Without it every
|
|
25
|
+
* script failure in this substandard surfaces as Kupmios's bare
|
|
26
|
+
* "evaluateTx failed", naming neither the failing validator nor the reason —
|
|
27
|
+
* which is the difference between a diagnosis and a guess, and it cost a cycle
|
|
28
|
+
* here exactly as it did in `dummy` before the same fix.
|
|
29
|
+
*
|
|
30
|
+
* ⚠ Shared across instances of this plugin in one process. Acceptable because
|
|
31
|
+
* it only affects DIAGNOSTICS, never the transaction built; if that ever stops
|
|
32
|
+
* being true, thread it through instead.
|
|
33
|
+
*/
|
|
34
|
+
let sharedEvaluator;
|
|
19
35
|
async function buildAndSerialize(
|
|
20
36
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
37
|
builder, changeAddress, availableUtxos, passAdditionalUtxos = false) {
|
|
@@ -23,6 +39,9 @@ builder, changeAddress, availableUtxos, passAdditionalUtxos = false) {
|
|
|
23
39
|
const buildOpts = {
|
|
24
40
|
changeAddress: EvoAddress.fromBech32(changeAddress),
|
|
25
41
|
};
|
|
42
|
+
if (sharedEvaluator) {
|
|
43
|
+
buildOpts.evaluator = sharedEvaluator;
|
|
44
|
+
}
|
|
26
45
|
if (availableUtxos) {
|
|
27
46
|
buildOpts.availableUtxos = availableUtxos;
|
|
28
47
|
}
|
|
@@ -42,6 +61,57 @@ builder, changeAddress, availableUtxos, passAdditionalUtxos = false) {
|
|
|
42
61
|
}
|
|
43
62
|
return { cbor, txHash, chainAvailable, _signBuilder: result };
|
|
44
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Wallet UTxOs that are SAFE TO SPEND.
|
|
66
|
+
*
|
|
67
|
+
* A deployment's reference scripts live in outputs somewhere on chain, and if
|
|
68
|
+
* that somewhere is the operator's own wallet — which is exactly what the
|
|
69
|
+
* devnet harness does — then `getUtxos(wallet)` hands them back alongside
|
|
70
|
+
* ordinary funds. Spending one DESTROYS protocol infrastructure: the
|
|
71
|
+
* `script_ref` is not carried into the change output, so the reference input
|
|
72
|
+
* every later operation depends on simply ceases to exist.
|
|
73
|
+
*
|
|
74
|
+
* MEASURED, not theorised: `seize` consumed the deployment's `third_party`
|
|
75
|
+
* reference-script UTxO this way, which is why it appeared to work at all —
|
|
76
|
+
* a spent input's reference script counts as supplied (CIP-33) — and why the
|
|
77
|
+
* next run failed with 3011.
|
|
78
|
+
*/
|
|
79
|
+
function spendableWalletUtxos(walletUtxos, deployment) {
|
|
80
|
+
const reserved = new Set([
|
|
81
|
+
deployment.programmableBaseRefInput,
|
|
82
|
+
deployment.transferRefInput,
|
|
83
|
+
deployment.thirdPartyRefInput,
|
|
84
|
+
deployment.unfrackingRefInput,
|
|
85
|
+
]
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
.map((r) => `${r.txHash}#${r.outputIndex}`));
|
|
88
|
+
return walletUtxos.filter((u) => {
|
|
89
|
+
// ⛔ ANY UTxO CARRYING A REFERENCE SCRIPT IS OFF LIMITS, not merely the four
|
|
90
|
+
// this deployment names.
|
|
91
|
+
//
|
|
92
|
+
// MEASURED, THE EXPENSIVE WAY: the named-four filter was not enough. A
|
|
93
|
+
// long-lived wallet accumulates reference scripts from EVERY deployment it
|
|
94
|
+
// has ever made, and coin selection is free to spend any of them. On
|
|
95
|
+
// preview it consumed two of the LIVE deployment's own four during a
|
|
96
|
+
// failed retry — the deployment two VERIFIED CIP-171 records describe —
|
|
97
|
+
// because those four were reached through a path that did not consult this
|
|
98
|
+
// filter at all.
|
|
99
|
+
//
|
|
100
|
+
// Spending one destroys protocol infrastructure silently: the script_ref is
|
|
101
|
+
// not carried into the change output, nothing errors, and the damage
|
|
102
|
+
// surfaces only when a later operation needs the script. On mainnet this is
|
|
103
|
+
// spending live reference scripts that running contracts depend on, and it
|
|
104
|
+
// would look like a successful transaction.
|
|
105
|
+
//
|
|
106
|
+
// The safe rule needs no knowledge of WHOSE deployment a script belongs to:
|
|
107
|
+
// if a wallet UTxO carries a reference script, it is infrastructure, not
|
|
108
|
+
// funds.
|
|
109
|
+
if (u.scriptRef)
|
|
110
|
+
return false;
|
|
111
|
+
const i = utxoToTxInput(u);
|
|
112
|
+
return !reserved.has(`${i.txHash}#${i.outputIndex}`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
45
115
|
function selectUtxosForAmount(utxos, unit, requiredAmount) {
|
|
46
116
|
const selected = [];
|
|
47
117
|
let total = 0n;
|
|
@@ -62,7 +132,9 @@ function selectUtxosForAmount(utxos, unit, requiredAmount) {
|
|
|
62
132
|
/** Find protocol params UTxO by searching for the protocol params NFT */
|
|
63
133
|
async function findProtocolParamsUtxo(client, networkId, deployment) {
|
|
64
134
|
const ppUnit = deployment.protocolParams.policyId + stringToHex("ProtocolParams");
|
|
65
|
-
const addr = EvoAddress.fromBech32(
|
|
135
|
+
const addr = EvoAddress.fromBech32(
|
|
136
|
+
// 0.5.x: params NFT lives at coordination_spend, not always_fail.
|
|
137
|
+
scriptAddress(networkId, deployment.coordination.scriptHash));
|
|
66
138
|
const utxos = await client.getUtxosWithUnit(addr, ppUnit);
|
|
67
139
|
if (utxos.length > 0)
|
|
68
140
|
return utxos[0];
|
|
@@ -114,7 +186,27 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
114
186
|
version: "0.1.0",
|
|
115
187
|
blueprint: config.blueprint,
|
|
116
188
|
init(context) {
|
|
189
|
+
// ---------------------------------------------------------------------
|
|
190
|
+
// MIGRATION STATE (W-E). Read this before assuming an operation works.
|
|
191
|
+
// ---------------------------------------------------------------------
|
|
192
|
+
//
|
|
193
|
+
// The blanket refusal that D-14 installed is GONE: `register`, `mint`,
|
|
194
|
+
// `transfer`, `initCompliance`, `freeze` and `unfreeze` are migrated to
|
|
195
|
+
// CIP-113 0.5.0-alpha.2 and this plugin initialises.
|
|
196
|
+
//
|
|
197
|
+
// `seize` and `burn` are migrated too (W-E S-5): both take the
|
|
198
|
+
// THIRD-PARTY route, which #110 moved off the deleted `ThirdPartyAct` and
|
|
199
|
+
// onto the standalone `third_party` validator's withdraw-0 with a
|
|
200
|
+
// ThirdPartyRedeemer, plus BaseSpendRedeemer(SpendViaThirdParty) on every
|
|
201
|
+
// programmable input.
|
|
202
|
+
//
|
|
203
|
+
// ⚠ The seize/burn paths still need re-validating against upstream #79
|
|
204
|
+
// (UTxO contamination), #80 (issuance delegation scope) and #115 (datum
|
|
205
|
+
// hashes and reference scripts banned on programmable outputs). Those are
|
|
206
|
+
// SEMANTIC changes with no signature to catch them, so a green devnet run
|
|
207
|
+
// is necessary and not sufficient.
|
|
117
208
|
ctx = context;
|
|
209
|
+
sharedEvaluator = ctx.evaluator;
|
|
118
210
|
networkId = ctx.client.chain.id;
|
|
119
211
|
const { adminPkh, assetName, blacklistNodePolicyId, blacklistInitTxInput } = config.deployment;
|
|
120
212
|
const fes = createFESScripts(config.blueprint);
|
|
@@ -140,7 +232,12 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
140
232
|
const { feePayerAddress, assetName, quantity, recipientAddress } = params;
|
|
141
233
|
const recipient = recipientAddress || feePayerAddress;
|
|
142
234
|
const chainedUtxos = (params.chainedUtxos ?? []);
|
|
143
|
-
|
|
235
|
+
// Already hex: `assetName` is raw asset-name HEX at every API boundary in
|
|
236
|
+
// this SDK. This previously ran stringToHex over it, double-encoding any
|
|
237
|
+
// caller who followed the convention — and silently producing a DIFFERENT
|
|
238
|
+
// token, under a policy derived from a different issuer_admin, so nothing
|
|
239
|
+
// failed; the tokens simply were not where anyone looked for them.
|
|
240
|
+
const assetNameHex = assetName;
|
|
144
241
|
const hasCIP68 = !!params.cip68Metadata;
|
|
145
242
|
const client = ctx.client;
|
|
146
243
|
// When CIP-68 is enabled, prefix asset names with CIP-67 labels
|
|
@@ -162,26 +259,41 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
162
259
|
const protocolParamsUtxo = await findProtocolParamsUtxo(client, networkId, ctx.deployment);
|
|
163
260
|
const issuanceCborHexUtxo = await findIssuanceCborHexUtxo(client, networkId, ctx.deployment);
|
|
164
261
|
// 3. Build datums
|
|
165
|
-
|
|
166
|
-
|
|
262
|
+
// UNREACHABLE — this plugin refuses at init (T-D09). Retained, and made to
|
|
263
|
+
// typecheck against the 7-field layout, so the freeze-and-seize epic gets a
|
|
264
|
+
// readable diff instead of a rewrite from memory.
|
|
265
|
+
//
|
|
266
|
+
// The index-based reads that used to be here (2/3/4) were silently wrong
|
|
267
|
+
// under the 7-field layout. Replaced with decodeRegistryNode, which is
|
|
268
|
+
// positional-safe and fails loudly on a short record.
|
|
269
|
+
if (!coveringDatum) {
|
|
270
|
+
throw new Error("register: the covering registry node has no inline datum. The directory " +
|
|
271
|
+
"cannot be traversed without it — a node with no datum is malformed, not empty.");
|
|
272
|
+
}
|
|
273
|
+
const covering = decodeRegistryNode(coveringDatum);
|
|
167
274
|
const updatedCoveringDatum = registryNodeDatum({
|
|
275
|
+
...covering,
|
|
168
276
|
key: coveringKey,
|
|
169
277
|
next: scripts.tokenPolicyId,
|
|
170
|
-
transferLogicScript: coveringTransferCred,
|
|
171
|
-
thirdPartyTransferLogicScript: coveringThirdPartyCred,
|
|
172
|
-
globalStateCs: extractConstrBytesField(coveringDatum, 4) ?? "",
|
|
173
278
|
});
|
|
174
279
|
const newRegistryNodeDatum = registryNodeDatum({
|
|
175
280
|
key: scripts.tokenPolicyId,
|
|
176
281
|
next: coveringNext,
|
|
282
|
+
mintingLogicScript: { type: "script", hash: scripts.issuerAdmin.hash },
|
|
177
283
|
transferLogicScript: { type: "script", hash: scripts.transfer.hash },
|
|
178
284
|
thirdPartyTransferLogicScript: { type: "script", hash: scripts.issuerAdmin.hash },
|
|
285
|
+
// ⚠ UNASSIGNED, NOT DECIDED. Upstream added an unfracking delegate per
|
|
286
|
+
// registry node; freeze-and-seize has no unfracking concept and the
|
|
287
|
+
// choice belongs to its own epic. Pointing it at issuerAdmin keeps the
|
|
288
|
+
// shape valid; it is NOT a design decision and is never emitted, because
|
|
289
|
+
// init() throws before any of this runs.
|
|
290
|
+
unfrackingLogicScript: { type: "script", hash: scripts.issuerAdmin.hash },
|
|
179
291
|
globalStateCs: "",
|
|
180
292
|
});
|
|
181
293
|
// 4. Build redeemers — registry output index shifts when CIP-68 adds an extra output
|
|
182
294
|
const registryOutputIndex = hasCIP68 ? 3 : 2;
|
|
183
|
-
const issuanceRedeemer =
|
|
184
|
-
const registryMintRedeemer = registryInsertRedeemer(scripts.issuanceMint.hash, scripts.issuerAdmin.hash);
|
|
295
|
+
const issuanceRedeemer = mintingProofOutputIndex(registryOutputIndex);
|
|
296
|
+
const registryMintRedeemer = registryInsertRedeemer(scripts.issuanceMint.hash, { type: "script", hash: scripts.issuerAdmin.hash });
|
|
185
297
|
const tokenDatum = voidData();
|
|
186
298
|
// 5. Determine if chaining from initCompliance
|
|
187
299
|
const useChaining = chainedUtxos.length > 0;
|
|
@@ -200,6 +312,14 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
200
312
|
const coveringNftUnit = findCoveringNodeNftUnit(coveringNodeUtxo, registryMintPolicyId);
|
|
201
313
|
// 8. Build transaction
|
|
202
314
|
let tx = client.newTx();
|
|
315
|
+
// CIP-171 provenance, carried by the transaction that parameterises the
|
|
316
|
+
// scripts it describes. Optional: absent, this behaves exactly as before.
|
|
317
|
+
if (params.cip171Record) {
|
|
318
|
+
tx = tx.attachMetadata({
|
|
319
|
+
label: CIP171_METADATA_LABEL,
|
|
320
|
+
metadata: buildCip171Metadatum(params.cip171Record),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
203
323
|
tx = tx.collectFrom({ inputs: [coveringNodeUtxo], redeemer: voidData() });
|
|
204
324
|
tx = tx.withdraw({
|
|
205
325
|
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(scripts.issuerAdmin.hash, "hex"))),
|
|
@@ -233,10 +353,19 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
233
353
|
assets: outputAssets(utxoLovelace(coveringNodeUtxo), coveringNodeTokenMap),
|
|
234
354
|
datum: new InlineDatum.InlineDatum({ data: updatedCoveringDatum }),
|
|
235
355
|
});
|
|
236
|
-
// New registry node (output 2 or 3)
|
|
356
|
+
// New registry node (output 2 or 3).
|
|
357
|
+
//
|
|
358
|
+
// 3 ADA, not 2: the RegistryNode datum is SEVEN fields in 0.5.x and
|
|
359
|
+
// min-UTxO scales with serialised output size. MEASURED at 2,038,630 for
|
|
360
|
+
// a node of this shape — the inherited 2,000,000 was sized for the
|
|
361
|
+
// five-field datum, and the ledger reports the shortfall as
|
|
362
|
+
// "insufficient Ada" with a number, never as "your datum grew".
|
|
363
|
+
// (The covering-node output above needs no change: it carries the
|
|
364
|
+
// covering UTxO's OWN lovelace forward, so it inherits whatever the
|
|
365
|
+
// bootstrap funded the origin with.)
|
|
237
366
|
tx = tx.payToAddress({
|
|
238
367
|
address: EvoAddress.fromBech32(registrySpendAddr),
|
|
239
|
-
assets: outputAssets(
|
|
368
|
+
assets: outputAssets(REGISTRY_NODE_MIN_ADA, new Map([[registryNftUnit, 1n]])),
|
|
240
369
|
datum: new InlineDatum.InlineDatum({ data: newRegistryNodeDatum }),
|
|
241
370
|
});
|
|
242
371
|
// Reference inputs
|
|
@@ -287,7 +416,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
287
416
|
const sortedRefInputs = sortTxInputs([regRef]);
|
|
288
417
|
const registryRefIdx = findRefInputIndex(sortedRefInputs, regRef);
|
|
289
418
|
// 3. Build redeemers
|
|
290
|
-
const issuanceRedeemer =
|
|
419
|
+
const issuanceRedeemer = mintingProofRefInput(registryRefIdx);
|
|
291
420
|
const tokenDatum = voidData();
|
|
292
421
|
// 4. Build PLB address
|
|
293
422
|
const plbHash = ctx.standardScripts.programmableLogicBase.hash;
|
|
@@ -297,7 +426,8 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
297
426
|
// 6. Build transaction
|
|
298
427
|
const tokenAssets = mintAssetsFromMap(new Map([[unit, quantity]]));
|
|
299
428
|
let tx = client.newTx();
|
|
300
|
-
|
|
429
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
430
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
301
431
|
tx = tx.withdraw({
|
|
302
432
|
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(scripts.issuerAdmin.hash, "hex"))),
|
|
303
433
|
amount: 0n,
|
|
@@ -313,7 +443,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
313
443
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuerAdmin.compiledCode) });
|
|
314
444
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuanceMint.compiledCode) });
|
|
315
445
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(config.deployment.adminPkh) });
|
|
316
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
446
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
317
447
|
return { cbor, txHash, tokenPolicyId, _signBuilder };
|
|
318
448
|
},
|
|
319
449
|
// ====================================================================
|
|
@@ -349,8 +479,33 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
349
479
|
const sortedRefInputs = sortTxInputs(allRefInputRefs);
|
|
350
480
|
const registryIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(registryUtxo));
|
|
351
481
|
// 4. Build redeemers
|
|
352
|
-
const issuanceRedeemer =
|
|
353
|
-
const
|
|
482
|
+
const issuanceRedeemer = mintingProofRefInput(registryIdx);
|
|
483
|
+
const paramsIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(protocolParamsUtxo));
|
|
484
|
+
// 0.5.x third-party route. `ThirdPartyAct` was DELETED by #110: the
|
|
485
|
+
// administrative path is now the standalone `third_party` validator, and
|
|
486
|
+
// programmable_logic_base dispatches to it via SpendViaThirdParty — so
|
|
487
|
+
// this transaction never loads the `transfer` reference script at all.
|
|
488
|
+
//
|
|
489
|
+
// The withdrawal set is BOTH scripts: the framework delegate, and the
|
|
490
|
+
// issuer authority the registry node names in
|
|
491
|
+
// `third_party_transfer_logic_script`. `third_party` requires the latter
|
|
492
|
+
// by name — it is the only thing standing between a holder's tokens and
|
|
493
|
+
// anyone who wants them, and for FES it is a real issuer-admin check
|
|
494
|
+
// rather than dummy's unconditional one.
|
|
495
|
+
const thirdPartyKey = {
|
|
496
|
+
hash: ctx.standardScripts.thirdParty.hash,
|
|
497
|
+
isScript: true,
|
|
498
|
+
};
|
|
499
|
+
const issuerAuthorityKey = { hash: scripts.issuerAdmin.hash, isScript: true };
|
|
500
|
+
const thirdPartyWdrlIdx = withdrawalIndexOf([thirdPartyKey, issuerAuthorityKey], thirdPartyKey);
|
|
501
|
+
// outputs_start_idx = 0: `third_party` PAIRS each programmable input with
|
|
502
|
+
// the NEXT output (same address, datum and reference script, lovelace
|
|
503
|
+
// ratcheting up) and reads the seized amount as the DELTA. Destination
|
|
504
|
+
// outputs must therefore sit AMONG THE LEADING ones it skips.
|
|
505
|
+
// See docs/api-reference.md — getting this backwards fails with an EMPTY
|
|
506
|
+
// TRACE LIST, because it is a structural expect and not a traced check.
|
|
507
|
+
const plgRedeemer = thirdPartyRedeemer(paramsIdx, registryIdx, 0);
|
|
508
|
+
const plbSpendRedeemer = baseSpendRedeemer("THIRD_PARTY", paramsIdx, thirdPartyWdrlIdx);
|
|
354
509
|
const tokenDatum = voidData();
|
|
355
510
|
// 5. Compute remaining assets (remove burned token's policy)
|
|
356
511
|
const remainingTokens = new Map();
|
|
@@ -370,31 +525,37 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
370
525
|
const walletUtxos = await client.getUtxos(EvoAddress.fromBech32(feePayerAddress));
|
|
371
526
|
// 8. Build transaction
|
|
372
527
|
let tx = client.newTx();
|
|
373
|
-
|
|
374
|
-
tx = tx.collectFrom({ inputs:
|
|
528
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
529
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
530
|
+
tx = tx.collectFrom({ inputs: [utxoToBurn], redeemer: plbSpendRedeemer });
|
|
375
531
|
tx = tx.withdraw({
|
|
376
532
|
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(scripts.issuerAdmin.hash, "hex"))),
|
|
377
533
|
amount: 0n,
|
|
378
534
|
redeemer: voidData(),
|
|
379
535
|
});
|
|
380
536
|
tx = tx.withdraw({
|
|
381
|
-
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.
|
|
537
|
+
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.thirdParty.hash, "hex"))),
|
|
382
538
|
amount: 0n,
|
|
383
539
|
redeemer: plgRedeemer,
|
|
384
540
|
});
|
|
385
541
|
tx = tx.payToAddress({
|
|
386
542
|
address: utxoToBurn.address,
|
|
387
543
|
assets: outputAssets(utxoLovelace(utxoToBurn), remainingTokens.size > 0 ? remainingTokens : undefined),
|
|
388
|
-
|
|
544
|
+
// The PAIRED continuation: `third_party` requires it to preserve the
|
|
545
|
+
// input's ADDRESS, DATUM and REFERENCE SCRIPT exactly. Carry the input's
|
|
546
|
+
// own datum rather than a fresh void one — they happen to be equal today
|
|
547
|
+
// because every programmable output here is void-datumed, and that is a
|
|
548
|
+
// coincidence rather than a guarantee.
|
|
549
|
+
datum: new InlineDatum.InlineDatum({ data: getInlineDatum(utxoToBurn) ?? tokenDatum }),
|
|
389
550
|
});
|
|
390
551
|
tx = tx.mintAssets({ assets: burnAssets, redeemer: issuanceRedeemer });
|
|
391
552
|
tx = tx.readFrom({ referenceInputs: [protocolParamsUtxo, registryUtxo] });
|
|
392
553
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicBase.compiledCode) });
|
|
393
|
-
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.
|
|
554
|
+
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.thirdParty.compiledCode) });
|
|
394
555
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuerAdmin.compiledCode) });
|
|
395
556
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuanceMint.compiledCode) });
|
|
396
557
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(config.deployment.adminPkh) });
|
|
397
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
558
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
398
559
|
return { cbor, txHash, _signBuilder };
|
|
399
560
|
},
|
|
400
561
|
// ====================================================================
|
|
@@ -451,18 +612,56 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
451
612
|
const sortedRefInputs = sortTxInputs(allRefInputRefs);
|
|
452
613
|
const proofIndices = selected.map(() => findRefInputIndex(sortedRefInputs, utxoToTxInput(proofUtxos[0])));
|
|
453
614
|
const registryIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(registryUtxo));
|
|
615
|
+
const paramsIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(protocolParamsUtxo));
|
|
454
616
|
// 8. Get sender's wallet UTxOs
|
|
455
617
|
const senderWalletUtxos = await client.getUtxos(EvoAddress.fromBech32(senderAddress));
|
|
456
|
-
// 9. Build redeemers
|
|
618
|
+
// 9. Build redeemers — 0.5.x shapes.
|
|
619
|
+
//
|
|
620
|
+
// The withdrawal set must be COMPLETE and in LEDGER order: script
|
|
621
|
+
// credentials before key credentials, bytewise within each. This
|
|
622
|
+
// transaction carries two, both scripts — the framework's `transfer`
|
|
623
|
+
// delegate and this substandard's own transfer logic.
|
|
624
|
+
const coreTransferKey = {
|
|
625
|
+
hash: ctx.standardScripts.transfer.hash,
|
|
626
|
+
isScript: true,
|
|
627
|
+
};
|
|
628
|
+
const fesLogicKey = { hash: scripts.transfer.hash, isScript: true };
|
|
629
|
+
const transferWdrlIdx = withdrawalIndexOf([coreTransferKey, fesLogicKey], coreTransferKey);
|
|
630
|
+
if (process.env.DUMP_TX_STRUCTURE) {
|
|
631
|
+
// Computed indices, printed beside the artefact's own order so the two
|
|
632
|
+
// can be COMPARED rather than reasoned about. A structural expect in
|
|
633
|
+
// programmable_logic_base fails with an empty trace list, so this is the
|
|
634
|
+
// only way to see which index is wrong.
|
|
635
|
+
// eslint-disable-next-line no-console
|
|
636
|
+
console.error("=== FES transfer COMPUTED ===\n" +
|
|
637
|
+
` refs (sorted): ${sortedRefInputs.map((r) => `${r.txHash.slice(0, 8)}#${r.outputIndex}`).join(" ")}\n` +
|
|
638
|
+
` paramsIdx=${paramsIdx} -> ${sortedRefInputs[paramsIdx]?.txHash?.slice(0, 8)}#${sortedRefInputs[paramsIdx]?.outputIndex}\n` +
|
|
639
|
+
` registryIdx=${registryIdx}\n` +
|
|
640
|
+
` coreTransfer=${ctx.standardScripts.transfer.hash}\n` +
|
|
641
|
+
` fesLogic =${scripts.transfer.hash}\n` +
|
|
642
|
+
` transferWdrlIdx=${transferWdrlIdx}\n` +
|
|
643
|
+
"=== END COMPUTED ===");
|
|
644
|
+
}
|
|
457
645
|
const fesTransferRedeemer = Data.list(proofIndices.map((idx) => Data.constr(0n, [Data.int(BigInt(idx))])));
|
|
458
|
-
|
|
459
|
-
|
|
646
|
+
// TransferRedeemer { params_idx, proofs } — params_idx was PREPENDED by
|
|
647
|
+
// #109; an old TransferAct encoder's bytes are not compatible.
|
|
648
|
+
const plgRedeemer = transferRedeemer(paramsIdx, [
|
|
649
|
+
{ type: "exists", nodeIdx: registryIdx },
|
|
650
|
+
]);
|
|
651
|
+
// programmable_logic_base no longer takes an untyped redeemer: it
|
|
652
|
+
// dispatches on the constructor and witnesses where its delegate's
|
|
653
|
+
// withdrawal sits.
|
|
654
|
+
const spendRdmr = baseSpendRedeemer("TRANSFER", paramsIdx, transferWdrlIdx);
|
|
460
655
|
const tokenDatum = voidData();
|
|
461
656
|
// 10. Build transaction
|
|
462
657
|
let tx = client.newTx();
|
|
463
658
|
tx = tx.collectFrom({ inputs: selected, redeemer: spendRdmr });
|
|
659
|
+
// The TRANSFER delegate — not third_party. programmable_logic_base
|
|
660
|
+
// resolves the withdrawal at `wdrl_idx` and requires it to equal the
|
|
661
|
+
// credential the params datum names for THIS dispatch arm, so withdrawing
|
|
662
|
+
// the wrong delegate fails the spend with an empty trace list.
|
|
464
663
|
tx = tx.withdraw({
|
|
465
|
-
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.
|
|
664
|
+
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.transfer.hash, "hex"))),
|
|
466
665
|
amount: 0n,
|
|
467
666
|
redeemer: plgRedeemer,
|
|
468
667
|
});
|
|
@@ -485,7 +684,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
485
684
|
});
|
|
486
685
|
tx = tx.readFrom({ referenceInputs: [...proofUtxos, protocolParamsUtxo, registryUtxo] });
|
|
487
686
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicBase.compiledCode) });
|
|
488
|
-
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.
|
|
687
|
+
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.transfer.compiledCode) });
|
|
489
688
|
tx = tx.attachScript({ script: buildEvoScript(scripts.transfer.compiledCode) });
|
|
490
689
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(senderStakingHash) });
|
|
491
690
|
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, senderAddress, senderWalletUtxos);
|
|
@@ -592,7 +791,8 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
592
791
|
const nftAssets = mintAssetsFromMap(new Map([[nftUnit, 1n]]));
|
|
593
792
|
const walletUtxos = await client.getUtxos(EvoAddress.fromBech32(feePayerAddress));
|
|
594
793
|
let tx = client.newTx();
|
|
595
|
-
|
|
794
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
795
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
596
796
|
tx = tx.collectFrom({ inputs: [coveringNode], redeemer: voidData() });
|
|
597
797
|
tx = tx.mintAssets({ assets: nftAssets, redeemer: blacklistAddRedeemer(targetStakingHash) });
|
|
598
798
|
// Output 0: updated covering node
|
|
@@ -615,7 +815,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
615
815
|
tx = tx.attachScript({ script: buildEvoScript(scripts.blacklistMint.compiledCode) });
|
|
616
816
|
const managerPkh = paymentCredentialHash(feePayerAddress);
|
|
617
817
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(managerPkh) });
|
|
618
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
818
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
619
819
|
return { cbor, txHash, _signBuilder };
|
|
620
820
|
},
|
|
621
821
|
// ====================================================================
|
|
@@ -648,7 +848,8 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
648
848
|
const burnAssets = mintAssetsFromMap(new Map([[nftUnit, -1n]]));
|
|
649
849
|
const walletUtxos = await client.getUtxos(EvoAddress.fromBech32(feePayerAddress));
|
|
650
850
|
let tx = client.newTx();
|
|
651
|
-
|
|
851
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
852
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
652
853
|
tx = tx.collectFrom({ inputs: [nodeToRemove], redeemer: voidData() });
|
|
653
854
|
tx = tx.collectFrom({ inputs: [precedingNode], redeemer: voidData() });
|
|
654
855
|
tx = tx.mintAssets({ assets: burnAssets, redeemer: blacklistRemoveRedeemer(targetStakingHash) });
|
|
@@ -666,7 +867,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
666
867
|
tx = tx.attachScript({ script: buildEvoScript(scripts.blacklistMint.compiledCode) });
|
|
667
868
|
const managerPkh = paymentCredentialHash(feePayerAddress);
|
|
668
869
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(managerPkh) });
|
|
669
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
870
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
670
871
|
return { cbor, txHash, _signBuilder };
|
|
671
872
|
},
|
|
672
873
|
// ====================================================================
|
|
@@ -709,11 +910,64 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
709
910
|
if (!registryUtxo)
|
|
710
911
|
throw new Error(`Registry node not found for ${tokenPolicyId}`);
|
|
711
912
|
// 3. Sort reference inputs
|
|
712
|
-
|
|
913
|
+
// `third_party`'s script is supplied EXPLICITLY, as a reference input.
|
|
914
|
+
//
|
|
915
|
+
// ⚠ It used to arrive BY ACCIDENT: the bootstrap publishes reference
|
|
916
|
+
// scripts to the fee payer's own address, so `getUtxos(wallet)` returns
|
|
917
|
+
// them and `slice(0, 2)` sometimes SPENT one. A spent input's reference
|
|
918
|
+
// script counts as supplied (CIP-33), so the transaction validated —
|
|
919
|
+
// and CONSUMED the deployment's reference-script UTxO in the process.
|
|
920
|
+
// When the draw went the other way the script was absent entirely and
|
|
921
|
+
// the ledger rejected with 3011 (missing script witness). One root
|
|
922
|
+
// cause, two opposite symptoms; the earlier 3104 was its third face,
|
|
923
|
+
// an explicit attach duplicating what the spent input already supplied.
|
|
924
|
+
const thirdPartyRefUtxos = await client.getUtxosByOutRef([
|
|
925
|
+
new EvoTransactionInput.TransactionInput({
|
|
926
|
+
transactionId: EvoTransactionHash.fromHex(ctx.deployment.thirdPartyRefInput.txHash),
|
|
927
|
+
index: BigInt(ctx.deployment.thirdPartyRefInput.outputIndex),
|
|
928
|
+
}),
|
|
929
|
+
]);
|
|
930
|
+
if (thirdPartyRefUtxos.length === 0) {
|
|
931
|
+
throw new Error(`third_party reference script not found on-chain at ` +
|
|
932
|
+
`${ctx.deployment.thirdPartyRefInput.txHash}#${ctx.deployment.thirdPartyRefInput.outputIndex}. ` +
|
|
933
|
+
`Deployment reference scripts are load-bearing: if an earlier transaction SPENT this ` +
|
|
934
|
+
`output, the deployment is broken and must be re-published.`);
|
|
935
|
+
}
|
|
936
|
+
const thirdPartyRefUtxo = thirdPartyRefUtxos[0];
|
|
937
|
+
const allRefInputRefs = [
|
|
938
|
+
utxoToTxInput(protocolParamsUtxo),
|
|
939
|
+
utxoToTxInput(registryUtxo),
|
|
940
|
+
utxoToTxInput(thirdPartyRefUtxo),
|
|
941
|
+
];
|
|
713
942
|
const sortedRefInputs = sortTxInputs(allRefInputRefs);
|
|
714
943
|
const registryIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(registryUtxo));
|
|
715
944
|
// 4. Build redeemers
|
|
716
|
-
const
|
|
945
|
+
const paramsIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(protocolParamsUtxo));
|
|
946
|
+
// 0.5.x third-party route. `ThirdPartyAct` was DELETED by #110: the
|
|
947
|
+
// administrative path is now the standalone `third_party` validator, and
|
|
948
|
+
// programmable_logic_base dispatches to it via SpendViaThirdParty — so
|
|
949
|
+
// this transaction never loads the `transfer` reference script at all.
|
|
950
|
+
//
|
|
951
|
+
// The withdrawal set is BOTH scripts: the framework delegate, and the
|
|
952
|
+
// issuer authority the registry node names in
|
|
953
|
+
// `third_party_transfer_logic_script`. `third_party` requires the latter
|
|
954
|
+
// by name — it is the only thing standing between a holder's tokens and
|
|
955
|
+
// anyone who wants them, and for FES it is a real issuer-admin check
|
|
956
|
+
// rather than dummy's unconditional one.
|
|
957
|
+
const thirdPartyKey = {
|
|
958
|
+
hash: ctx.standardScripts.thirdParty.hash,
|
|
959
|
+
isScript: true,
|
|
960
|
+
};
|
|
961
|
+
const issuerAuthorityKey = { hash: scripts.issuerAdmin.hash, isScript: true };
|
|
962
|
+
const thirdPartyWdrlIdx = withdrawalIndexOf([thirdPartyKey, issuerAuthorityKey], thirdPartyKey);
|
|
963
|
+
// outputs_start_idx = 1: `third_party` PAIRS each programmable input with
|
|
964
|
+
// the NEXT output (same address, datum and reference script, lovelace
|
|
965
|
+
// ratcheting up) and reads the seized amount as the DELTA. Destination
|
|
966
|
+
// outputs must therefore sit AMONG THE LEADING ones it skips.
|
|
967
|
+
// See docs/api-reference.md — getting this backwards fails with an EMPTY
|
|
968
|
+
// TRACE LIST, because it is a structural expect and not a traced check.
|
|
969
|
+
const plgRedeemer = thirdPartyRedeemer(paramsIdx, registryIdx, 1);
|
|
970
|
+
const plbSpendRedeemer = baseSpendRedeemer("THIRD_PARTY", paramsIdx, thirdPartyWdrlIdx);
|
|
717
971
|
const tokenDatum = voidData();
|
|
718
972
|
// 5. Build recipient PLB address
|
|
719
973
|
const recipientPlbAddr = baseAddress(networkId, plbHash, destinationAddress);
|
|
@@ -729,17 +983,19 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
729
983
|
}
|
|
730
984
|
// 7. Get wallet UTxOs
|
|
731
985
|
const walletUtxos = await client.getUtxos(EvoAddress.fromBech32(feePayerAddress));
|
|
986
|
+
// NEVER spend a deployment reference-script UTxO — see spendableWalletUtxos.
|
|
987
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
732
988
|
// 8. Build transaction
|
|
733
989
|
let tx = client.newTx();
|
|
734
|
-
tx = tx.collectFrom({ inputs:
|
|
735
|
-
tx = tx.collectFrom({ inputs: [utxoToSeize], redeemer:
|
|
990
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
991
|
+
tx = tx.collectFrom({ inputs: [utxoToSeize], redeemer: plbSpendRedeemer });
|
|
736
992
|
tx = tx.withdraw({
|
|
737
993
|
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(scripts.issuerAdmin.hash, "hex"))),
|
|
738
994
|
amount: 0n,
|
|
739
995
|
redeemer: voidData(),
|
|
740
996
|
});
|
|
741
997
|
tx = tx.withdraw({
|
|
742
|
-
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.
|
|
998
|
+
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.thirdParty.hash, "hex"))),
|
|
743
999
|
amount: 0n,
|
|
744
1000
|
redeemer: plgRedeemer,
|
|
745
1001
|
});
|
|
@@ -753,14 +1009,31 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
753
1009
|
tx = tx.payToAddress({
|
|
754
1010
|
address: utxoToSeize.address,
|
|
755
1011
|
assets: outputAssets(utxoLovelace(utxoToSeize), remainingTokens.size > 0 ? remainingTokens : undefined),
|
|
756
|
-
|
|
1012
|
+
// The PAIRED continuation: `third_party` requires it to preserve the
|
|
1013
|
+
// input's ADDRESS, DATUM and REFERENCE SCRIPT exactly. Carry the input's
|
|
1014
|
+
// own datum rather than a fresh void one — they happen to be equal today
|
|
1015
|
+
// because every programmable output here is void-datumed, and that is a
|
|
1016
|
+
// coincidence rather than a guarantee.
|
|
1017
|
+
datum: new InlineDatum.InlineDatum({ data: getInlineDatum(utxoToSeize) ?? tokenDatum }),
|
|
757
1018
|
});
|
|
758
|
-
tx = tx.readFrom({ referenceInputs: [protocolParamsUtxo, registryUtxo] });
|
|
1019
|
+
tx = tx.readFrom({ referenceInputs: [protocolParamsUtxo, registryUtxo, thirdPartyRefUtxo] });
|
|
1020
|
+
if (process.env.DUMP_TX_STRUCTURE) {
|
|
1021
|
+
// eslint-disable-next-line no-console
|
|
1022
|
+
console.error("=== SEIZE attached scripts ===\n" +
|
|
1023
|
+
` programmableLogicBase=${ctx.standardScripts.programmableLogicBase.hash}\n` +
|
|
1024
|
+
` thirdParty =${ctx.standardScripts.thirdParty.hash}\n` +
|
|
1025
|
+
` issuerAdmin =${scripts.issuerAdmin.hash}\n` +
|
|
1026
|
+
"=== END SEIZE SCRIPTS ===");
|
|
1027
|
+
}
|
|
759
1028
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicBase.compiledCode) });
|
|
760
|
-
|
|
1029
|
+
// ⚠ `third_party` is NOT attached here. Its withdrawal already carries the
|
|
1030
|
+
// script witness, and attaching it again makes the duplicate extraneous —
|
|
1031
|
+
// the ledger rejects the whole transaction with code 3104. MEASURED: the
|
|
1032
|
+
// hash it names is third_party's, while the withdrawal for that same
|
|
1033
|
+
// credential is present and correct.
|
|
761
1034
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuerAdmin.compiledCode) });
|
|
762
1035
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(config.deployment.adminPkh) });
|
|
763
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
1036
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
764
1037
|
return { cbor, txHash, _signBuilder };
|
|
765
1038
|
},
|
|
766
1039
|
};
|