Tutorial

Simple Boolean Policies: Non-Transferable Assets

This example demonstrates how Alice can define a new asset, issue units of that asset to herself, and transfers some units of the asset to her friend Bob. Alice will make the asset non-transferable, so that Bob won't be able to transfer the asset to his friend, Bubba. Alice and Bob need to create keys first.

console.log('Creating a keystore for Alice...');
const ALICE_PASSWORD = 'aliceisthebest';
const aliceKeyStore = new KeyStore.KeyStore(ALICE_PASSWORD);
const aliceMasterKey = aliceKeyStore.genMasterKey(ALICE_PASSWORD);
const aliceKeyPair = aliceKeyStore.genKeyPair(aliceMasterKey, 'Alice');

console.log('Creating a keystore for Bob...');
const BOB_PASSWORD = 'bobisthebest';
const bobKeyStore = new KeyStore.KeyStore(BOB_PASSWORD);
const bobMasterKey = bobKeyStore.genMasterKey(BOB_PASSWORD);
const bobKeyPair = bobKeyStore.genKeyPair(bobMasterKey, 'Bob');

console.log('Creating a keystore for Bubba...');
const BUBBA_PASSWORD = 'bubbaismediocre';
const bubbaKeyStore = new KeyStore.KeyStore(BUBBA_PASSWORD);
const bubbaMasterKey = bubbaKeyStore.genMasterKey(BUBBA_PASSWORD);
const bubbaKeyPair = bubbaKeyStore.genKeyPair(bubbaMasterKey, 'Bubba');

Alice will now define an new asset.

const network = new Network.Network(PROTOCOL, HOST, QUERY_PORT, SUBMISSION_PORT, LEDGER_PORT);
const tokenCode = Ledger.random_asset_type();
const memo = 'this is a test asset';

If we omit the next line, or make the argument true, the asset will be transferable

const assetRules = Ledger.AssetRules.new().set_transferable(false);
let blockCount = BigInt((await network.getStateCommitment())[1]);
const definitionTransaction = Ledger.TransactionBuilder
  .new(blockCount).add_operation_create_asset(aliceKeyPair, memo, tokenCode, assetRules).transaction();

await network.submitTransaction(definitionTransaction);

console.log('Asset created.');

await Utils.sleep(4);

Alice will issue a few units of the asset to herself.

blockCount = BigInt((await network.getStateCommitment())[1]);
const zeiParams = Ledger.PublicParams.new();
const seqID = BigInt(await network.getIssuanceNum(tokenCode));
const amount = BigInt(5);
const confidential = false;
const issueTxn = Ledger.TransactionBuilder.new(blockCount)
  .add_basic_issue_asset(aliceKeyPair, tokenCode, seqID, amount, confidential, zeiParams).transaction();

let handle = await network.submitTransaction(issueTxn);

Alice can now construct a TransferAsset operation using Findora-Wasm's TransferOperationBuilder. First, Alice needs to fetch the asset record that she wishes to transfer. Included in the transaction status is a list of TXOSIDs of asset records created by that transaction.

await Utils.sleep(4);
const status = await network.getTxnStatus(handle);
let utxoSid = status.Committed[1][0];
let utxo = await network.getUtxo(utxoSid);
let assetRecord = Ledger.ClientAssetRecord.from_json(utxo.utxo);

Alice must also fetch the owner memo of the record she wishes to send. An owner memo contains data that allows record owners to decrypt confidential records. In this case, the asset record Alice is attempting to send isn't confidential, so the owner memo isn't very useful. We can set it to undefined. The query server conveniently stores owner memos.

const ownerMemo = undefined;

Alice can now construct her transfer operation. Alice will send most of the units in the asset record she has issued to Bob, but return a few to herself. The input and output amounts must be completely balanced. Alice must also sign the operation with her private key. In general, all owners of input records of transfers must sign the transfer.

let transferAmount = utxo.utxo.record.amount.NonConfidential;
let txoRef = Ledger.TxoRef.absolute(BigInt(utxoSid)); // Reference to the TXO SID being transferred. Absolute references are used for TXOs that have already been committed to the ledger while relative references point to records created within transactions.
assetRecord = Ledger.ClientAssetRecord.from_json(utxo.utxo);
let transferOp = Ledger.TransferOperationBuilder.new()
  .add_input_no_tracking(txoRef,
    assetRecord,
    ownerMemo,
    aliceKeyPair,
    BigInt(transferAmount - 2))
  .add_output_no_tracking(BigInt(transferAmount - 2), bobKeyPair.get_pk(), tokenCode)
  .add_output_no_tracking(BigInt(2), aliceKeyPair.get_pk(), tokenCode)
  .create()
  .sign(aliceKeyPair)
  .transaction();

Alice can now add her newly constructed transfer operation to a transaction builder, and submit the transaction to the ledger.

blockCount = BigInt((await network.getStateCommitment())[1]);
let transferTxn = Ledger.TransactionBuilder.new(blockCount)
  .add_transfer_operation(transferOp)
  .transaction();
await network.submitTransaction(transferTxn);

Bob can fetch his list of owned TXOSIDs and see that he has recieved a new asset.

const base64PubKey = Ledger.public_key_to_base64(bobKeyPair.get_pk());
await Utils.sleep(2);
const bobOwnedSids = await network.getOwnedSids(base64PubKey);
console.log(`Bob owned SIDs is ${JSON.stringify(bobOwnedSids)}`);
assert(bobOwnedSids.length == 1);

Now, we attempt a transfer from Bob to Bubba

utxoSid = bobOwnedSids[0];
console.log(`utxoSid is ${JSON.stringify(utxoSid)}`);
utxo = await network.getUtxo(utxoSid);
assetRecord = Ledger.ClientAssetRecord.from_json(utxo.utxo);
transferAmount = utxo.utxo.record.amount.NonConfidential;
console.log(`transferAmount is ${JSON.stringify(transferAmount)}`);
txoRef = Ledger.TxoRef.absolute(BigInt(utxoSid)); // Reference to the TXO SID being transferred. Absolute references are used for TXOs that have already been committed to the ledger while relative references point to records created within transactions.
console.log(`txoRef is ${JSON.stringify(txoRef)}`);
transferOp = Ledger.TransferOperationBuilder.new()
  .add_input_no_tracking(txoRef,
    assetRecord,
    ownerMemo,
    bobKeyPair,
    BigInt(transferAmount - 2))
  .add_output_no_tracking(BigInt(transferAmount - 2), bubbaKeyPair.get_pk(), tokenCode)
  .add_output_no_tracking(BigInt(2), bobKeyPair.get_pk(), tokenCode)
  .create()
  .sign(bobKeyPair)
  .transaction();

Bob can now add his newly constructed transfer operation to a transaction builder, and submit the transaction to the ledger.

blockCount = BigInt((await network.getStateCommitment())[1]);
transferTxn = Ledger.TransactionBuilder.new(blockCount)
  .add_transfer_operation(transferOp)
  .transaction();

handle = await network.submitTransaction(transferTxn);
console.log(`Transfer transaction submitted successfully! The transaction handle is ${handle}.`);

The transfer will be rejected at the ledger side. The following code demonstrates this

console.log('Getting transaction status...');
transactionStatus = await network.getTxnStatus(handle);
console.log(JSON.stringify(transactionStatus));
if (transactionStatus.Rejected) {
  console.log('Your transaction was rejected');
  // throw "You probably violated a policy!";
} else if (transactionStatus.Committed) {
  console.log('All good');
} else { // if (transactionStatus["Pending"])
  console.log('Still waiting for your transaction to complete...');
}