You got your faboulos script with all those cute opcodes, and now what? Ionio SDK FTW!
Contextโ
In the Bitcoin world, most of the possible scripts are de-facto standards all wallets follow, set in stone by the wallet developer.
In a post-Simplicty world, Bitcoin (tap)scripts will introduce much more capabilities, but stadardize all possible combination in all wallets becomes impossibile, will be the user (or any external app he's interacting with) to instruct the wallet what to do at runtime.
Output Descriptors and Miniscript could be a good candidate on how to generalize a way to import script, but i) lack of extensibility in cooperative script building scenarios ii) need to write a parser/compiler for each language makes it a bit cumbersome for wallet/libraries to work with, plus the "policy oriented" nature does not well fit the introspection (ie. covenants) paradigm Simplicity will allow.
The feature to import a script template is fundamental for the wallet to track balances and to know how spend those coins in the future.
The Ionio Artifact it's a JSON file that fully describe how a Pay to Taproot address is constructed, how the contract behaves and what it should be expected to do spend it in the future. The documentation fot the data structure can be found here
๐งฎ You first "calculator"
Dev Environmentโ
- Docker Linux or Docker Desktop for Mac
- Nigiri
- nodejs
- yarn (optional, you can use npm)
Nigiriโ
Install Nigiri
curl https://getnigiri.vulpem.com | bash
- Run a Liquid box
nigiri start --liquid
Install dependencies & configโ
- Project setup
Pull a Svelte starter app
npx degit "tiero/svelte-webpack-bulma" ionio-app
Enter the folder
cd ionio-app
Install depenendencies
yarn add @ionio-lang/ionio tiny-secp256k1
- Create a
calculator.json
file insrc
{
"contractName": "Calculator",
"constructorInputs": [
{
"name": "sum",
"type": "number"
}
],
"functions": [
{
"name": "sumMustBeThree",
"functionInputs": [
{
"name": "a",
"type": "number"
},
{
"name": "b",
"type": "number"
}
],
"require": [],
"asm": [
"OP_ADD",
"$sum",
"OP_EQUAL"
]
}
]
}
Add Layout and stateโ
Open
App.svelte
in your editorAdd layout after the title box
<div class="box">
<h1 class="title">Calculator</h1>
<p class="has-text-weight-bold">
{contractAddress}
</p>
<hr />
{#if txhex.length > 0}
<hr />
<p class="subtitle">Raw transaction</p>
<input class="input" value={txhex} />
{/if}
</div>
- Add script section on top
<script type="ts">
import { Artifact, Contract } from '@ionio-lang/ionio';
import { networks, address, ElementsValue, AssetHash } from 'liquidjs-lib';
import * as ecc from 'tiny-secp256k1';
import artifact from './calculator.json';
// instantiate the secp256-zkp wasm library
// define the network we going to work
const network = networks.regtest;
// create empty state
let txhex = '';
// amounts to use for spending
const sats = 100000;
const fee = 100;
// ๐ Let's compile the script
const contract = new Contract(
// our JSON artifact file
artifact as Artifact,
// our constructor to replace template strings
[3],
// network for address encoding
network,
// injectable secp256k1 libraries
{ ecc, zkp: null }
);
const contractAddress = contract.address;
</script>
๐ฐ Fundโ
Run the app with yarn dev
to see the address for your calculator
# send 100k sats to the contract
# this will auto-mine a block
nigiri faucet --liquid <contract_address> 0.001
You can open the exploer at http://localhost:5001
and copy/paste address to check utxos
Track down the txid
and vout
of the new unspent output that locks coin in the calculator
๐ธ Spendโ
- Add a
onClick
function to be triggered by button
const onClick = async () => {
const txid = prompt('Enter a transaction hash');
const vout = prompt('Enter the vout');
// attach to the funded contract using the utxo
const instance = contract.from(
// tranaction ID
txid,
// previous output index
parseInt(vout),
// the full previous output
{
script: address.toOutputScript(contractAddress),
value: ElementsValue.fromNumber(sats).bytes,
asset: AssetHash.fromHex(network.assetHash).bytes,
nonce: Buffer.alloc(0),
}
);
const recipient = prompt('Enter a recipient to send funds to');
const tx = await instance.functions
.sumMustBeThree(1, 2)
.withRecipient(recipient, sats - fee, network.assetHash)
.withFeeOutput(fee)
.unlock();
// extract and broadcast
txhex = tx.toHex();
};
- Add the
onClick
to theon:click
Svelte directive of the button
<button class="button is-primary" on:click={onClick}> Sum must be 3 </button>
- ๐ push the transaction
Run the app and click on the button Sum must be 3
It will ask you to enter a transaction hash and vout and an recipient address.
Get a fresh unconfidential address
nigiri rpc --liquid validateaddress `nigiri rpc --liquid getnewaddress`
Broadcast
nigiri push --liquid <txhex>