4.1 Off-chain Smart Contracts
The smart contracts are written in Bitcoin's advanced smart contract language provided by sCrypt, ensuring that the index validator completes execution within a limited time. Additionally, it supports advanced use cases including Swaps, Oracles, and Zero-Knowledge Proofs.
Here is a simple example of an off-chain contract:
export class N20_Sample extends SmartContract {
@prop()
readonly tick: ByteString
@prop()
readonly max: bigint
@prop()
readonly lim: bigint
@prop()
readonly dec: bigint
constructor(tick: ByteString, max: bigint, lim: bigint, dec: bigint) {
super(...arguments)
this.tick = tick
this.max = max
this.lim = lim
this.dec = dec
}
@method()
public mint(tick: ByteString, amt: bigint) {
assert(tick == this.tick, 'Tick does not match')
assert(amt <= this.lim, 'Amount check failed')
}
@method()
public transfer(tick: ByteString, amt: bigint) {
assert(tick == this.tick, 'Tick does not match')
assert(amt <= this.lim, 'Amount check failed')
}
}
The mint
and transfer
functions validate whether the two actions of N20 can be executed. Only after the operations pass contract validation will the indexer record changes in the account balances.
Contract Runtime Variables
During the runtime of the smart contract, the indexer provides constants and environmental variables as follows:
account
: ByteString, the account of current tx.inputSatoshis
: bigint, the satoshis of the current input (Satoshi amount unlocking the UTXO)height
: bigint, current height; when the transaction is unconfirmed, it is the latest height plus 6total
: bigint, current minted amount of tokencreator
: ByteString, the account of the deployed tokenblockHash
: ByteString, the hash of the current block, empty string when unconfirmedblockTime
: bigint, the timestamp of the current block, 0 when unconfirmedindexInBlock
: bigint, the index of the current transaction in the block, 0 when unconfirmedindexInChain
: bigint, the index of the current transaction in the chain, 0 when unconfirmedtx
: ByteString, the HEX of the current transactiontxId
: ByteString, the ID of the current transactionversion
: bigint, the version number of the current transaction, typically 2nLockTime
: bigint, the nLockTime of the current transactioninputs
: array of current transaction inputs, each input includes:prevTxId
: ByteString, the ID of the previous transactionoutputIndex
: bigint, the output index of the previous transactionsequenceNumber
: bigint, the unlocking sequenceNumber
outputs
: array of current transaction outputs, each output includes:script
: ByteString, the script of the outputsatoshis
: bigint, the satoshis of the output
balance
: The balance oftick
held by the currentaccount
confirmed
: bigint, the balance of confirmed transactionsunconfirmed
: bigint, the balance of unconfirmed transactions
noteBalance
: The balance ofNOTE
held by the currentaccount
confirmed
: bigint, the balance of confirmed transactionsunconfirmed
: bigint, the balance of unconfirmed transactions
pre_*
: the previous transaction's variables
Don't use runtime variables in your parameters of methods.
The pre_*
variables are only available in the transfer
function.
Runtime Variables Example
note: { amt: 10000000000n, op: 'transfer', p: 'n20', tick: 'WUKONG#8' },
dataMap: {
constructor: {
bitwork: '3230',
dec: 8,
desc: 'A sample token showcasing a mining contract with progressively increasing difficulty.',
lim: 900000000000n,
max: 81000000000000n,
op: '6465706c6f79',
p: '6e3230',
sch: 'd594416d99151d3bf962f53a792b1eb244646b984985c863fa8ee27bdfce2992',
start: 41305,
tick: '57554b4f4e472338'
},
transfer: {
bitwork: '3230',
dec: 8,
desc: 'A sample token showcasing a mining contract with progressively increasing difficulty.',
lim: 900000000000n,
max: 81000000000000n,
op: '7472616e73666572',
p: '6e3230',
sch: 'd594416d99151d3bf962f53a792b1eb244646b984985c863fa8ee27bdfce2992',
start: 41305,
tick: '57554b4f4e472338',
amt: [ 10000000000n, 5390000000000n ],
height: 44176,
blockHash: '',
blockTime: 0n,
indexInBlock: 0n,
indexInChain: 0n,
tx: '020000000001029ca52125503b229f19287eb5c1af3a713b0f07e00251a6c3cc4e4983dd8ad7e90000000000ffffffff8b154ed3ef950c5b1c24e8bd04cf17df35c8be2e52dbfe90c83221010783c3280200000000ffffffff032202000000000000225120fb1397257ecba1b51739192853c08209235bb662482eaebf6556170442d7f0502202000000000000225120fb1397257ecba1b51739192853c08209235bb662482eaebf6556170442d7f050542a270000000000160014bc5fa59b7108e0ec633e66233684bef4d4dbad4808401c715b2d877412f895eb20c88c65b90288843a56426cf49031343994c4f56c050e0657d059de04c6fa9149553fbe55d65d8a9c50f750f2adee6d4ad2cc4f7fdc2e84a3616d74cf00000002540be400a26f70a87472616e73666572a170a36e3230a47469636ba857554b4f4e472338000000002a044e4f54456d6d6d20da6c71b73fb5462258b16c60f30465fc5985fe9e63610e671f7c8bfddab3b115ac41c0da6c71b73fb5462258b16c60f30465fc5985fe9e63610e671f7c8bfddab3b1152a56124065fd50baecd89ca4204fbfaa0b66021d78891c9b7b9255a11b1341140248304502210093786529225604a185a4bf893c13e576b1f7cee338d5968ae41c1bdc7e523782022068259b0f910fa7c53eae6cfde0e798593110527289dda17b36eab75631ce7abe012102da6c71b73fb5462258b16c60f30465fc5985fe9e63610e671f7c8bfddab3b11500000000',
inputs: [
{
prevTxId: 'e9d78add83494eccc3a65102e0070f3b713aafc1b57e28199f223b502521a59c',
outputIndex: 0n,
sequenceNumber: 4294967295n
},
{
prevTxId: '28c38307012132c890fedb522ebec835df17cf04bde8241c5b0c95efd34e158b',
outputIndex: 2n,
sequenceNumber: 4294967295n
}
],
outputs: [
{
script: '5120fb1397257ecba1b51739192853c08209235bb662482eaebf6556170442d7f050',
satoshis: 546n
},
{
script: '5120fb1397257ecba1b51739192853c08209235bb662482eaebf6556170442d7f050',
satoshis: 546n
},
{
script: '0014bc5fa59b7108e0ec633e66233684bef4d4dbad48',
satoshis: 2566740n
}
],
nLockTime: 0,
txId: '88e0ba59cd2b8b7f795b81c06f94e961a2eaa14359c231318d9cb23ff17a0b94',
version: 2,
account: '209e7f0e21d5314ff6d0370200565f1831a84b8c6666331dca00d8d16dbdcc24',
inputIndex: 0,
inputSatoshis: 546n,
prevTxId: 'e9d78add83494eccc3a65102e0070f3b713aafc1b57e28199f223b502521a59c',
outputIndex: 0,
prev_amt: [ 5400000000000n ],
prev_op: '7472616e73666572',
prev_p: '6e3230',
prev_tick: '57554b4f4e472338',
prev_script: '5120fb1397257ecba1b51739192853c08209235bb662482eaebf6556170442d7f050',
prev_satoshis: 546n,
prev_height: 44132,
prev_blockHash: '0000000000000018483c2904d32ba0d8bd292063ef713d14f5ea5ccc02e10ce7',
prev_blockTime: 1725938902,
prev_indexInBlock: 52,
prev_indexInChain: 44132000052,
prev_sender: '209e7f0e21d5314ff6d0370200565f1831a84b8c6666331dca00d8d16dbdcc24',
total: 72900000000000n,
creator: '6d96a0c6127ef53b161678a8426056c19526a36d1790c72b7f9b118b64e9d4f5',
balance: { confirmed: 5387700000000n, unconfirmed: 0n }
}
}
Explanation:
- The
constructor
field contains the initialization parameters of the contract. - The
transfer
field contains constants or variables that can be used during transfers. Parameters from the contract's initialization can also be used, such as thedesc
field, which comes from the user's custom description.