Smart Contract Transactions
Build a marketplace where users list assets for sale and purchase listed assets. Sellers can update or cancel listings.
Initialize the Plutus script
Initialize the Plutus script. The compiled Plutus smart contract script CBOR is:
const scriptCbor = '59079559079201000033232323232323232323232323232332232323232323232222232325335333006300800530070043333573466e1cd55cea80124000466442466002006004646464646464646464646464646666ae68cdc39aab9d500c480008cccccccccccc88888888888848cccccccccccc00403403002c02802402001c01801401000c008cd4060064d5d0a80619a80c00c9aba1500b33501801a35742a014666aa038eb9406cd5d0a804999aa80e3ae501b35742a01066a0300466ae85401cccd54070091d69aba150063232323333573466e1cd55cea801240004664424660020060046464646666ae68cdc39aab9d5002480008cc8848cc00400c008cd40b9d69aba15002302f357426ae8940088c98c80c8cd5ce01981901809aab9e5001137540026ae854008c8c8c8cccd5cd19b8735573aa004900011991091980080180119a8173ad35742a004605e6ae84d5d1280111931901919ab9c033032030135573ca00226ea8004d5d09aba2500223263202e33573805e05c05826aae7940044dd50009aba1500533501875c6ae854010ccd540700808004d5d0a801999aa80e3ae200135742a00460446ae84d5d1280111931901519ab9c02b02a028135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d5d1280089aba25001135744a00226ae8940044d55cf280089baa00135742a00460246ae84d5d1280111931900e19ab9c01d01c01a101b13263201b3357389201035054350001b135573ca00226ea80054049404448c88c008dd6000990009aa80a911999aab9f0012500a233500930043574200460066ae880080548c8c8cccd5cd19b8735573aa004900011991091980080180118061aba150023005357426ae8940088c98c8054cd5ce00b00a80989aab9e5001137540024646464646666ae68cdc39aab9d5004480008cccc888848cccc00401401000c008c8c8c8cccd5cd19b8735573aa0049000119910919800801801180a9aba1500233500f014357426ae8940088c98c8068cd5ce00d80d00c09aab9e5001137540026ae854010ccd54021d728039aba150033232323333573466e1d4005200423212223002004357426aae79400c8cccd5cd19b875002480088c84888c004010dd71aba135573ca00846666ae68cdc3a801a400042444006464c6403866ae700740700680640604d55cea80089baa00135742a00466a016eb8d5d09aba2500223263201633573802e02c02826ae8940044d5d1280089aab9e500113754002266aa002eb9d6889119118011bab00132001355012223233335573e0044a010466a00e66442466002006004600c6aae754008c014d55cf280118021aba200301313574200222440042442446600200800624464646666ae68cdc3a800a40004642446004006600a6ae84d55cf280191999ab9a3370ea0049001109100091931900899ab9c01201100f00e135573aa00226ea80048c8c8cccd5cd19b875001480188c848888c010014c01cd5d09aab9e500323333573466e1d400920042321222230020053009357426aae7940108cccd5cd19b875003480088c848888c004014c01cd5d09aab9e500523333573466e1d40112000232122223003005375c6ae84d55cf280311931900899ab9c01201100f00e00d00c135573aa00226ea80048c8c8cccd5cd19b8735573aa004900011991091980080180118029aba15002375a6ae84d5d1280111931900699ab9c00e00d00b135573ca00226ea80048c8cccd5cd19b8735573aa002900011bae357426aae7940088c98c802ccd5ce00600580489baa001232323232323333573466e1d4005200c21222222200323333573466e1d4009200a21222222200423333573466e1d400d2008233221222222233001009008375c6ae854014dd69aba135744a00a46666ae68cdc3a8022400c4664424444444660040120106eb8d5d0a8039bae357426ae89401c8cccd5cd19b875005480108cc8848888888cc018024020c030d5d0a8049bae357426ae8940248cccd5cd19b875006480088c848888888c01c020c034d5d09aab9e500b23333573466e1d401d2000232122222223005008300e357426aae7940308c98c8050cd5ce00a80a00900880800780700680609aab9d5004135573ca00626aae7940084d55cf280089baa0012323232323333573466e1d400520022333222122333001005004003375a6ae854010dd69aba15003375a6ae84d5d1280191999ab9a3370ea0049000119091180100198041aba135573ca00c464c6401a66ae7003803402c0284d55cea80189aba25001135573ca00226ea80048c8c8cccd5cd19b875001480088c8488c00400cdd71aba135573ca00646666ae68cdc3a8012400046424460040066eb8d5d09aab9e500423263200a33573801601401000e26aae7540044dd500089119191999ab9a3370ea00290021091100091999ab9a3370ea00490011190911180180218031aba135573ca00846666ae68cdc3a801a400042444004464c6401666ae7003002c02402001c4d55cea80089baa0012323333573466e1d40052002212200223333573466e1d40092000212200123263200733573801000e00a00826aae74dd5000891999ab9a3370e6aae74dd5000a40004008464c6400866ae700140100092612001490103505431001123230010012233003300200200122212200201';Initialize the Plutus script:
const script: PlutusScript = {
code: scriptCbor,
version: 'V2',
};Resolve the Plutus script address using resolvePlutusScriptAddress. Input the PlutusScript and network (0 for testnet):
const scriptAddress = resolvePlutusScriptAddress(script, 0);Listing Asset for Sale
Get the seller's address from the connected wallet:
const addr = (await wallet.getUsedAddresses())[0];Create the datum schema:
const datumConstr: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(addr), // seller address as pubkeyhash
listPriceInLovelace, // price
policyId, // policy ID of token
assetId, // asset name of token in hex
],
};Create a transaction using sendAssets() to send the asset to the script address with the defined datum. policyId + assetId is the asset name in hex. Build, sign, and submit the transaction.
const tx = new Transaction({ initiator: wallet })
.sendAssets(
{
address: scriptAddress,
datum: {
value: datumConstr,
},
},
[
{
unit: policyId + assetId,
quantity: '1',
},
]
);
const unsignedTx = await tx.build();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);Implement your own marketplace. Note: A database may be required to store listing information.
Full code for listing an asset:
const addr = (await wallet.getUsedAddresses())[0];
const datumConstr: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(addr),
listPriceInLovelace,
policyId,
assetId,
],
};
const tx = new Transaction({ initiator: wallet })
.sendAssets(
{
address: scriptAddress,
datum: {
value: datumConstr,
},
},
[
{
unit: policyId + assetId,
quantity: '1',
},
]
);
const unsignedTx = await tx.build();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);Cancel the Listing
Cancel the listing. Only the seller can cancel. Define the datum:
const datumConstr: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(addr), // seller address as pubkeyhash
listPriceInLovelace, // price
policyId, // policy ID of token
assetId, // asset name of token in hex
],
};Cancel, update, and purchase endpoints require the UTxO in the script address. Use fetchAddressUTxOs() to query UTxOs containing the asset. Filter by datum hash using resolveDataHash() (see resolvers).
Implementation for _getAssetUtxo():
async function _getAssetUtxo({ scriptAddress, asset, datum }) {
const utxos = await blockchainFetcher.fetchAddressUTxOs(
scriptAddress,
asset
);
if (utxos.length == 0) {
throw 'No listing found.';
}
const dataHash = resolveDataHash(datum);
let utxo = utxos.find((utxo: any) => {
return utxo.output.dataHash == dataHash;
});
return utxo;
}Define the redeemer for cancellation:
const redeemer = { data: { alternative: 1, fields: [] } };Build the transaction. Use redeemValue() to redeem the UTxO and send the value back to the seller. Set required signers to the seller's address.
const tx = new Transaction({ initiator: wallet })
.redeemValue({
value: assetUtxo,
script: script,
datum: datumConstr,
redeemer: redeemer,
})
.sendValue(addr, assetUtxo)
.setRequiredSigners([addr]);
const unsignedTx = await tx.build();
const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);Full code for cancellation:
const addr = (await wallet.getUsedAddresses())[0];
const datumConstr: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(addr),
listPriceInLovelace,
policyId,
assetId,
],
};
const assetUtxo = await _getAssetUtxo({
scriptAddress: scriptAddress,
asset: 'd9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e',
datum: datumConstr,
});
if (assetUtxo === undefined) {
throw 'No listing found.';
}
const redeemer = { data: { alternative: 1, fields: [] } };
const tx = new Transaction({ initiator: wallet })
.redeemValue({
value: assetUtxo,
script: script,
datum: datumConstr,
redeemer: redeemer,
})
.sendValue(addr, assetUtxo)
.setRequiredSigners([addr]);
const unsignedTx = await tx.build();
const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);Purchase the Listed Asset
Purchase the listed asset. The endpoint requires the asset, price, and seller address to create the validator datum. Successful purchase transfers the asset to the buyer and price to the seller.
Get the buyer's address:
const addr = (await wallet.getUsedAddresses())[0]; // buyer's addressCreate the validator datum using the seller's address:
const datumConstr: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(sellerAddr), // seller address as pubkeyhash
listPriceInLovelace, // price
policyId, // policy ID of token
assetId, // asset name of token in hex
],
};Define the redeemer:
const redeemer = { data: { alternative: 0, fields: [] } };Build and submit the transaction. Use redeemValue() to redeem the asset, sendValue() to transfer to the buyer, and sendLovelace() to pay the seller:
const tx = new Transaction({ initiator: wallet })
.redeemValue({
value: assetUtxo,
script: script,
datum: datumConstr,
redeemer: redeemer,
})
.sendValue(addr, assetUtxo)
.sendLovelace(sellerAddr, listPriceInLovelace.toString())
.setRequiredSigners([addr]);
const unsignedTx = await tx.build();
const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);Full code for purchase:
const addr = (await wallet.getUsedAddresses())[0]; // buyer's address
const datumConstr: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(sellerAddr),
listPriceInLovelace,
policyId,
assetId,
],
};
const assetUtxo = await _getAssetUtxo({
scriptAddress: scriptAddress,
asset: 'd9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e',
datum: datumConstr,
});
const redeemer = { data: { alternative: 0, fields: [] } };
const tx = new Transaction({ initiator: wallet })
.redeemValue({
value: assetUtxo,
script: script,
datum: datumConstr,
redeemer: redeemer,
})
.sendValue(addr, assetUtxo)
.sendLovelace(sellerAddr, listPriceInLovelace.toString())
.setRequiredSigners([addr]);
const unsignedTx = await tx.build();
const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);Update the Listing
Update the listing. Only the seller can update. Define the original datum:
const datumConstr: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(addr), // seller address as pubkeyhash
listPriceInLovelace, // listed price
policyId, // policy ID of token
assetId, // asset name of token in hex
],
};Create the updated datum with the new price:
const datumConstrNew: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(addr), // seller address as pubkeyhash
updatedPriceInLovelace, // updated price
policyId, // policy ID of token
assetId, // asset name of token in hex
],
};Define the redeemer for update:
const redeemer = { data: { alternative: 1, fields: [] } };Build the transaction. Redeem the UTxO with the original datum and send the NFT to the script address with the new datum using sendAssets().
const tx = new Transaction({ initiator: wallet })
.redeemValue({
value: assetUtxo,
script: script,
datum: datumConstr,
redeemer: redeemer,
})
.setRequiredSigners([addr])
.sendAssets(
{
address: scriptAddress,
datum: {
value: datumConstrNew,
},
},
[
{
unit: 'd9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e',
quantity: '1',
},
]
);
const unsignedTx = await tx.build();
const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);Full code for update:
const addr = (await wallet.getUsedAddresses())[0];
const datumConstr: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(addr),
listPriceInLovelace,
policyId,
assetId,
],
};
const datumConstrNew: Data = {
alternative: 0,
fields: [
resolvePaymentKeyHash(addr),
updatedPriceInLovelace,
policyId,
assetId,
],
};
const assetUtxo = await _getAssetUtxo({
scriptAddress: scriptAddress,
asset: 'd9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e',
datum: datumConstr,
});
const redeemer = { data: { alternative: 1, fields: [] } };
const tx = new Transaction({ initiator: wallet })
.redeemValue({
value: assetUtxo,
script: script,
datum: datumConstr,
redeemer: redeemer,
})
.setRequiredSigners([addr])
.sendAssets(
{
address: scriptAddress,
datum: {
value: datumConstrNew,
},
},
[
{
unit: 'd9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e',
quantity: '1',
},
]
);
const unsignedTx = await tx.build();
const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);This serves as a starting point for building apps with smart contracts.