Skip to content

Commit

Permalink
Fix bsv-20 sell limit order and add test.
Browse files Browse the repository at this point in the history
  • Loading branch information
msinkec committed Dec 21, 2023
1 parent f619e58 commit 9f95884
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 23 deletions.
83 changes: 69 additions & 14 deletions src/contracts/bsv20SellLimitOrder.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { BSV20V2 } from 'scrypt-ord'
import { BSV20V2, Ordinal } from 'scrypt-ord'
import {
ByteString,
PubKey,
Addr,
Sig,
Utils,
hash256,
method,
prop,
pubKey2Addr,
assert,
len,
toByteString,
slice,
int2ByteString,
hash160,
MethodCallOptions,
ContractTransaction,
bsv,
Addr,
StatefulNext,
} from 'scrypt-ts'

/**
Expand Down Expand Up @@ -56,7 +57,7 @@ export class BSV20SellLimitOrder extends BSV20V2 {
}

@method()
public buy(amount: bigint) {
public buy(amount: bigint, buyer: Addr) {
// Check token amount doesn't exceed total.
assert(
this.tokenAmtSold + amount < this.tokenAmt,
Expand All @@ -71,20 +72,16 @@ export class BSV20SellLimitOrder extends BSV20V2 {
const tokensRemaining = this.tokenAmt - this.tokenAmtSold
let outputs = toByteString('')
if (tokensRemaining > 0n) {
outputs = this.buildStateOutput(1n)
outputs = this.buildStateOutputFT(tokensRemaining)
}

// Ensure the sold tokens are being payed out to the buyer.
outputs += BSV20V2.buildTransferOutput(
pubKey2Addr(this.seller),
this.id,
amount
)
outputs += BSV20V2.buildTransferOutput(buyer, this.id, amount)

// Ensure the next output is paying the to the Bitcoin to the seller.
// Ensure the next output is paying the Bitcoin to the seller.
const satsForSeller = this.pricePerUnit * amount
outputs += Utils.buildPublicKeyHashOutput(
hash160(this.seller),
pubKey2Addr(this.seller),
satsForSeller
)

Expand All @@ -99,4 +96,62 @@ export class BSV20SellLimitOrder extends BSV20V2 {
public cancel(buyerSig: Sig) {
assert(this.checkSig(buyerSig, this.seller))
}

static async buyTxBuilder(
current: BSV20SellLimitOrder,
options: MethodCallOptions<BSV20SellLimitOrder>,
amount: bigint,
buyer: Addr
): Promise<ContractTransaction> {
const defaultAddress = await current.signer.getDefaultAddress()

const next = current.next()
next.tokenAmtSold += amount
const tokensRemaining = next.tokenAmt - next.tokenAmtSold

next.setAmt(tokensRemaining)

const tx = new bsv.Transaction().addInput(current.buildContractInput())

if (tokensRemaining > 0n) {
const stateOut = new bsv.Transaction.Output({
script: next.lockingScript,
satoshis: 1,
})
tx.addOutput(stateOut)
}
const buyerOut = BSV20SellLimitOrder.buildTransferOutput(
buyer,
next.id,
amount
)
tx.addOutput(
bsv.Transaction.Output.fromBufferReader(
new bsv.encoding.BufferReader(Buffer.from(buyerOut, 'hex'))
)
)

const satsForSeller = next.pricePerUnit * amount
const paymentOut = new bsv.Transaction.Output({
script: bsv.Script.fromHex(
Utils.buildPublicKeyHashScript(pubKey2Addr(next.seller))
),
satoshis: Number(satsForSeller),
})
tx.addOutput(paymentOut)

tx.change(options.changeAddress || defaultAddress)

return {
tx,
atInputIndex: 0,
nexts: [
{
instance: next,
balance: 1,
atOutputIndex: 0,
} as StatefulNext<BSV20SellLimitOrder>,
],
}
}
}
18 changes: 9 additions & 9 deletions tests/bsv20Auction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async function main() {

const ordinalUTXO = bsv20p2pkh.utxo

console.log('Mock BSV-20 ordinal deployed:', ordinalUTXO.txId)
console.log('Mock BSV-20 tokens deployed:', ordinalUTXO.txId)

await sleep(3)

Expand Down Expand Up @@ -91,7 +91,7 @@ async function main() {
const nextInstance = currentInstance.next()
nextInstance.bidder = newHighestBidder

const contractTx = await currentInstance.methods.bid(
const callRes = await currentInstance.methods.bid(
newHighestBidder,
bid,
{
Expand All @@ -103,7 +103,7 @@ async function main() {
} as MethodCallOptions<BSV20Auction>
)

console.log('Bid Tx:', contractTx.tx.id)
console.log('Bid Tx:', callRes.tx.id)

balance += Number(bid)
currentInstance = nextInstance
Expand Down Expand Up @@ -182,7 +182,7 @@ async function main() {
}
)

let contractTx = await currentInstance.methods.close(
let callRes = await currentInstance.methods.close(
(sigResps) => findSig(sigResps, publicKeyAuctioneer),
{
pubKeyOrAddrToSign: publicKeyAuctioneer,
Expand All @@ -196,15 +196,15 @@ async function main() {

// If we would like to broadcast, here we need to sign ordinal UTXO input.
const ordinalSig = signTx(
contractTx.tx,
callRes.tx,
privateKeyAuctioneer,
bsv.Script.fromHex(ordinalUTXO.script),
ordinalUTXO.satoshis,
0,
bsv.crypto.Signature.ANYONECANPAY_SINGLE
)

contractTx.tx.inputs[0].setScript(
callRes.tx.inputs[0].setScript(
bsv.Script.fromASM(
`${ordinalSig} ${publicKeyAuctioneer.toByteString()}`
)
Expand All @@ -218,14 +218,14 @@ async function main() {
options: MethodCallOptions<BSV20Auction>
) => {
return Promise.resolve({
tx: contractTx.tx,
tx: callRes.tx,
atInputIndex: 1,
nexts: [],
})
}
)

contractTx = await currentInstance.methods.close(
callRes = await currentInstance.methods.close(
(sigResps) => findSig(sigResps, publicKeyAuctioneer),
{
pubKeyOrAddrToSign: publicKeyAuctioneer,
Expand All @@ -235,7 +235,7 @@ async function main() {
} as MethodCallOptions<BSV20Auction>
)

console.log('Close Tx: ', contractTx.tx.id)
console.log('Close Tx: ', callRes.tx.id)
}

describe('Test SmartContract `BSV20Auction`', () => {
Expand Down
102 changes: 102 additions & 0 deletions tests/bsv20SellLimitOrder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { getDefaultSigner, sleep } from './utils/helper'
import { Addr, bsv, findSig, PubKey, toByteString, UTXO } from 'scrypt-ts'
import { myAddress, myPrivateKey, myPublicKey } from './utils/privateKey'
import { BSV20SellLimitOrder } from '../src/contracts/bsv20SellLimitOrder'
import { BSV20V2P2PKH, OrdiMethodCallOptions } from 'scrypt-ord'

async function main() {
BSV20SellLimitOrder.loadArtifact()

const privateKeySeller = myPrivateKey
const publicKeySeller = myPublicKey

///// Mint some tokens to a P2PKH first. /////
const max = 1000000000000000n // Whole token amount.
const dec = 8n // Decimal precision.
const sym = toByteString('TEST', true)
const pricePerUnit = 10n

const bsv20p2pkh = new BSV20V2P2PKH(
toByteString(''),
sym,
max,
dec,
Addr(publicKeySeller.toAddress().toByteString())
)
await bsv20p2pkh.connect(getDefaultSigner(privateKeySeller))
const tokenId = await bsv20p2pkh.deployToken()
const ordinalUTXO = bsv20p2pkh.utxo

console.log('Mock BSV-20 tokens deployed:', ordinalUTXO.txId)

///// Transfer tokens to a sell order instance. /////
const transferAmt = 100000n
const instance = new BSV20SellLimitOrder(
toByteString(tokenId, true),
sym,
max,
dec,
transferAmt,
PubKey(publicKeySeller.toByteString()),
pricePerUnit
)
await instance.connect(getDefaultSigner(privateKeySeller))

const { tx: transferTx } = await bsv20p2pkh.methods.unlock(
(sigResps) => findSig(sigResps, publicKeySeller),
PubKey(publicKeySeller.toByteString()),
{
transfer: {
instance,
amt: transferAmt,
},
pubKeyOrAddrToSign: publicKeySeller,
} as OrdiMethodCallOptions<BSV20V2P2PKH>
)

console.log('BSV-20 sell order deployed: ', transferTx.id)

///// Perform buying. /////
let currentInstance = instance
for (let i = 0; i < 3; i++) {
const amount = 100n
const buyer = myAddress

currentInstance.bindTxBuilder('buy', BSV20SellLimitOrder.buyTxBuilder)
const callRes = await currentInstance.methods.buy(
amount,
Addr(buyer.toByteString())
)

console.log('Buy Tx:', callRes.tx.id)

currentInstance = callRes.nexts[0].instance
}

////// Cancel sell limit order and transfer remaining tokens back to seller. /////
const recipient = new BSV20V2P2PKH(
toByteString(tokenId, true),
sym,
max,
dec,
Addr(publicKeySeller.toAddress().toByteString())
)
const contractTx = await currentInstance.methods.cancel(
(sigResps) => findSig(sigResps, publicKeySeller),
{
transfer: {
instance: recipient,
amt: currentInstance.tokenAmt - currentInstance.tokenAmtSold,
},
pubKeyOrAddrToSign: publicKeySeller,
} as OrdiMethodCallOptions<BSV20SellLimitOrder>
)

console.log('Cancel Tx: ', contractTx.tx.id)
}

describe('Test SmartContract `BSV20SellLimitOrder`', () => {
it('should succeed', async () => {
await main()
})
})

0 comments on commit 9f95884

Please sign in to comment.