@meshsdk/transaction 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/package.json +35 -0
- package/src/index.ts +4 -0
- package/src/mesh-tx-builder/index.ts +259 -0
- package/src/mesh-tx-builder/tx-builder-core.ts +1180 -0
- package/src/scripts/forge.script.ts +63 -0
- package/src/scripts/index.ts +1 -0
- package/src/transaction/index.ts +609 -0
- package/src/transaction/transaction-v2.ts +81 -0
- package/src/utxo-selection/common.ts +113 -0
- package/src/utxo-selection/experimental.ts +137 -0
- package/src/utxo-selection/index.ts +69 -0
- package/src/utxo-selection/keepRelevant.ts +35 -0
- package/src/utxo-selection/largestFirst.ts +31 -0
- package/src/utxo-selection/largestFirstMultiAsset.ts +37 -0
- package/tsconfig.json +5 -0
- package/types/index.ts +0 -0
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meshsdk/transaction",
|
|
3
|
+
"version": "1.6.0-alpha.11",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build:mesh": "tsup src/index.ts --format esm,cjs --dts",
|
|
16
|
+
"dev": "tsup src/index.ts --format esm,cjs --watch --dts",
|
|
17
|
+
"lint": "eslint",
|
|
18
|
+
"clean": "rm -rf .turbo && rm -rf dist && rm -rf node_modules",
|
|
19
|
+
"format": "prettier --check . --ignore-path ../../.gitignore",
|
|
20
|
+
"build:docs": "typedoc src/index.ts --json ../../apps/docs/src/data/mesh-transactions.json"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@meshsdk/typescript-config": "*",
|
|
24
|
+
"@types/json-bigint": "^1.0.4",
|
|
25
|
+
"eslint": "^8.57.0",
|
|
26
|
+
"tsup": "^8.0.2",
|
|
27
|
+
"typescript": "^5.3.3"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@meshsdk/common": "*",
|
|
31
|
+
"@meshsdk/core-csl": "*",
|
|
32
|
+
"@meshsdk/core-cst": "*",
|
|
33
|
+
"json-bigint": "^1.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { MeshTxBuilderCore } from "./tx-builder-core";
|
|
2
|
+
import {
|
|
3
|
+
IMeshTxSerializer,
|
|
4
|
+
IFetcher,
|
|
5
|
+
ISubmitter,
|
|
6
|
+
IEvaluator,
|
|
7
|
+
MeshTxBuilderBody,
|
|
8
|
+
TxIn,
|
|
9
|
+
ScriptTxIn,
|
|
10
|
+
UTxO,
|
|
11
|
+
Protocol,
|
|
12
|
+
} from "@meshsdk/common";
|
|
13
|
+
// import { CardanoSDKSerializer } from "@meshsdk/core-cst";
|
|
14
|
+
import { CSLSerializer } from "@meshsdk/core-csl";
|
|
15
|
+
import { CardanoSDKSerializer } from "@meshsdk/core-cst";
|
|
16
|
+
|
|
17
|
+
export interface MeshTxBuilderOptions {
|
|
18
|
+
fetcher?: IFetcher;
|
|
19
|
+
submitter?: ISubmitter;
|
|
20
|
+
evaluator?: IEvaluator;
|
|
21
|
+
serializer?: IMeshTxSerializer;
|
|
22
|
+
isHydra?: boolean;
|
|
23
|
+
params?: Partial<Protocol>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class MeshTxBuilder extends MeshTxBuilderCore {
|
|
27
|
+
serializer: IMeshTxSerializer;
|
|
28
|
+
fetcher?: IFetcher;
|
|
29
|
+
submitter?: ISubmitter;
|
|
30
|
+
evaluator?: IEvaluator;
|
|
31
|
+
txHex: string = "";
|
|
32
|
+
private queriedTxHashes: Set<string> = new Set();
|
|
33
|
+
private queriedUTxOs: { [x: string]: UTxO[] } = {};
|
|
34
|
+
|
|
35
|
+
constructor({
|
|
36
|
+
serializer,
|
|
37
|
+
fetcher,
|
|
38
|
+
submitter,
|
|
39
|
+
evaluator,
|
|
40
|
+
params,
|
|
41
|
+
isHydra = false,
|
|
42
|
+
}: MeshTxBuilderOptions = {}) {
|
|
43
|
+
super();
|
|
44
|
+
if (serializer) {
|
|
45
|
+
this.serializer = serializer;
|
|
46
|
+
} else {
|
|
47
|
+
// this.serializer = new CardanoSDKSerializer();
|
|
48
|
+
this.serializer = new CSLSerializer();
|
|
49
|
+
}
|
|
50
|
+
if (fetcher) this.fetcher = fetcher;
|
|
51
|
+
if (submitter) this.submitter = submitter;
|
|
52
|
+
if (evaluator) this.evaluator = evaluator;
|
|
53
|
+
if (params) this.protocolParams(params);
|
|
54
|
+
if (isHydra)
|
|
55
|
+
this.protocolParams({
|
|
56
|
+
minFeeA: 0,
|
|
57
|
+
minFeeB: 0,
|
|
58
|
+
priceMem: 0,
|
|
59
|
+
priceStep: 0,
|
|
60
|
+
collateralPercent: 0,
|
|
61
|
+
coinsPerUtxoSize: 0,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* It builds the transaction and query the blockchain for missing information
|
|
67
|
+
* @param customizedTx The optional customized transaction body
|
|
68
|
+
* @returns The signed transaction in hex ready to submit / signed by client
|
|
69
|
+
*/
|
|
70
|
+
complete = async (customizedTx?: MeshTxBuilderBody) => {
|
|
71
|
+
if (customizedTx) {
|
|
72
|
+
this.meshTxBuilderBody = customizedTx;
|
|
73
|
+
} else {
|
|
74
|
+
this.queueAllLastItem();
|
|
75
|
+
}
|
|
76
|
+
this.removeDuplicateInputs();
|
|
77
|
+
|
|
78
|
+
// Checking if all inputs are complete
|
|
79
|
+
const { inputs, collaterals } = this.meshTxBuilderBody;
|
|
80
|
+
const incompleteTxIns = [...inputs, ...collaterals].filter(
|
|
81
|
+
(txIn) => !this.isInputComplete(txIn)
|
|
82
|
+
);
|
|
83
|
+
// Getting all missing utxo information
|
|
84
|
+
await this.queryAllTxInfo(incompleteTxIns);
|
|
85
|
+
// Completing all inputs
|
|
86
|
+
incompleteTxIns.forEach((txIn) => {
|
|
87
|
+
this.completeTxInformation(txIn);
|
|
88
|
+
});
|
|
89
|
+
this.addUtxosFromSelection();
|
|
90
|
+
|
|
91
|
+
let txHex = this.serializer.serializeTxBody(
|
|
92
|
+
this.meshTxBuilderBody,
|
|
93
|
+
this._protocolParams
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Evaluating the transaction
|
|
97
|
+
if (this.evaluator) {
|
|
98
|
+
const txEvaluation = await this.evaluator
|
|
99
|
+
.evaluateTx(txHex)
|
|
100
|
+
.catch((error) => {
|
|
101
|
+
throw Error(`Tx evaluation failed: ${error}`);
|
|
102
|
+
});
|
|
103
|
+
this.updateRedeemer(this.meshTxBuilderBody, txEvaluation);
|
|
104
|
+
txHex = this.serializer.serializeTxBody(
|
|
105
|
+
this.meshTxBuilderBody,
|
|
106
|
+
this._protocolParams
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
console.log(txHex);
|
|
110
|
+
|
|
111
|
+
this.txHex = txHex;
|
|
112
|
+
return txHex;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* It builds the transaction without dependencies
|
|
117
|
+
* @param customizedTx The optional customized transaction body
|
|
118
|
+
* @returns The signed transaction in hex ready to submit / signed by client
|
|
119
|
+
*/
|
|
120
|
+
completeSync = (customizedTx?: MeshTxBuilderBody) => {
|
|
121
|
+
if (customizedTx) {
|
|
122
|
+
this.meshTxBuilderBody = customizedTx;
|
|
123
|
+
} else {
|
|
124
|
+
this.queueAllLastItem();
|
|
125
|
+
}
|
|
126
|
+
return this.serializer.serializeTxBody(
|
|
127
|
+
this.meshTxBuilderBody,
|
|
128
|
+
this._protocolParams
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Complete the signing process
|
|
134
|
+
* @returns The signed transaction in hex
|
|
135
|
+
*/
|
|
136
|
+
completeSigning = () => {
|
|
137
|
+
const signedTxHex = this.serializer.addSigningKeys(
|
|
138
|
+
this.txHex,
|
|
139
|
+
this.meshTxBuilderBody.signingKey
|
|
140
|
+
);
|
|
141
|
+
this.txHex = signedTxHex;
|
|
142
|
+
return signedTxHex;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Submit transactions to the blockchain using the fetcher instance
|
|
147
|
+
* @param txHex The signed transaction in hex
|
|
148
|
+
* @returns
|
|
149
|
+
*/
|
|
150
|
+
submitTx = async (txHex: string): Promise<string | undefined> => {
|
|
151
|
+
const txHash = await this.submitter?.submitTx(txHex);
|
|
152
|
+
return txHash;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get the UTxO information from the blockchain
|
|
157
|
+
* @param TxHash The TxIn object that contains the txHash and txIndex, while missing amount and address information
|
|
158
|
+
*/
|
|
159
|
+
private getUTxOInfo = async (txHash: string): Promise<void> => {
|
|
160
|
+
let utxos: UTxO[] = [];
|
|
161
|
+
if (!this.queriedTxHashes.has(txHash)) {
|
|
162
|
+
this.queriedTxHashes.add(txHash);
|
|
163
|
+
utxos = (await this.fetcher?.fetchUTxOs(txHash)) || [];
|
|
164
|
+
this.queriedUTxOs[txHash] = utxos;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
private queryAllTxInfo = (incompleteTxIns: TxIn[]) => {
|
|
169
|
+
const queryUTxOPromises: Promise<void>[] = [];
|
|
170
|
+
if (incompleteTxIns.length > 0 && !this.fetcher)
|
|
171
|
+
throw Error(
|
|
172
|
+
"Transaction information is incomplete while no fetcher instance is provided"
|
|
173
|
+
);
|
|
174
|
+
for (let i = 0; i < incompleteTxIns.length; i++) {
|
|
175
|
+
const currentTxIn = incompleteTxIns[i]!;
|
|
176
|
+
if (!this.isInputInfoComplete(currentTxIn)) {
|
|
177
|
+
queryUTxOPromises.push(this.getUTxOInfo(currentTxIn.txIn.txHash));
|
|
178
|
+
}
|
|
179
|
+
if (
|
|
180
|
+
currentTxIn.type === "Script" &&
|
|
181
|
+
currentTxIn.scriptTxIn.scriptSource?.type === "Inline" &&
|
|
182
|
+
!this.isRefScriptInfoComplete(currentTxIn)
|
|
183
|
+
) {
|
|
184
|
+
queryUTxOPromises.push(
|
|
185
|
+
this.getUTxOInfo(currentTxIn.scriptTxIn.scriptSource.txHash)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return Promise.all(queryUTxOPromises);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
private completeTxInformation = (input: TxIn) => {
|
|
193
|
+
// Adding value and address information for inputs if missing
|
|
194
|
+
if (!this.isInputInfoComplete(input)) {
|
|
195
|
+
const utxos: UTxO[] = this.queriedUTxOs[input.txIn.txHash]!;
|
|
196
|
+
const utxo = utxos?.find(
|
|
197
|
+
(utxo) => utxo.input.outputIndex === input.txIn.txIndex
|
|
198
|
+
);
|
|
199
|
+
const amount = utxo?.output.amount;
|
|
200
|
+
const address = utxo?.output.address;
|
|
201
|
+
if (!amount || amount.length === 0)
|
|
202
|
+
throw Error(
|
|
203
|
+
`Couldn't find value information for ${input.txIn.txHash}#${input.txIn.txIndex}`
|
|
204
|
+
);
|
|
205
|
+
input.txIn.amount = amount;
|
|
206
|
+
if (input.type === "PubKey") {
|
|
207
|
+
if (!address || address === "")
|
|
208
|
+
throw Error(
|
|
209
|
+
`Couldn't find address information for ${input.txIn.txHash}#${input.txIn.txIndex}`
|
|
210
|
+
);
|
|
211
|
+
input.txIn.address = address;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Adding spendingScriptHash for script inputs' scriptSource if missing
|
|
215
|
+
if (
|
|
216
|
+
input.type === "Script" &&
|
|
217
|
+
input.scriptTxIn.scriptSource?.type == "Inline" &&
|
|
218
|
+
!this.isRefScriptInfoComplete(input)
|
|
219
|
+
) {
|
|
220
|
+
const scriptSource = input.scriptTxIn.scriptSource;
|
|
221
|
+
const refUtxos = this.queriedUTxOs[scriptSource.txHash]!;
|
|
222
|
+
const scriptRefUtxo = refUtxos.find(
|
|
223
|
+
(utxo) => utxo.input.outputIndex === scriptSource.txIndex
|
|
224
|
+
);
|
|
225
|
+
if (!scriptRefUtxo)
|
|
226
|
+
throw Error(
|
|
227
|
+
`Couldn't find script reference utxo for ${scriptSource.txHash}#${scriptSource.txIndex}`
|
|
228
|
+
);
|
|
229
|
+
scriptSource.scriptHash = scriptRefUtxo?.output.scriptHash;
|
|
230
|
+
// TODO: Calculate script size
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
private isInputComplete = (txIn: TxIn): boolean => {
|
|
235
|
+
if (txIn.type === "PubKey") return this.isInputInfoComplete(txIn);
|
|
236
|
+
if (txIn.type === "Script") {
|
|
237
|
+
return (
|
|
238
|
+
this.isInputInfoComplete(txIn) && this.isRefScriptInfoComplete(txIn)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
private isInputInfoComplete = (txIn: TxIn): boolean => {
|
|
245
|
+
const { amount, address } = txIn.txIn;
|
|
246
|
+
if (txIn.type === "PubKey" && (!amount || !address)) return false;
|
|
247
|
+
if (txIn.type === "Script") {
|
|
248
|
+
if (!amount) return false;
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
private isRefScriptInfoComplete = (scriptTxIn: ScriptTxIn): boolean => {
|
|
254
|
+
const { scriptSource } = scriptTxIn.scriptTxIn;
|
|
255
|
+
if (scriptSource?.type === "Inline" && !scriptSource?.scriptHash)
|
|
256
|
+
return false;
|
|
257
|
+
return true;
|
|
258
|
+
};
|
|
259
|
+
}
|