Skip to main content

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 6
  • total: bigint, current minted amount of token
  • creator: ByteString, the account of the deployed token
  • blockHash: ByteString, the hash of the current block, empty string when unconfirmed
  • blockTime: bigint, the timestamp of the current block, 0 when unconfirmed
  • indexInBlock: bigint, the index of the current transaction in the block, 0 when unconfirmed
  • indexInChain: bigint, the index of the current transaction in the chain, 0 when unconfirmed
  • tx: ByteString, the HEX of the current transaction
  • txId: ByteString, the ID of the current transaction
  • version: bigint, the version number of the current transaction, typically 2
  • nLockTime: bigint, the nLockTime of the current transaction
  • inputs: array of current transaction inputs, each input includes:
    • prevTxId: ByteString, the ID of the previous transaction
    • outputIndex: bigint, the output index of the previous transaction
    • sequenceNumber: bigint, the unlocking sequenceNumber
  • outputs: array of current transaction outputs, each output includes:
    • script: ByteString, the script of the output
    • satoshis: bigint, the satoshis of the output
  • balance: The balance of tick held by the current account
    • confirmed: bigint, the balance of confirmed transactions
    • unconfirmed: bigint, the balance of unconfirmed transactions
  • noteBalance: The balance of NOTE held by the current account
    • confirmed: bigint, the balance of confirmed transactions
    • unconfirmed: bigint, the balance of unconfirmed transactions
  • pre_*: the previous transaction's variables
caution

Don't use runtime variables in your parameters of methods.

info

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 the desc field, which comes from the user's custom description.