Hi guys,
I try using
GitHub - MartifyLabs/Martify: NFT Marketplace on the Cardano Blockchain. Powered by Plutus Smart Contracts
when I interact with the smart contract using CLI everything works as expected.
I was able to lock token - offer others to buy it and later I was able to cancel the offer and NFT was back in my wallet.
locking token:
cardano-cli transaction build \
--alonzo-era \
--$testnet \
--tx-in 1faeef0ce92b02ff90348b4f86f54ac02d6ad6b0270602573130a872dd37af67#0 \
--tx-in 1faeef0ce92b02ff90348b4f86f54ac02d6ad6b0270602573130a872dd37af67#1 \
--tx-out "$(cat script.addr) + 1724100 lovelace + 1 $(cat policy/policyID).AdamNFT" \
--tx-out-datum-hash $(cat dhash) \
--change-address $(cat payment.addr) \
--protocol-params-file protocol.json \
--out-file tx.02
unlocking token:
cardano-cli transaction build \
--alonzo-era \
--$testnet \
--tx-in d10fcf51e2bc75dd499ff747bfb8f4112e75ced61c598711a00554d08dc930ed#0 \
--tx-in d10fcf51e2bc75dd499ff747bfb8f4112e75ced61c598711a00554d08dc930ed#1 \
--tx-in-script-file market.plutus \
--tx-in-datum-file datum-f9160cfb676909cbd6a6a9bdd7868abd1e3b988fd32ceee05b95246d-AdamNFT.json \
--tx-in-redeemer-file close.json \
--required-signer payment.skey \
--tx-in-collateral d10fcf51e2bc75dd499ff747bfb8f4112e75ced61c598711a00554d08dc930ed#0 \
--tx-out "$(cat payment.addr) + 1724100 lovelace + 1 f9160cfb676909cbd6a6a9bdd7868abd1e3b988fd32ceee05b95246d.AdamNFT" \
--change-address $(cat payment.addr) \
--protocol-params-file protocol.json \
--out-file unlock-body.02
After signing and submitting transaction everything works as expected.
When I try to do lock and cancel using cardano-serialization-lib and hosky code
HOSKYSWAP.Validator/hosky-swap-ui-prototype at master · ADAPhilippines/HOSKYSWAP.Validator · GitHub
import { Value, TransactionUnspentOutput, BigNum, Vkeywitnesses, PlutusData, Ed25519KeyHash, BaseAddress, Redeemer, PlutusScripts, TransactionBuilder, Address, TransactionOutput } from './custom_modules/@emurgo/cardano-serialization-lib-browser';
import CardanoLoader from './CardanoLoader';
import CardanoProtocolParameters from './Types/CardanoProtocolParam';
import { contractCbor } from "./contract";
import { languageViews } from './languageViews'; import { CoinSelection, setCardanoSerializationLib as setCoinSelectionCardanoSerializationLib } from './coinSelection';
import { PlutusDataObject } from './Types/PlutusDataObject';
import { PlutusField, PlutusFieldType } from './Types/PlutusField';
const BLOCKFROST_PROJECT_ID = "";
const CardanoSerializationLib = async () => {
return await import("./custom_modules/@emurgo/cardano-serialization-lib-browser/cardano_serialization_lib");
}
const GetWalletAddressAsync = async () => (await window.cardano.getUsedAddresses())[0];
const toHex = (bytes: Uint8Array) => Buffer.from(bytes).toString("hex");
const fromHex = (hex: string) => Buffer.from(hex, "hex");
const toByteArray = (name: string) => Buffer.from(name, "utf8");
const toBigNum = async (value: any) => {
let Cardano = await CardanoSerializationLib();
return Cardano?.BigNum.from_str(value.toString()) as BigNum;
}
let btnOffer: HTMLButtonElement;
let btnCancel: HTMLButtonElement;
const policyId = "26e3d767b97e9b61f3bfc294b84b67f6a569aae89666f239eceb7466"
const askingPrice = "5000000";
//const assetName = "StressTestCol04294"
// const scriptTxId = "b77f268a3c55f4919ef1d34be6139ae70c07efe024e3f2d5135b839f14e9fc04"
// const scriptTxIndex = 0
// const amountToTransfer = "2000000"
const assetName = "StressTestCol04256"
const scriptTxId = "1cf640e2672a581a780636c546423f9b10ddb3661d6eb065994e0619a062af24"
const scriptTxIndex = 0
const amountToTransfer = "3000000"
const assetNameInHex = toHex(toByteArray(assetName));
async function Main() {
await CardanoLoader.LoadAsync();
btnOffer = document.getElementById("btnOffer") as HTMLButtonElement;
btnCancel = document.getElementById("btnCancel") as HTMLButtonElement;
btnOffer.addEventListener("click", OfferToken);
btnCancel.addEventListener("click", CancelOffer);
}
async function OfferToken() {
await BuildOfferTxAsync();
}
async function CancelOffer() {
await CancelOfferTxAsync();
}
async function BuildOfferTxAsync() {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
setCoinSelectionCardanoSerializationLib(Cardano);
const protocolParameters = await GetProtocolProtocolParamsAsync();
CoinSelection.setProtocolParameters(
protocolParameters.min_utxo.toString(),
protocolParameters.min_fee_a.toString(),
protocolParameters.min_fee_b.toString(),
protocolParameters.max_tx_size.toString()
);
if (!await window.cardano.isEnabled()) await window.cardano.enable();
const txBuilder = await CreateTransactionBuilderAsync() as TransactionBuilder;
const selfAddress = Cardano.Address.from_bytes(fromHex(await GetWalletAddressAsync()));
const baseAddress = Cardano.BaseAddress.from_address(selfAddress) as BaseAddress;
const pkh = toHex(baseAddress.payment_cred().to_keyhash()?.to_bytes() as Uint8Array);
const transactionWitnessSet = Cardano.TransactionWitnessSet.new();
const hoskyDatumObject = OfferDatum(pkh, askingPrice, policyId, assetName) as PlutusDataObject;
const datumHash = Cardano.hash_plutus_data(await ToPlutusData(hoskyDatumObject) as PlutusData);
console.log("datumHash", toHex(datumHash.to_bytes()));
console.log("pkh", pkh);
console.log("value", (await GetContractOutput())?.coin().to_str());
const contractOutput = Cardano.TransactionOutput.new(
await ContractAddress() as Address,
await GetContractOutput() as Value
);
contractOutput.set_data_hash(datumHash);
const transactionOutputs = Cardano.TransactionOutputs.new();
transactionOutputs.add(contractOutput);
const utxos = await window.cardano.getUtxos();
const csResult = CoinSelection.randomImprove(
utxos.map(utxo => Cardano?.TransactionUnspentOutput.from_bytes(fromHex(utxo)) as TransactionUnspentOutput),
transactionOutputs,
8
);
csResult.inputs.forEach((utxo) => {
txBuilder.add_input(
utxo.output().address(),
utxo.input(),
utxo.output().amount()
);
});
txBuilder.add_output(contractOutput);
txBuilder.add_change_if_needed(selfAddress);
const txBody = txBuilder.build();
const transaction = Cardano.Transaction.new(
Cardano.TransactionBody.from_bytes(txBody.to_bytes()),
Cardano.TransactionWitnessSet.from_bytes(
transactionWitnessSet.to_bytes()
)
);
const serializedTx = toHex(transaction.to_bytes());
const txVkeyWitnesses = await window.cardano.signTx(serializedTx, true);
let signedtxVkeyWitnesses = Cardano.TransactionWitnessSet.from_bytes(
fromHex(txVkeyWitnesses)
);
transactionWitnessSet.set_vkeys(signedtxVkeyWitnesses.vkeys() as Vkeywitnesses);
const signedTx = Cardano.Transaction.new(
Cardano.TransactionBody.from_bytes(txBody.to_bytes()),
Cardano.TransactionWitnessSet.from_bytes(
transactionWitnessSet.to_bytes()
)
);
console.log("full tx size", signedTx.to_bytes().length);
console.log("datumObj", hoskyDatumObject);
const txHash = await window.cardano.submitTx(toHex(signedTx.to_bytes()));
console.log(txHash);
}
}
async function CancelOfferTxAsync() {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
setCoinSelectionCardanoSerializationLib(Cardano);
const protocolParameters = await GetProtocolProtocolParamsAsync();
CoinSelection.setProtocolParameters(
protocolParameters.min_utxo.toString(),
protocolParameters.min_fee_a.toString(),
protocolParameters.min_fee_b.toString(),
protocolParameters.max_tx_size.toString()
);
const txBuilder = await CreateTransactionBuilderAsync() as TransactionBuilder;
const transactionWitnessSet = Cardano.TransactionWitnessSet.new();
const selfAddress = Cardano.Address.from_bytes(fromHex(await GetWalletAddressAsync()));
const baseAddress = Cardano.BaseAddress.from_address(selfAddress) as BaseAddress;
console.log(`selfAddress: ${selfAddress.to_bech32()}`);
// const pkh = "3e4a2ec70fcef9e54c437a173714d1f82b96242379816bea3dd387dd";
const pkh = toHex(baseAddress.payment_cred().to_keyhash()?.to_bytes() as Uint8Array);
console.log(`pkh: ${pkh}`)
const scriptUtxo = Cardano.TransactionUnspentOutput.new(
Cardano.TransactionInput.new(
Cardano.TransactionHash.from_bytes(fromHex(scriptTxId)), scriptTxIndex
),
Cardano.TransactionOutput.new(
await ContractAddress() as Address,
await GetContractOutput() as Value
)
);
const utxos = (await window.cardano.getUtxos()).map((utxo) =>
Cardano.TransactionUnspentOutput.from_bytes(fromHex(utxo))
);
const outputs: TransactionOutput[] = [
Cardano.TransactionOutput.new(
selfAddress,
await GetContractOutput() as Value
)
];
const transactionOutputs = Cardano.TransactionOutputs.new();
outputs.forEach(output => transactionOutputs.add(output));
const csResult = CoinSelection.randomImprove(
utxos,
transactionOutputs,
8,
[scriptUtxo]
);
csResult.inputs.forEach((utxo) => {
console.log(`output address: ${utxo.output().address().to_bech32()}`)
console.log(`input: txId ${toHex(utxo.input().transaction_id().to_bytes())} txIndex ${utxo.input().index()}`)
console.log(`output amount: ${utxo.output().amount().coin().to_str()} lovelace, other len: ${utxo.output().amount().multiasset()?.len()}`)
txBuilder.add_input(
utxo.output().address(),
utxo.input(),
utxo.output().amount()
);
});
const scriptInputIndex = txBuilder.index_of_input(scriptUtxo.input());
outputs.forEach(output => txBuilder.add_output(output));
const requiredSigners = Cardano.Ed25519KeyHashes.new();
requiredSigners.add(baseAddress.payment_cred().to_keyhash() as Ed25519KeyHash);
txBuilder.set_required_signers(requiredSigners);
const datumObj = OfferDatum(pkh, askingPrice, policyId, assetName);
const datum = await ToPlutusData(datumObj as PlutusDataObject) as PlutusData;
const datumHash = Cardano.hash_plutus_data(datum);
console.log("datumHash", toHex(datumHash.to_bytes()));
const datumList = Cardano.PlutusList.new();
datumList.add(datum);
const redeemers = Cardano.Redeemers.new();
// not passing datum because close.json content is {"constructor":2,"fields":[]}
redeemers.add(await SimpleRedeemer(scriptInputIndex) as Redeemer);
txBuilder.set_plutus_scripts(await ContractScript() as PlutusScripts);
txBuilder.set_plutus_data(datumList);
txBuilder.set_redeemers(redeemers);
transactionWitnessSet.set_plutus_scripts(await ContractScript() as PlutusScripts);
transactionWitnessSet.set_plutus_data(datumList);
transactionWitnessSet.set_redeemers(redeemers);
const collateralUnspentTransactions = (await GetCollateralUnspentTransactionOutputAsync()) as TransactionUnspentOutput[];
const collateralInputs = Cardano.TransactionInputs.new();
collateralUnspentTransactions.forEach(c => collateralInputs.add(c.input()));
txBuilder.set_collateral(collateralInputs);
txBuilder.add_change_if_needed(selfAddress);
const txBody = txBuilder.build();
const transaction = Cardano.Transaction.new(
Cardano.TransactionBody.from_bytes(txBody.to_bytes()),
transactionWitnessSet
);
const serializedTx = toHex(transaction.to_bytes());
const txVkeyWitnesses = await window.cardano.signTx(serializedTx, true);
let signedtxVkeyWitnesses = Cardano.TransactionWitnessSet.from_bytes(
fromHex(txVkeyWitnesses)
);
transactionWitnessSet.set_vkeys(signedtxVkeyWitnesses.vkeys() as Vkeywitnesses);
const signedTx = Cardano.Transaction.new(
Cardano.TransactionBody.from_bytes(txBody.to_bytes()),
transactionWitnessSet
// Cardano.TransactionWitnessSet.from_bytes(
// transactionWitnessSet.to_bytes()
// )
);
console.log("Full Tx Size", signedTx.to_bytes().length);
let result = await window.cardano.submitTx(toHex(signedTx.to_bytes()));
console.log("tx submitted", result);
}
}
async function GetProtocolProtocolParamsAsync(): Promise<CardanoProtocolParameters> {
let protocolParamsResult = await fetch("https://cardano-testnet.blockfrost.io/api/v0/epochs/latest/parameters", {
"headers": {
"project_id": BLOCKFROST_PROJECT_ID
}
});
return await protocolParamsResult.json();
}
const GetContractOutput = async () => {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
return AssetValue(
await toBigNum(amountToTransfer),
policyId,
assetNameInHex,
await toBigNum("1")
);
}
}
const AssetValue = async (lovelace: BigNum, policyIdHex: string, assetNameHex: string, amount: BigNum) => {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
const assetVal = Cardano.Value.new(lovelace);
const assetMA = Cardano.MultiAsset.new();
const asset = Cardano.Assets.new();
asset.insert(
Cardano.AssetName.new(fromHex(assetNameHex)),
amount
);
assetMA.insert(
Cardano.ScriptHash.from_bytes(fromHex(policyIdHex)),
asset
);
assetVal.set_multiasset(assetMA)
return assetVal;
}
}
const ToPlutusData = async (plutusDataObj: PlutusDataObject) => {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
const datumFields = Cardano.PlutusList.new();
plutusDataObj.Fields.sort((a, b) => a.Index - b.Index);
plutusDataObj.Fields.forEach(f => {
if (Cardano === null) return;
switch (f.Type) {
case PlutusFieldType.Integer:
datumFields.add(Cardano.PlutusData.new_integer(Cardano.BigInt.from_str(f.Value.toString())));
break;
// case PlutusFieldType.Data:
// datumFields.add(ToPlutusData(f.Value) as PlutusData);
case PlutusFieldType.Bytes:
datumFields.add(Cardano.PlutusData.new_bytes(f.Value));
}
})
return Cardano.PlutusData.new_constr_plutus_data(
Cardano.ConstrPlutusData.new(
Cardano.Int.new_i32(plutusDataObj.ConstructorIndex),
datumFields
)
);
}
}
const OfferDatum = (pkh: string, price: string, policyId: string, assetName: string) => {
let Cardano = CardanoSerializationLib();
if (Cardano !== null) {
const offerDatum = new PlutusDataObject(0);
offerDatum.Fields = [
{
Index: 0,
Type: PlutusFieldType.Bytes,
Key: "pkh",
Value: fromHex(pkh)
} as PlutusField,
{
Index: 0,
Type: PlutusFieldType.Integer,
Key: "price",
Value: price
} as PlutusField,
{
Index: 0,
Type: PlutusFieldType.Bytes,
Key: "policyId",
Value: toByteArray(policyId)
} as PlutusField,
{
Index: 0,
Type: PlutusFieldType.Bytes,
Key: "assetName",
Value: toByteArray(assetName)
} as PlutusField,
];
return offerDatum;
}
}
const CreateTransactionBuilderAsync = async () => {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
let protocolParams = await GetProtocolProtocolParamsAsync();
const txBuilder = Cardano.TransactionBuilder.new(
Cardano.LinearFee.new(
await toBigNum(protocolParams.min_fee_a),
await toBigNum(protocolParams.min_fee_b)
),
await toBigNum("1000000"),
await toBigNum("500000000"),
await toBigNum("2000000"),
parseInt("5000"),
16384,
5.77e-2,
7.21e-5,
Cardano.LanguageViews.new(fromHex(languageViews))
);
return txBuilder;
}
}
const SimpleRedeemer = async (index: number) => {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
//close.json - {"constructor":2,"fields":[]} - this is why I pyt new_i32(2), maybe I'm wrong here
const redeemerData = Cardano.PlutusData.new_constr_plutus_data(
Cardano.ConstrPlutusData.new(
Cardano.Int.new_i32(2),
Cardano.PlutusList.new()
)
);
const r = Cardano.Redeemer.new(
Cardano.RedeemerTag.new_spend(),
await toBigNum(index),
redeemerData,
Cardano.ExUnits.new(
Cardano.BigNum.from_str("1754991"),
Cardano.BigNum.from_str("652356532")
)
)
return r;
}
}
const ContractScript = async () => {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
const scripts = Cardano.PlutusScripts.new();
scripts.add(Cardano.PlutusScript.new(fromHex(contractCbor)));
return scripts;
}
};
const GetCollateralUnspentTransactionOutputAsync = async () => {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
const utxosHex = await window.cardano.getCollateral();
let utxos = utxosHex.map((utxoHex: string) => Cardano?.TransactionUnspentOutput.from_bytes(fromHex(utxoHex) ?? null));
utxos = utxos.filter((utxo) => utxo !== undefined);
return utxos as TransactionUnspentOutput[];
}
};
const ContractAddress = async () => {
let Cardano = await CardanoSerializationLib();
if (Cardano !== null) {
return Cardano.Address.from_bech32("addr_test1wrgd386qdtskfe5uhtk3ur9yx4mnjr5qfn4w0n6uhc9zmwq2qa6n4")
}
}
document.onreadystatechange = async () => {
if (document.readyState === "complete") await Main();
};
I’m getting exception:
transaction submit error ShelleyTxValidationError ShelleyBasedEraAlonzo (ApplyTxError
[ UtxowFailure (NonOutputSupplimentaryDatums (fromList [SafeHash \"8ac77ecbddbc22497349be7ef08ea61bccc57733b588548ebe1d8c6c0a68f9c8\"]) (fromList [])),
UtxowFailure (ExtraRedeemers [RdmrPtr Spend 0]),UtxowFailure (WrappedShelleyEraFailure (MissingScriptWitnessesUTXOW (fromList [ScriptHash \"d0d89f406ae164e69cbaed1e0ca43577390e804ceae7cf5cbe0a2db8\"]))),
UtxowFailure (WrappedShelleyEraFailure (UtxoFailure (UtxosFailure (CollectErrors [NoWitness (ScriptHash \"d0d89f406ae164e69cbaed1e0ca43577390e804ceae7cf5cbe0a2db8\")]))))])"
Any idea why I cannot unlock and why it is complaining that I’m missing signature?