@lucid-evolution/provider 0.1.34 → 0.1.35
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/dist/index.cjs +676 -12
- package/dist/index.d.cts +54 -2
- package/dist/index.d.ts +54 -2
- package/dist/index.js +671 -6
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { applyDoubleCborEncoding } from "@lucid-evolution/utils";
|
|
|
14
14
|
// package.json
|
|
15
15
|
var package_default = {
|
|
16
16
|
name: "@lucid-evolution/provider",
|
|
17
|
-
version: "0.1.
|
|
17
|
+
version: "0.1.35",
|
|
18
18
|
description: "",
|
|
19
19
|
main: "./dist/index.js",
|
|
20
20
|
types: "./dist/index.d.ts",
|
|
@@ -51,6 +51,7 @@ var package_default = {
|
|
|
51
51
|
"@lucid-evolution/core-types": "workspace:*",
|
|
52
52
|
"@lucid-evolution/core-utils": "workspace:*",
|
|
53
53
|
"@lucid-evolution/utils": "workspace:*",
|
|
54
|
+
"@lucid-evolution/wallet": "workspace:*",
|
|
54
55
|
effect: "^3.5.6"
|
|
55
56
|
},
|
|
56
57
|
devDependencies: {
|
|
@@ -713,9 +714,671 @@ var kupmiosUtxosToUtxos = (kupoURL, utxos) => Effect.forEach(
|
|
|
713
714
|
{ concurrency: "unbounded" }
|
|
714
715
|
);
|
|
715
716
|
|
|
717
|
+
// src/emulator.ts
|
|
718
|
+
import {
|
|
719
|
+
generateSeedPhrase,
|
|
720
|
+
PROTOCOL_PARAMETERS_DEFAULT
|
|
721
|
+
} from "@lucid-evolution/utils";
|
|
722
|
+
import { coreToUtxo, getAddressDetails } from "@lucid-evolution/utils";
|
|
723
|
+
import { fromHex as fromHex2 } from "@lucid-evolution/core-utils";
|
|
724
|
+
import { walletFromSeed } from "@lucid-evolution/wallet";
|
|
725
|
+
function generateEmulatorAccount(assets) {
|
|
726
|
+
const seedPhrase = generateSeedPhrase();
|
|
727
|
+
return {
|
|
728
|
+
seedPhrase,
|
|
729
|
+
address: walletFromSeed(seedPhrase, {
|
|
730
|
+
addressType: "Base",
|
|
731
|
+
accountIndex: 0,
|
|
732
|
+
network: "Custom"
|
|
733
|
+
}).address,
|
|
734
|
+
assets
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
var Emulator = class {
|
|
738
|
+
ledger;
|
|
739
|
+
mempool = {};
|
|
740
|
+
/**
|
|
741
|
+
* Only stake key registrations/delegations and rewards are tracked.
|
|
742
|
+
* Other certificates are not tracked.
|
|
743
|
+
*/
|
|
744
|
+
chain = {};
|
|
745
|
+
blockHeight;
|
|
746
|
+
slot;
|
|
747
|
+
time;
|
|
748
|
+
protocolParameters;
|
|
749
|
+
datumTable = {};
|
|
750
|
+
constructor(accounts, protocolParameters = PROTOCOL_PARAMETERS_DEFAULT) {
|
|
751
|
+
const GENESIS_HASH = "00".repeat(32);
|
|
752
|
+
this.blockHeight = 0;
|
|
753
|
+
this.slot = 0;
|
|
754
|
+
this.time = Date.now();
|
|
755
|
+
this.ledger = {};
|
|
756
|
+
accounts.forEach(({ address, assets, outputData }, index) => {
|
|
757
|
+
if ([outputData?.hash, outputData?.asHash, outputData?.inline].filter(
|
|
758
|
+
(b) => b
|
|
759
|
+
).length > 1) {
|
|
760
|
+
throw new Error(
|
|
761
|
+
"Not allowed to set hash, asHash and inline at the same time."
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
this.ledger[GENESIS_HASH + index] = {
|
|
765
|
+
utxo: {
|
|
766
|
+
txHash: GENESIS_HASH,
|
|
767
|
+
outputIndex: index,
|
|
768
|
+
address,
|
|
769
|
+
assets,
|
|
770
|
+
datumHash: outputData?.asHash ? CML.hash_plutus_data(
|
|
771
|
+
CML.PlutusData.from_cbor_hex(outputData.asHash)
|
|
772
|
+
).to_hex() : outputData?.hash,
|
|
773
|
+
datum: outputData?.inline,
|
|
774
|
+
scriptRef: outputData?.scriptRef
|
|
775
|
+
},
|
|
776
|
+
spent: false
|
|
777
|
+
};
|
|
778
|
+
});
|
|
779
|
+
this.protocolParameters = protocolParameters;
|
|
780
|
+
}
|
|
781
|
+
now() {
|
|
782
|
+
return this.time;
|
|
783
|
+
}
|
|
784
|
+
awaitSlot(length = 1) {
|
|
785
|
+
this.slot += length;
|
|
786
|
+
this.time += length * 1e3;
|
|
787
|
+
const currentHeight = this.blockHeight;
|
|
788
|
+
this.blockHeight = Math.floor(this.slot / 20);
|
|
789
|
+
if (this.blockHeight > currentHeight) {
|
|
790
|
+
for (const [outRef, { utxo, spent }] of Object.entries(this.mempool)) {
|
|
791
|
+
this.ledger[outRef] = { utxo, spent };
|
|
792
|
+
}
|
|
793
|
+
for (const [outRef, { spent }] of Object.entries(this.ledger)) {
|
|
794
|
+
if (spent) delete this.ledger[outRef];
|
|
795
|
+
}
|
|
796
|
+
this.mempool = {};
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
awaitBlock(height = 1) {
|
|
800
|
+
this.blockHeight += height;
|
|
801
|
+
this.slot += height * 20;
|
|
802
|
+
this.time += height * 20 * 1e3;
|
|
803
|
+
for (const [outRef, { utxo, spent }] of Object.entries(this.mempool)) {
|
|
804
|
+
this.ledger[outRef] = { utxo, spent };
|
|
805
|
+
}
|
|
806
|
+
for (const [outRef, { spent }] of Object.entries(this.ledger)) {
|
|
807
|
+
if (spent) delete this.ledger[outRef];
|
|
808
|
+
}
|
|
809
|
+
this.mempool = {};
|
|
810
|
+
}
|
|
811
|
+
getUtxos(addressOrCredential) {
|
|
812
|
+
const utxos = Object.values(this.ledger).flatMap(({ utxo }) => {
|
|
813
|
+
if (typeof addressOrCredential === "string") {
|
|
814
|
+
return addressOrCredential === utxo.address ? utxo : [];
|
|
815
|
+
} else {
|
|
816
|
+
const { paymentCredential } = getAddressDetails(utxo.address);
|
|
817
|
+
return paymentCredential?.hash === addressOrCredential.hash ? utxo : [];
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
return Promise.resolve(utxos);
|
|
821
|
+
}
|
|
822
|
+
getProtocolParameters() {
|
|
823
|
+
return Promise.resolve(this.protocolParameters);
|
|
824
|
+
}
|
|
825
|
+
getDatum(datumHash) {
|
|
826
|
+
return Promise.resolve(this.datumTable[datumHash]);
|
|
827
|
+
}
|
|
828
|
+
getUtxosWithUnit(addressOrCredential, unit) {
|
|
829
|
+
const utxos = Object.values(this.ledger).flatMap(({ utxo }) => {
|
|
830
|
+
if (typeof addressOrCredential === "string") {
|
|
831
|
+
return addressOrCredential === utxo.address && utxo.assets[unit] > 0n ? utxo : [];
|
|
832
|
+
} else {
|
|
833
|
+
const { paymentCredential } = getAddressDetails(utxo.address);
|
|
834
|
+
return paymentCredential?.hash === addressOrCredential.hash && utxo.assets[unit] > 0n ? utxo : [];
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
return Promise.resolve(utxos);
|
|
838
|
+
}
|
|
839
|
+
getUtxosByOutRef(outRefs) {
|
|
840
|
+
return Promise.resolve(
|
|
841
|
+
outRefs.flatMap(
|
|
842
|
+
(outRef) => this.ledger[outRef.txHash + outRef.outputIndex]?.utxo || []
|
|
843
|
+
)
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
getUtxoByUnit(unit) {
|
|
847
|
+
const utxos = Object.values(this.ledger).flatMap(
|
|
848
|
+
({ utxo }) => utxo.assets[unit] > 0n ? utxo : []
|
|
849
|
+
);
|
|
850
|
+
if (utxos.length > 1) {
|
|
851
|
+
throw new Error("Unit needs to be an NFT or only held by one address.");
|
|
852
|
+
}
|
|
853
|
+
return Promise.resolve(utxos[0]);
|
|
854
|
+
}
|
|
855
|
+
getDelegation(rewardAddress) {
|
|
856
|
+
return Promise.resolve({
|
|
857
|
+
poolId: this.chain[rewardAddress]?.delegation?.poolId || null,
|
|
858
|
+
rewards: this.chain[rewardAddress]?.delegation?.rewards || 0n
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
awaitTx(txHash) {
|
|
862
|
+
if (this.mempool[txHash + 0]) {
|
|
863
|
+
this.awaitBlock();
|
|
864
|
+
return Promise.resolve(true);
|
|
865
|
+
}
|
|
866
|
+
return Promise.resolve(true);
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Emulates the behaviour of the reward distribution at epoch boundaries.
|
|
870
|
+
* Stake keys need to be registered and delegated like on a real chain in order to receive rewards.
|
|
871
|
+
*/
|
|
872
|
+
distributeRewards(rewards) {
|
|
873
|
+
for (const [
|
|
874
|
+
rewardAddress,
|
|
875
|
+
{ registeredStake, delegation }
|
|
876
|
+
] of Object.entries(this.chain)) {
|
|
877
|
+
if (registeredStake && delegation.poolId) {
|
|
878
|
+
this.chain[rewardAddress] = {
|
|
879
|
+
registeredStake,
|
|
880
|
+
delegation: {
|
|
881
|
+
poolId: delegation.poolId,
|
|
882
|
+
rewards: delegation.rewards += rewards
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
this.awaitBlock();
|
|
888
|
+
}
|
|
889
|
+
submitTx(tx) {
|
|
890
|
+
const desTx = CML.Transaction.from_cbor_hex(tx);
|
|
891
|
+
const body = desTx.body();
|
|
892
|
+
const witnesses = desTx.witness_set();
|
|
893
|
+
const datums = witnesses.plutus_datums();
|
|
894
|
+
const txHash = CML.hash_transaction(body).to_hex();
|
|
895
|
+
const lowerBound = body.validity_interval_start() ? parseInt(body.validity_interval_start().toString()) : null;
|
|
896
|
+
const upperBound = body.ttl() ? parseInt(body.ttl().toString()) : null;
|
|
897
|
+
if (Number.isInteger(lowerBound) && this.slot < lowerBound) {
|
|
898
|
+
throw new Error(
|
|
899
|
+
`Lower bound (${lowerBound}) not in slot range (${this.slot}).`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
if (Number.isInteger(upperBound) && this.slot > upperBound) {
|
|
903
|
+
throw new Error(
|
|
904
|
+
`Upper bound (${upperBound}) not in slot range (${this.slot}).`
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
const datumTable = (() => {
|
|
908
|
+
const table = {};
|
|
909
|
+
for (let i = 0; i < (datums?.len() || 0); i++) {
|
|
910
|
+
const datum = datums.get(i);
|
|
911
|
+
const datumHash = CML.hash_plutus_data(datum).to_hex();
|
|
912
|
+
table[datumHash] = datum.to_cbor_hex();
|
|
913
|
+
}
|
|
914
|
+
return table;
|
|
915
|
+
})();
|
|
916
|
+
const consumedHashes = /* @__PURE__ */ new Set();
|
|
917
|
+
const keyHashes = (() => {
|
|
918
|
+
const keyHashes2 = [];
|
|
919
|
+
for (let i = 0; i < (witnesses.vkeywitnesses()?.len() || 0); i++) {
|
|
920
|
+
const witness = witnesses.vkeywitnesses().get(i);
|
|
921
|
+
const publicKey = witness.vkey();
|
|
922
|
+
const keyHash = publicKey.hash().to_hex();
|
|
923
|
+
if (!publicKey.verify(fromHex2(txHash), witness.ed25519_signature())) {
|
|
924
|
+
throw new Error(`Invalid vkey witness. Key hash: ${keyHash}`);
|
|
925
|
+
}
|
|
926
|
+
keyHashes2.push(keyHash);
|
|
927
|
+
}
|
|
928
|
+
return keyHashes2;
|
|
929
|
+
})();
|
|
930
|
+
const edKeyHashes = CML.Ed25519KeyHashList.new();
|
|
931
|
+
keyHashes.forEach(
|
|
932
|
+
(keyHash) => edKeyHashes.add(CML.Ed25519KeyHash.from_hex(keyHash))
|
|
933
|
+
);
|
|
934
|
+
const nativeHashes = (() => {
|
|
935
|
+
const scriptHashes = [];
|
|
936
|
+
for (let i = 0; i < (witnesses.native_scripts()?.len() || 0); i++) {
|
|
937
|
+
const witness = witnesses.native_scripts().get(i);
|
|
938
|
+
const scriptHash = witness.hash().to_hex();
|
|
939
|
+
if (!witness.verify(
|
|
940
|
+
Number.isInteger(lowerBound) ? CML.BigInteger.from_str(lowerBound.toString()).to_js_value() : void 0,
|
|
941
|
+
Number.isInteger(upperBound) ? CML.BigInteger.from_str(upperBound.toString()).to_js_value() : void 0,
|
|
942
|
+
edKeyHashes
|
|
943
|
+
)) {
|
|
944
|
+
throw new Error(
|
|
945
|
+
`Invalid native script witness. Script hash: ${scriptHash}`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
for (let i2 = 0; i2 < witness.get_required_signers().len(); i2++) {
|
|
949
|
+
const keyHash = witness.get_required_signers().get(i2).to_hex();
|
|
950
|
+
consumedHashes.add(keyHash);
|
|
951
|
+
}
|
|
952
|
+
scriptHashes.push(scriptHash);
|
|
953
|
+
}
|
|
954
|
+
return scriptHashes;
|
|
955
|
+
})();
|
|
956
|
+
const nativeHashesOptional = {};
|
|
957
|
+
const plutusHashesOptional = [];
|
|
958
|
+
const plutusHashes = (() => {
|
|
959
|
+
const scriptHashes = [];
|
|
960
|
+
for (let i = 0; i < (witnesses.plutus_v1_scripts()?.len() || 0); i++) {
|
|
961
|
+
const script = witnesses.plutus_v1_scripts().get(i);
|
|
962
|
+
const scriptHash = script.hash().to_hex();
|
|
963
|
+
scriptHashes.push(scriptHash);
|
|
964
|
+
}
|
|
965
|
+
for (let i = 0; i < (witnesses.plutus_v2_scripts()?.len() || 0); i++) {
|
|
966
|
+
const script = witnesses.plutus_v2_scripts().get(i);
|
|
967
|
+
const scriptHash = script.hash().to_hex();
|
|
968
|
+
scriptHashes.push(scriptHash);
|
|
969
|
+
}
|
|
970
|
+
return scriptHashes;
|
|
971
|
+
})();
|
|
972
|
+
const inputs = body.inputs();
|
|
973
|
+
const resolvedInputs = [];
|
|
974
|
+
for (let i = 0; i < inputs.len(); i++) {
|
|
975
|
+
const input = inputs.get(i);
|
|
976
|
+
const outRef = input.transaction_id().to_hex() + input.index().toString();
|
|
977
|
+
const entryLedger = this.ledger[outRef];
|
|
978
|
+
const { entry, type } = !entryLedger ? { entry: this.mempool[outRef], type: "Mempool" } : { entry: entryLedger, type: "Ledger" };
|
|
979
|
+
if (!entry || entry.spent) {
|
|
980
|
+
throw new Error(
|
|
981
|
+
`Could not spend UTxO: ${JSON.stringify({
|
|
982
|
+
txHash: entry?.utxo.txHash,
|
|
983
|
+
outputIndex: entry?.utxo.outputIndex
|
|
984
|
+
})}
|
|
985
|
+
It does not exist or was already spent.`
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
const scriptRef = entry.utxo.scriptRef;
|
|
989
|
+
if (scriptRef) {
|
|
990
|
+
switch (scriptRef.type) {
|
|
991
|
+
case "Native": {
|
|
992
|
+
const script = CML.NativeScript.from_cbor_bytes(
|
|
993
|
+
fromHex2(scriptRef.script)
|
|
994
|
+
);
|
|
995
|
+
nativeHashesOptional[script.hash().to_hex()] = script;
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
998
|
+
case "PlutusV1": {
|
|
999
|
+
const script = CML.PlutusScript.from_v1(
|
|
1000
|
+
CML.PlutusV1Script.from_cbor_bytes(fromHex2(scriptRef.script))
|
|
1001
|
+
);
|
|
1002
|
+
plutusHashesOptional.push(script.hash().to_hex());
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
case "PlutusV2": {
|
|
1006
|
+
const script = CML.PlutusScript.from_v2(
|
|
1007
|
+
CML.PlutusV2Script.from_cbor_bytes(fromHex2(scriptRef.script))
|
|
1008
|
+
);
|
|
1009
|
+
plutusHashesOptional.push(script.hash().to_hex());
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (entry.utxo.datumHash) consumedHashes.add(entry.utxo.datumHash);
|
|
1015
|
+
resolvedInputs.push({ entry, type });
|
|
1016
|
+
}
|
|
1017
|
+
for (let i = 0; i < (body.reference_inputs()?.len() || 0); i++) {
|
|
1018
|
+
const input = body.reference_inputs().get(i);
|
|
1019
|
+
const outRef = input.transaction_id().to_hex() + input.index().toString();
|
|
1020
|
+
const entry = this.ledger[outRef] || this.mempool[outRef];
|
|
1021
|
+
if (!entry || entry.spent) {
|
|
1022
|
+
throw new Error(
|
|
1023
|
+
`Could not read UTxO: ${JSON.stringify({
|
|
1024
|
+
txHash: entry?.utxo.txHash,
|
|
1025
|
+
outputIndex: entry?.utxo.outputIndex
|
|
1026
|
+
})}
|
|
1027
|
+
It does not exist or was already spent.`
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
const scriptRef = entry.utxo.scriptRef;
|
|
1031
|
+
if (scriptRef) {
|
|
1032
|
+
switch (scriptRef.type) {
|
|
1033
|
+
case "Native": {
|
|
1034
|
+
const script = CML.NativeScript.from_cbor_bytes(
|
|
1035
|
+
fromHex2(scriptRef.script)
|
|
1036
|
+
);
|
|
1037
|
+
nativeHashesOptional[script.hash().to_hex()] = script;
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
case "PlutusV1": {
|
|
1041
|
+
const script = CML.PlutusScript.from_v1(
|
|
1042
|
+
CML.PlutusV1Script.from_cbor_bytes(fromHex2(scriptRef.script))
|
|
1043
|
+
);
|
|
1044
|
+
plutusHashesOptional.push(script.hash().to_hex());
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
case "PlutusV2": {
|
|
1048
|
+
const script = CML.PlutusScript.from_v2(
|
|
1049
|
+
CML.PlutusV2Script.from_cbor_bytes(fromHex2(scriptRef.script))
|
|
1050
|
+
);
|
|
1051
|
+
plutusHashesOptional.push(script.hash().to_hex());
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (entry.utxo.datumHash) consumedHashes.add(entry.utxo.datumHash);
|
|
1057
|
+
}
|
|
1058
|
+
const redeemers = (() => {
|
|
1059
|
+
const tagMap = {
|
|
1060
|
+
0: "Spend",
|
|
1061
|
+
1: "Mint",
|
|
1062
|
+
2: "Cert",
|
|
1063
|
+
3: "Reward"
|
|
1064
|
+
};
|
|
1065
|
+
const collected = [];
|
|
1066
|
+
for (let i = 0; i < (witnesses.redeemers()?.to_js_value().len() || 0); i++) {
|
|
1067
|
+
const redeemer = witnesses.redeemers().to_js_value().get(i);
|
|
1068
|
+
collected.push({
|
|
1069
|
+
tag: tagMap[redeemer.tag().kind()],
|
|
1070
|
+
index: parseInt(redeemer.index().to_str())
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
return collected;
|
|
1074
|
+
})();
|
|
1075
|
+
function checkAndConsumeHash(credential, tag, index) {
|
|
1076
|
+
switch (credential.type) {
|
|
1077
|
+
case "Key": {
|
|
1078
|
+
if (!keyHashes.includes(credential.hash)) {
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
`Missing vkey witness. Key hash: ${credential.hash}`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
consumedHashes.add(credential.hash);
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
case "Script": {
|
|
1087
|
+
if (nativeHashes.includes(credential.hash)) {
|
|
1088
|
+
consumedHashes.add(credential.hash);
|
|
1089
|
+
break;
|
|
1090
|
+
} else if (nativeHashesOptional[credential.hash]) {
|
|
1091
|
+
if (!nativeHashesOptional[credential.hash].verify(
|
|
1092
|
+
Number.isInteger(lowerBound) ? CML.BigInteger.from_str(
|
|
1093
|
+
lowerBound.toString()
|
|
1094
|
+
).to_js_value() : void 0,
|
|
1095
|
+
Number.isInteger(upperBound) ? CML.BigInteger.from_str(
|
|
1096
|
+
upperBound.toString()
|
|
1097
|
+
).to_js_value() : void 0,
|
|
1098
|
+
edKeyHashes
|
|
1099
|
+
)) {
|
|
1100
|
+
throw new Error(
|
|
1101
|
+
`Invalid native script witness. Script hash: ${credential.hash}`
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
break;
|
|
1105
|
+
} else if (plutusHashes.includes(credential.hash) || plutusHashesOptional.includes(credential.hash)) {
|
|
1106
|
+
if (redeemers.find(
|
|
1107
|
+
(redeemer) => redeemer.tag === tag && redeemer.index === index
|
|
1108
|
+
)) {
|
|
1109
|
+
consumedHashes.add(credential.hash);
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
throw new Error(
|
|
1114
|
+
`Missing script witness. Script hash: ${credential.hash}`
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
for (let i = 0; i < (body.collateral_inputs()?.len() || 0); i++) {
|
|
1120
|
+
const input = body.collateral_inputs().get(i);
|
|
1121
|
+
const outRef = input.transaction_id().to_hex() + input.index().toString();
|
|
1122
|
+
const entry = this.ledger[outRef] || this.mempool[outRef];
|
|
1123
|
+
if (!entry || entry.spent) {
|
|
1124
|
+
throw new Error(
|
|
1125
|
+
`Could not read UTxO: ${JSON.stringify({
|
|
1126
|
+
txHash: entry?.utxo.txHash,
|
|
1127
|
+
outputIndex: entry?.utxo.outputIndex
|
|
1128
|
+
})}
|
|
1129
|
+
It does not exist or was already spent.`
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
const { paymentCredential } = getAddressDetails(entry.utxo.address);
|
|
1133
|
+
if (paymentCredential?.type === "Script") {
|
|
1134
|
+
throw new Error("Collateral inputs can only contain vkeys.");
|
|
1135
|
+
}
|
|
1136
|
+
checkAndConsumeHash(paymentCredential, null, null);
|
|
1137
|
+
}
|
|
1138
|
+
for (let i = 0; i < (body.required_signers()?.len() || 0); i++) {
|
|
1139
|
+
const signer = body.required_signers().get(i);
|
|
1140
|
+
checkAndConsumeHash({ type: "Key", hash: signer.to_hex() }, null, null);
|
|
1141
|
+
}
|
|
1142
|
+
for (let index = 0; index < (body.mint()?.keys().len() || 0); index++) {
|
|
1143
|
+
const policyId = body.mint().keys().get(index).to_hex();
|
|
1144
|
+
checkAndConsumeHash({ type: "Script", hash: policyId }, "Mint", index);
|
|
1145
|
+
}
|
|
1146
|
+
const withdrawalRequests = [];
|
|
1147
|
+
for (let index = 0; index < (body.withdrawals()?.keys().len() || 0); index++) {
|
|
1148
|
+
const rawAddress = body.withdrawals().keys().get(index);
|
|
1149
|
+
const withdrawal = BigInt(
|
|
1150
|
+
body.withdrawals().get(rawAddress).toString()
|
|
1151
|
+
);
|
|
1152
|
+
const rewardAddress = rawAddress.to_address().to_bech32(void 0);
|
|
1153
|
+
const { stakeCredential } = getAddressDetails(rewardAddress);
|
|
1154
|
+
checkAndConsumeHash(stakeCredential, "Reward", index);
|
|
1155
|
+
if (this.chain[rewardAddress]?.delegation.rewards !== withdrawal) {
|
|
1156
|
+
throw new Error(
|
|
1157
|
+
"Withdrawal amount doesn't match actual reward balance."
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
withdrawalRequests.push({ rewardAddress, withdrawal });
|
|
1161
|
+
}
|
|
1162
|
+
const certRequests = [];
|
|
1163
|
+
for (let index = 0; index < (body.certs()?.len() || 0); index++) {
|
|
1164
|
+
const cert = body.certs().get(index);
|
|
1165
|
+
switch (cert.kind()) {
|
|
1166
|
+
case 0: {
|
|
1167
|
+
const registration = cert.as_stake_registration();
|
|
1168
|
+
const rewardAddress = CML.RewardAddress.new(
|
|
1169
|
+
CML.NetworkInfo.testnet().network_id(),
|
|
1170
|
+
registration.stake_credential()
|
|
1171
|
+
).to_address().to_bech32(void 0);
|
|
1172
|
+
if (this.chain[rewardAddress]?.registeredStake) {
|
|
1173
|
+
throw new Error(
|
|
1174
|
+
`Stake key is already registered. Reward address: ${rewardAddress}`
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
certRequests.push({ type: "Registration", rewardAddress });
|
|
1178
|
+
break;
|
|
1179
|
+
}
|
|
1180
|
+
case 1: {
|
|
1181
|
+
const deregistration = cert.as_stake_deregistration();
|
|
1182
|
+
const rewardAddress = CML.RewardAddress.new(
|
|
1183
|
+
CML.NetworkInfo.testnet().network_id(),
|
|
1184
|
+
deregistration.stake_credential()
|
|
1185
|
+
).to_address().to_bech32(void 0);
|
|
1186
|
+
const { stakeCredential } = getAddressDetails(rewardAddress);
|
|
1187
|
+
checkAndConsumeHash(stakeCredential, "Cert", index);
|
|
1188
|
+
if (!this.chain[rewardAddress]?.registeredStake) {
|
|
1189
|
+
throw new Error(
|
|
1190
|
+
`Stake key is already deregistered. Reward address: ${rewardAddress}`
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
certRequests.push({ type: "Deregistration", rewardAddress });
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
case 2: {
|
|
1197
|
+
const delegation = cert.as_stake_delegation();
|
|
1198
|
+
const rewardAddress = CML.RewardAddress.new(
|
|
1199
|
+
CML.NetworkInfo.testnet().network_id(),
|
|
1200
|
+
delegation.stake_credential()
|
|
1201
|
+
).to_address().to_bech32(void 0);
|
|
1202
|
+
const poolId = delegation.pool().to_bech32("pool");
|
|
1203
|
+
const { stakeCredential } = getAddressDetails(rewardAddress);
|
|
1204
|
+
checkAndConsumeHash(stakeCredential, "Cert", index);
|
|
1205
|
+
if (!this.chain[rewardAddress]?.registeredStake && !certRequests.find(
|
|
1206
|
+
(request) => request.type === "Registration" && request.rewardAddress === rewardAddress
|
|
1207
|
+
)) {
|
|
1208
|
+
throw new Error(
|
|
1209
|
+
`Stake key is not registered. Reward address: ${rewardAddress}`
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
certRequests.push({ type: "Delegation", rewardAddress, poolId });
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
resolvedInputs.forEach(({ entry: { utxo } }, index) => {
|
|
1218
|
+
const { paymentCredential } = getAddressDetails(utxo.address);
|
|
1219
|
+
checkAndConsumeHash(paymentCredential, "Spend", index);
|
|
1220
|
+
});
|
|
1221
|
+
const outputs = (() => {
|
|
1222
|
+
const collected = [];
|
|
1223
|
+
for (let i = 0; i < body.outputs().len(); i++) {
|
|
1224
|
+
const output = body.outputs().get(i);
|
|
1225
|
+
const unspentOutput = CML.TransactionUnspentOutput.new(
|
|
1226
|
+
CML.TransactionInput.new(
|
|
1227
|
+
CML.TransactionHash.from_hex(txHash),
|
|
1228
|
+
CML.BigInteger.from_str(i.toString()).to_js_value()
|
|
1229
|
+
),
|
|
1230
|
+
output
|
|
1231
|
+
);
|
|
1232
|
+
const utxo = coreToUtxo(unspentOutput);
|
|
1233
|
+
if (utxo.datumHash) consumedHashes.add(utxo.datumHash);
|
|
1234
|
+
collected.push({
|
|
1235
|
+
utxo,
|
|
1236
|
+
spent: false
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
return collected;
|
|
1240
|
+
})();
|
|
1241
|
+
const [extraKeyHash] = keyHashes.filter(
|
|
1242
|
+
(keyHash) => !consumedHashes.has(keyHash)
|
|
1243
|
+
);
|
|
1244
|
+
if (extraKeyHash) {
|
|
1245
|
+
throw new Error(`Extraneous vkey witness. Key hash: ${extraKeyHash}`);
|
|
1246
|
+
}
|
|
1247
|
+
const [extraNativeHash] = nativeHashes.filter(
|
|
1248
|
+
(scriptHash) => !consumedHashes.has(scriptHash)
|
|
1249
|
+
);
|
|
1250
|
+
if (extraNativeHash) {
|
|
1251
|
+
throw new Error(
|
|
1252
|
+
`Extraneous native script. Script hash: ${extraNativeHash}`
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
const [extraPlutusHash] = plutusHashes.filter(
|
|
1256
|
+
(scriptHash) => !consumedHashes.has(scriptHash)
|
|
1257
|
+
);
|
|
1258
|
+
if (extraPlutusHash) {
|
|
1259
|
+
throw new Error(
|
|
1260
|
+
`Extraneous plutus script. Script hash: ${extraPlutusHash}`
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
const [extraDatumHash] = Object.keys(datumTable).filter(
|
|
1264
|
+
(datumHash) => !consumedHashes.has(datumHash)
|
|
1265
|
+
);
|
|
1266
|
+
if (extraDatumHash) {
|
|
1267
|
+
throw new Error(`Extraneous plutus data. Datum hash: ${extraDatumHash}`);
|
|
1268
|
+
}
|
|
1269
|
+
resolvedInputs.forEach(({ entry, type }) => {
|
|
1270
|
+
const outRef = entry.utxo.txHash + entry.utxo.outputIndex;
|
|
1271
|
+
entry.spent = true;
|
|
1272
|
+
if (type === "Ledger") this.ledger[outRef] = entry;
|
|
1273
|
+
else if (type === "Mempool") this.mempool[outRef] = entry;
|
|
1274
|
+
});
|
|
1275
|
+
withdrawalRequests.forEach(({ rewardAddress, withdrawal }) => {
|
|
1276
|
+
this.chain[rewardAddress].delegation.rewards -= withdrawal;
|
|
1277
|
+
});
|
|
1278
|
+
certRequests.forEach(({ type, rewardAddress, poolId }) => {
|
|
1279
|
+
switch (type) {
|
|
1280
|
+
case "Registration": {
|
|
1281
|
+
if (this.chain[rewardAddress]) {
|
|
1282
|
+
this.chain[rewardAddress].registeredStake = true;
|
|
1283
|
+
} else {
|
|
1284
|
+
this.chain[rewardAddress] = {
|
|
1285
|
+
registeredStake: true,
|
|
1286
|
+
delegation: { poolId: null, rewards: 0n }
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
break;
|
|
1290
|
+
}
|
|
1291
|
+
case "Deregistration": {
|
|
1292
|
+
this.chain[rewardAddress].registeredStake = false;
|
|
1293
|
+
this.chain[rewardAddress].delegation.poolId = null;
|
|
1294
|
+
break;
|
|
1295
|
+
}
|
|
1296
|
+
case "Delegation": {
|
|
1297
|
+
this.chain[rewardAddress].delegation.poolId = poolId;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
outputs.forEach(({ utxo, spent }) => {
|
|
1302
|
+
this.mempool[utxo.txHash + utxo.outputIndex] = {
|
|
1303
|
+
utxo,
|
|
1304
|
+
spent
|
|
1305
|
+
};
|
|
1306
|
+
});
|
|
1307
|
+
for (const [datumHash, datum] of Object.entries(datumTable)) {
|
|
1308
|
+
this.datumTable[datumHash] = datum;
|
|
1309
|
+
}
|
|
1310
|
+
return Promise.resolve(txHash);
|
|
1311
|
+
}
|
|
1312
|
+
log() {
|
|
1313
|
+
function getRandomColor(unit) {
|
|
1314
|
+
const seed = unit === "lovelace" ? "1" : unit;
|
|
1315
|
+
let num = 0;
|
|
1316
|
+
for (let i = 0; i < seed.length; i++) {
|
|
1317
|
+
num += seed.charCodeAt(i);
|
|
1318
|
+
}
|
|
1319
|
+
const r = num * 123 % 256;
|
|
1320
|
+
const g = num * 321 % 256;
|
|
1321
|
+
const b = num * 213 % 256;
|
|
1322
|
+
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
|
1323
|
+
}
|
|
1324
|
+
const totalBalances = {};
|
|
1325
|
+
const balances = {};
|
|
1326
|
+
for (const { utxo } of Object.values(this.ledger)) {
|
|
1327
|
+
for (const [unit, quantity] of Object.entries(utxo.assets)) {
|
|
1328
|
+
if (!balances[utxo.address]) {
|
|
1329
|
+
balances[utxo.address] = { [unit]: quantity };
|
|
1330
|
+
} else if (!balances[utxo.address]?.[unit]) {
|
|
1331
|
+
balances[utxo.address][unit] = quantity;
|
|
1332
|
+
} else {
|
|
1333
|
+
balances[utxo.address][unit] += quantity;
|
|
1334
|
+
}
|
|
1335
|
+
if (!totalBalances[unit]) {
|
|
1336
|
+
totalBalances[unit] = quantity;
|
|
1337
|
+
} else {
|
|
1338
|
+
totalBalances[unit] += quantity;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
console.log("\n%cBlockchain state", "color:purple");
|
|
1343
|
+
console.log(
|
|
1344
|
+
`
|
|
1345
|
+
Block height: %c${this.blockHeight}%c
|
|
1346
|
+
Slot: %c${this.slot}%c
|
|
1347
|
+
Unix time: %c${this.time}
|
|
1348
|
+
`,
|
|
1349
|
+
"color:yellow",
|
|
1350
|
+
"color:white",
|
|
1351
|
+
"color:yellow",
|
|
1352
|
+
"color:white",
|
|
1353
|
+
"color:yellow"
|
|
1354
|
+
);
|
|
1355
|
+
console.log("\n");
|
|
1356
|
+
for (const [address, assets] of Object.entries(balances)) {
|
|
1357
|
+
console.log(`Address: %c${address}`, "color:blue", "\n");
|
|
1358
|
+
for (const [unit, quantity] of Object.entries(assets)) {
|
|
1359
|
+
const barLength = Math.max(
|
|
1360
|
+
Math.floor(60 * (Number(quantity) / Number(totalBalances[unit]))),
|
|
1361
|
+
1
|
|
1362
|
+
);
|
|
1363
|
+
console.log(
|
|
1364
|
+
`%c${"\u2586".repeat(barLength) + " ".repeat(60 - barLength)}`,
|
|
1365
|
+
`color: ${getRandomColor(unit)}`,
|
|
1366
|
+
"",
|
|
1367
|
+
`${unit}:`,
|
|
1368
|
+
quantity,
|
|
1369
|
+
""
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
console.log(`
|
|
1373
|
+
${"\u2581".repeat(60)}
|
|
1374
|
+
`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
|
|
716
1379
|
// src/maestro.ts
|
|
717
1380
|
import { applyDoubleCborEncoding as applyDoubleCborEncoding2 } from "@lucid-evolution/utils";
|
|
718
|
-
import { fromHex as
|
|
1381
|
+
import { fromHex as fromHex3 } from "@lucid-evolution/core-utils";
|
|
719
1382
|
var Maestro = class {
|
|
720
1383
|
url;
|
|
721
1384
|
apiKey;
|
|
@@ -914,7 +1577,7 @@ var Maestro = class {
|
|
|
914
1577
|
Accept: "text/plain",
|
|
915
1578
|
...this.commonHeaders()
|
|
916
1579
|
},
|
|
917
|
-
body:
|
|
1580
|
+
body: fromHex3(tx)
|
|
918
1581
|
});
|
|
919
1582
|
const result = await response.text();
|
|
920
1583
|
if (!response.ok) {
|
|
@@ -978,7 +1641,7 @@ var lucid2 = package_default.version;
|
|
|
978
1641
|
|
|
979
1642
|
// src/koios/koios.ts
|
|
980
1643
|
import * as S4 from "@effect/schema/Schema";
|
|
981
|
-
import { fromHex as
|
|
1644
|
+
import { fromHex as fromHex4 } from "@lucid-evolution/core-utils";
|
|
982
1645
|
import { applyDoubleCborEncoding as applyDoubleCborEncoding3, fromUnit as fromUnit2 } from "@lucid-evolution/utils";
|
|
983
1646
|
|
|
984
1647
|
// src/koios/schema.ts
|
|
@@ -1429,7 +2092,7 @@ var Koios = class {
|
|
|
1429
2092
|
"Content-Type": "application/cbor"
|
|
1430
2093
|
},
|
|
1431
2094
|
method: "POST",
|
|
1432
|
-
body:
|
|
2095
|
+
body: fromHex4(tx)
|
|
1433
2096
|
}).then((res) => res.json());
|
|
1434
2097
|
const TxHashSchema = S4.String;
|
|
1435
2098
|
const isTxHash = S4.is(TxHashSchema);
|
|
@@ -1465,10 +2128,12 @@ var toUTxO = (koiosUTxO, address) => ({
|
|
|
1465
2128
|
});
|
|
1466
2129
|
export {
|
|
1467
2130
|
Blockfrost,
|
|
2131
|
+
Emulator,
|
|
1468
2132
|
fetch_exports as FetchProvider,
|
|
1469
2133
|
Koios,
|
|
1470
2134
|
Kupmios,
|
|
1471
2135
|
KupmiosError,
|
|
1472
2136
|
Maestro,
|
|
1473
|
-
datumJsonToCbor
|
|
2137
|
+
datumJsonToCbor,
|
|
2138
|
+
generateEmulatorAccount
|
|
1474
2139
|
};
|