Skip to content

Commit

Permalink
Update ordinal auction and bsv20 controlled mint to use scrypt-ord pa…
Browse files Browse the repository at this point in the history
…ckage.
  • Loading branch information
msinkec committed Oct 25, 2023
1 parent 92de36e commit 5bff8a0
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 162 deletions.
131 changes: 12 additions & 119 deletions src/contracts/bsv20Mint.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { BSV20V2 } from 'scrypt-ord'
import {
ByteString,
OpCode,
Addr,
SmartContract,
Utils,
byteString2Int,
hash256,
int2ByteString,
len,
method,
prop,
slice,
toByteString,
assert,
} from 'scrypt-ts'

export class BSV20Mint extends SmartContract {
export class BSV20Mint extends BSV20V2 {
@prop(true)
supply: bigint

Expand All @@ -25,39 +19,28 @@ export class BSV20Mint extends SmartContract {
@prop(true)
isFirstMint: boolean

@prop(true)
tokenId: ByteString

@prop(true)
lastUpdate: bigint

@prop()
timeDelta: bigint

@prop(true)
prevInscriptionLen: bigint

// Hex representation of bytes 0-255
@prop()
static readonly hexAsciiTable: ByteString = toByteString(
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff',
true
)

constructor(
id: ByteString,
max: bigint,
dec: bigint,
supply: bigint,
maxMintAmount: bigint,
lastUpdate: bigint,
timeDelta: bigint,
prevInscriptionLen: bigint
timeDelta: bigint
) {
super(...arguments)
super(id, max, dec)
this.init(...arguments)

this.supply = supply
this.maxMintAmount = maxMintAmount
this.tokenId = toByteString('')
this.lastUpdate = lastUpdate
this.timeDelta = timeDelta
this.prevInscriptionLen = prevInscriptionLen
}

@method()
Expand All @@ -74,112 +57,22 @@ export class BSV20Mint extends SmartContract {
// Check mint amount doesn't exceed maximum.
assert(amount <= this.maxMintAmount, 'mint amount exceeds maximum')

// If first mint, parse token id and store it in a state var
if (this.tokenId == toByteString('')) {
this.tokenId =
BSV20Mint.txidToAscii(this.ctx.utxo.outpoint.txid) +
toByteString('_', true) +
BSV20Mint.intToAscii(this.ctx.utxo.outpoint.outputIndex)
}

// Update supply.
this.supply -= amount

// If there are still tokens left, then
// build state output inscribed with leftover tokens.
let outputs = toByteString('')
if (this.supply > 0n) {
const transferInscription = BSV20Mint.buildTransferInsciption(
this.tokenId,
this.supply
)
const stateScript = slice(
this.getStateScript(),
this.prevInscriptionLen
) // Slice prev inscription
outputs += Utils.buildOutput(transferInscription + stateScript, 1n)

// Store next inscription length, so we know how much to slice in the next iteration.
this.prevInscriptionLen = len(transferInscription)
outputs += this.buildStateOutputFT(this.supply)
}

// Build P2PKH output to dest paying specified amount of tokens.
const destScript =
BSV20Mint.buildTransferInsciption(this.tokenId, amount) +
Utils.buildPublicKeyHashScript(dest)
outputs += Utils.buildOutput(destScript, 1n)
// Build FT P2PKH output to dest paying specified amount of tokens.
outputs += BSV20V2.buildTransferOutput(dest, this.id, amount)

// Build change output.
outputs += this.buildChangeOutput()

assert(hash256(outputs) == this.ctx.hashOutputs, 'hashOutputs mismatch')
}

// OP_FALSE OP_IF OP_DATA3 "ord" OP_1 OP_DATA18 "application/bsv-20" OP_FALSE <transfer-json> OP_ENDIF
// Transfer JSON example:
//{
// "p": "bsv-20",
// "op": "transfer",
// "id": "3b313338fa0555aebeaf91d8db1ffebd74773c67c8ad5181ff3d3f51e21e0000_1"
// "amt": "10000"
//}
@method()
static buildTransferInsciption(
tokenId: ByteString,
amt: bigint
): ByteString {
const json: ByteString =
toByteString('{"p":"bsv-20","op":"transfer","id":"', true) +
tokenId +
toByteString('","amt":"', true) +
BSV20Mint.intToAscii(amt) +
toByteString('"}', true)
return (
// OP_FALSE OP_IF OP_DATA3 "ord" OP_1 OP_DATA18 "application/bsv-20" OP_0
toByteString(
'0063036f726451126170706c69636174696f6e2f6273762d323000'
) +
OpCode.OP_PUSHDATA1 +
int2ByteString(len(json)) +
json +
OpCode.OP_ENDIF
)
}

// Converts integer to hex-encoded ASCII.
// 1000 -> '31303030'
// Input cannot be larger than 2^64-1.
@method()
static intToAscii(num: bigint): ByteString {
assert(
num >= 0n && num < 18446744073709551616n,
'value must be uint64 ' + num
)
let ascii = toByteString('', true)
let done = false
for (let i = 0; i < 20; i++) {
if (!done) {
const char = (num % 10n) + 48n
ascii = int2ByteString(char) + ascii
if (num > 9n) {
num = num / 10n
} else {
done = true
}
}
}
return ascii
}

@method()
static txidToAscii(txId: ByteString): ByteString {
let res = toByteString('')
for (let i = 0n; i < 32n; i++) {
const bytePos = 31n - i
const byte = slice(txId, bytePos, bytePos + 1n)
const hexPos = byteString2Int(byte + toByteString('00')) * 2n
res += slice(BSV20Mint.hexAsciiTable, hexPos, hexPos + 2n)
}
return res
}
}
1 change: 0 additions & 1 deletion src/contracts/ordinalAuction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
// https://xiaohuiliu.medium.com/integrate-ordinals-with-smart-contracts-on-bitcoin-part-2-d638b7ca3742

import Transaction = bsv.Transaction
import Address = bsv.Address
import Script = bsv.Script

export class OrdinalAuction extends SmartContract {
Expand Down
53 changes: 11 additions & 42 deletions tests/ordinalAuction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,7 @@ import { myPrivateKey, myPublicKey } from './utils/privateKey'
import { OrdinalAuction } from '../src/contracts/ordinalAuction'
import { signTx } from 'scryptlib'
import { expect } from 'chai'

async function deployOrdinal(dest: Addr, msg: string): Promise<UTXO> {
const signer = getDefaultSigner()
await signer.provider?.connect()

const address = await signer.getDefaultAddress()

// TODO: pick only as many utxos as needed
const utxos = await signer.listUnspent(address)

// Add msg as text/plain inscription.
const msgBuff = Buffer.from(msg, 'utf8')
const msgHex = msgBuff.toString('hex')
const inscription = bsv.Script.fromASM(
`OP_FALSE OP_IF 6f7264 OP_TRUE 746578742f706c61696e OP_FALSE ${msgHex} OP_ENDIF`
)

const unsignedTx = new bsv.Transaction()
.from(utxos)
.addOutput(
new bsv.Transaction.Output({
script: bsv.Script.fromHex(
Utils.buildPublicKeyHashScript(dest)
).add(inscription),
satoshis: 1,
})
)
.change(address)

const resp = await signer.signAndsendTransaction(unsignedTx, { address })

return {
txId: resp.id,
outputIndex: 0,
script: resp.outputs[0].script.toHex(),
satoshis: resp.outputs[0].satoshis,
}
}
import { OrdiNFTP2PKH } from 'scrypt-ord'

describe('Test SmartContract `OrdinalAuction`', () => {
const privateKeyAuctioneer = myPrivateKey
Expand All @@ -81,10 +44,16 @@ describe('Test SmartContract `OrdinalAuction`', () => {
bidderAddresses.push(addressBidder)
}

ordinalUTXO = await deployOrdinal(
Addr(addressAuctioneer.toByteString()),
'Hello, sCrypt!'
)
const ordinal = new OrdiNFTP2PKH(Addr(addressAuctioneer.toByteString()))
ordinal.connect(getDefaultSigner())
const ordinalTx = await ordinal.inscribeText('Hello, sCrypt!')

ordinalUTXO = {
txId: ordinalTx.id,
outputIndex: 0,
script: ordinalTx.outputs[0].script.toHex(),
satoshis: ordinalTx.outputs[0].satoshis,
}
console.log('Ordinal deployed:', ordinalUTXO.txId)

const ordinalPrevout: ByteString =
Expand Down

0 comments on commit 5bff8a0

Please sign in to comment.