Built-in Functions
Global Functions
The following functions come with sCrypt:
Assert
assert(condition: boolean, errorMsg?: string)Throw anErrorwith the optional error message ifconditionisfalse. Otherwise, nothing happens.
assert(1n === 1n) // nothing happens
assert(1n === 2n) // throws Error('Execution failed')
assert(false, 'hello') // throws Error('Execution failed, hello')
Fill
fill(value: T, length: number): T[length]Returns anFixedArraywith allsizeelements set tovalue, wherevaluecan be any type.
length must be a compiled-time constant.
// good
fill(1n, 3) // numeric literal 3
fill(1n, M) // const M = 3
fill(1n, Demo.N) // `N` is a static readonly property of class `Demo`
Math
abs(a: bigint): bigintReturns the absolute value ofa.
abs(1n) // 1n
abs(0n) // 0n
abs(-1n) // 1n
min(a: bigint, b: bigint): bigintReturns the smallest ofaandb.
min(1n, 2n) // 1n
max(a: bigint, b: bigint): bigintReturns the lagest ofaandb.
max(1n, 2n) // 2n
within(x: bigint, min: bigint, max: bigint): booleanReturnstrueifxis within the specified range (left-inclusive and right-exclusive),falseotherwise.
within(0n, 0n, 2n) // true
within(1n, 0n, 2n) // true
within(2n, 0n, 2n) // false
Hashing
ripemd160(a: ByteString): Ripemd160Returns the RIPEMD160 hash result ofa.sha1(a: ByteString): Sha1Returns the SHA1 hash result ofa.sha256(a: ByteString): Sha256Returns the SHA256 hash result ofa.hash160(a: ByteString): Ripemd160Actually returnsripemd160(sha256(a)).pubKey2Addr(pk: PubKey): AddrWrapper function ofhash160.hash256(a: ByteString): Sha256Actually returnssha256(sha256(a)).
ByteString Operations
Basic types allowed to be used in @props and @methods are boolean and bigint, along with their wrapper types Boolean and BigInt.
A string literal is not allowed to be used directly without being converted into a ByteString.
@method()
public example(x: bigint, y: ByteString, z: boolean) {
assert(x == 5n)
assert(z)
// Strings must by converted to ByteString before being used
// in a smart contract:
assert(y == toByteString("hello world!", true))
// Vice versa, we can turn integers into ByteStrings:
assert(int32ToByteString(x) == toByteString('05'))
// Little-endian signed-magnitude representation is being used:
assert(int32ToByteString(-x) == toByteString('85'))
assert(int32ToByteString(-x * 1000n) == toByteString('8893'))
}
int32ToByteString(n: bigint): ByteStringIfsizeis omitted, convertnis converted to aByteStringin sign-magnitude little endian format, with as few bytes as possible (a.k.a., minimally encoded). Otherwise, converts the numbernto aByteStringof the specified size, including the sign bit; fails if the number cannot be accommodated.
// as few bytes as possible
int32ToByteString(128n) // '8000', little endian
int32ToByteString(127n) // '7f'
int32ToByteString(0n) // ''
int32ToByteString(-1n) // '81'
int32ToByteString(-129n) // '8180', little endian
len(a: ByteString): numberReturns the byte length ofa.
const s1 = toByteString('0011', false) // '0011', 2 bytes
len(s1) // 2
const s2 = toByteString('hello', true) // '68656c6c6f', 5 bytes
len(s2) // 5
SmartContract Methods
The following @methods come with the SmartContract base class.
loadArtifact
Function static loadArtifact(artifactFile: Artifact) loads the contract artifact file from the path you passed in to initialize the contract class.
If no parameter is passed when calling, the function will load the artifact file from the default directory. This is generally used during testing.
You can also pass the artifact path directly. This is usually used when the method is called when interacting with a contract at the front-end.
import { TicTacToe } from './contracts/tictactoe';
import artifact from '../artifacts/tictactoe.json';
TicTacToe.loadArtifact(artifact);
checkSig
Function checkSig(signature: Sig, publicKey: PubKey): boolean verifies an ECDSA signature. It takes two inputs: an ECDSA signature and a public key.
It returns if the signature matches the public key.
All signature checking functions (checkSig and checkMultiSig) follow the NULLFAIL rule: if the signature is invalid, the entire contract aborts and fails immediately, unless the signature is an empty ByteString, in which case these functions return false.
For example, Pay-to-Public-Key-Hash (P2PKH) can be implemented as below.
class P2PKH extends SmartContract {
// Address of the recipient.
@prop()
readonly address: Addr
constructor(address: Addr) {
super(...arguments)
this.address = address
}
@method()
public unlock(sig: Sig, pubkey: PubKey) {
// Check if the passed public key belongs to the specified public key hash.
assert(pubKey2Addr(pubkey) == this.address, 'address does not correspond to address')
// Check signature validity.
assert(this.checkSig(sig, pubkey), 'signature check failed')
}
}
checkMultiSig
Function checkMultiSig(signatures: Sig[], publickeys: PubKey[]): boolean verifies an array of ECDSA signatures. It takes two inputs: an array of ECDSA signatures and an array of public keys.
The function compares the first signature against each public key until it finds an ECDSA match. Starting with the subsequent public key, it compares the second signature against each remaining public key until it finds an ECDSA match. The process is repeated until all signatures have been checked or not enough public keys remain to produce a successful result. All signatures need to match a public key. Because public keys are not checked again if they fail any signature comparison, signatures must be placed in the signatures array using the same order as their corresponding public keys were placed in the publickeys array. If all signatures are valid, true is returned, false otherwise.
class MultiSigPayment extends SmartContract {
// Addresses of the 3 recipients.
@prop()
readonly addresses: FixedArray<Addr, 3>
constructor(addresses: FixedArray<Addr, 3>) {
super(...arguments)
this.addresses = addresses
}
@method()
public unlock(
signatures: FixedArray<Sig, 3>,
publicKeys: FixedArray<PubKey, 3>
) {
// Check if the passed public keys belong to the specified addresses.
for (let i = 0; i < 3; i++) {
assert(pubKey2Addr(publicKeys[i]) == this.addresses[i], 'address mismatch')
}
// Validate signatures.
assert(this.checkMultiSig(signatures, publicKeys), 'checkMultiSig failed')
}
}
buildChangeOutput
Function buildChangeOutput(): ByteString creates a change output.
class Auction extends SmartContract {
// ...
@method()
public bid(bidder: Addr, bid: bigint) {
// Addr
const auctionOutput: ByteString = ...
// Refund previous highest bidder.
const refundOutput: ByteString = ...
let outputs: ByteString = auctionOutput + refundOutput
// Add change output.
outputs += this.buildChangeOutput()
assert(sha256(outputs) == this.ctx.shaOutputs, 'shaOutputs check failed')
}
}
Specify the change address via PSBT:
const address = await signer.getAddress();
const feeRate = await provider.getFeeRate();
const psbt = new ExtPsbt();
psbt.addUTXO(utxos) // add inputs and outputs
.addCovenantOutput(covenant, satoshis)
.change(address, feeRate); // add change output explicitly
Standard Libraries
sCrypt comes with standard libraries that define many commonly used functions.
TxUtils
The TxUtils library provides a set of commonly used utility functions.
static buildOutput(outputScript: ByteString, outputSatoshis: ByteString): ByteStringBuild a transaction output with the specified script and satoshi amount.
const lockingScript = toByteString('01020304')
TxUtils.buildOutput(lockingScript, toByteString('0100000000000000')) // '01000000000000000401020304'
static buildP2PKHScript(pubKeyHash: PubKeyHash ): ByteStringBuild a Pay to Public Key Hash (P2PKH) script from a public key hash / address.
const address = Addr(toByteString('0011223344556677889900112233445566778899'))
TxUtils.buildP2PKHScript(address) // '76a914001122334455667788990011223344556677889988ac'
static buildP2PKHOutput(pubKeyHash: PubKeyHash, amount: ByteString): ByteStringBuild a P2PKH output from the public key hash.
const address = Addr(toByteString('0011223344556677889900112233445566778899'))
TxUtils.buildP2PKHOutput(address, toByteString('0100000000000000')) // '01000000000000001976a914001122334455667788990011223344556677889988ac'
static buildP2WPKHScript(pubKeyHash: PubKeyHash ): ByteStringBuild a Pay to witness public-key hash (P2WPKH) script from a public key hash / address.
const address = Addr(toByteString('0011223344556677889900112233445566778899'))
TxUtils.buildP2WPKHScript(address) // '00140011223344556677889900112233445566778899'
static buildP2WPKHOutput(pubKeyHash: PubKeyHash, amount: ByteString): ByteStringBuild a P2PKH output from the public key hash.
const address = Addr(toByteString('0011223344556677889900112233445566778899'))
TxUtils.buildP2WPKHOutput(address, toByteString('0100000000000000')) // '01000000000000003000140011223344556677889900112233445566778899'
static buildP2TRScript(pubKey: XOnlyPubKey): ByteStringBuild a Pay to witness public-key hash (P2WPKH) script from a public key hash / address.
const pubKey = XOnlyPubKey(toByteString('cb83ce14e6fcca547b00aaa64b99a533c3354bb24d49ceefca7b0cf856d13b64'))
TxUtils.buildP2TRScript(pubKey) // '5120cb83ce14e6fcca547b00aaa64b99a533c3354bb24d49ceefca7b0cf856d13b64'
static buildP2TROutput(pubKey: XOnlyPubKey, amount: ByteString): ByteStringBuild a P2PKH output from the public key hash.
const pubKey = XOnlyPubKey(toByteString('cb83ce14e6fcca547b00aaa64b99a533c3354bb24d49ceefca7b0cf856d13b64'))
TxUtils.buildP2TROutput(pubKey, toByteString('0100000000000000')) // '0100000000000000445120cb83ce14e6fcca547b00aaa64b99a533c3354bb24d49ceefca7b0cf856d13b64'
static buildOpreturnOutput(data: ByteString): ByteStringBuild a data-carrying OP_RETURN script fromdatapayload.
const data = toByteString('hello world', true)
TxUtils.buildOpreturnOutput(data) // '6a0b68656c6c6f20776f726c64'