From 182478cbf3c270e60e3a6f7b1be25853e2f1f0f3 Mon Sep 17 00:00:00 2001 From: scrypt Date: Fri, 22 Sep 2023 10:59:00 +0800 Subject: [PATCH] Fix pr --- src/contracts/bsv20V1.ts | 47 ++++++------ src/contracts/onesatNFT.ts | 104 +------------------------ src/contracts/ordP2PKH.ts | 14 +++- src/types.ts | 13 ++++ tests/specs/fromTxHashPuzzle.spec.ts | 4 +- tests/specs/hashPuzzle.spec.ts | 111 ++++++++++++++++++++------- tests/specs/skipTokenChange.spec.ts | 99 ------------------------ 7 files changed, 134 insertions(+), 258 deletions(-) delete mode 100644 tests/specs/skipTokenChange.spec.ts diff --git a/src/contracts/bsv20V1.ts b/src/contracts/bsv20V1.ts index 9f17313..b8fa751 100644 --- a/src/contracts/bsv20V1.ts +++ b/src/contracts/bsv20V1.ts @@ -25,18 +25,8 @@ import { import { Ordinal } from './ordinal' import { OrdP2PKH } from './ordP2PKH' import { fromByteString } from '../utils' -import { OneSatApis } from './1satApis' - -export type TokenReceiver = { - instance: BSV20V1 | OrdP2PKH - amt: bigint -} - -export interface OrdMethodCallOptions extends MethodCallOptions { - transfer: Array - tokenChangeAddress?: bsv.Address - skipTokenChange?: boolean -} +import { OneSatApis } from '../1satApis' +import { FTMethodCallOptions, FTReceiver } from '../types' /** * A base class implementing the bsv20 v1 protocol @@ -173,14 +163,15 @@ export class BSV20V1 extends SmartContract { ): MethodCallTxBuilder { return async function ( current: BSV20V1, - options: OrdMethodCallOptions, + options: FTMethodCallOptions, ...args ): Promise { - const tokenChangeAmt = - current.getAmt() - - options.transfer.reduce((acc, receiver) => { - return (acc += receiver.amt) - }, 0n) + const tokenChangeAmt = Array.isArray(options.transfer) + ? current.getAmt() - + options.transfer.reduce((acc, receiver) => { + return (acc += receiver.amt) + }, 0n) + : options.transfer.amt if (tokenChangeAmt < 0n) { throw new Error(`Not enough tokens`) } @@ -193,11 +184,7 @@ export class BSV20V1 extends SmartContract { tx.addInput(current.buildContractInput()) - for (let i = 0; i < options.transfer.length; i++) { - const receiver = options.transfer[i] - - await receiver.instance.connect(current.signer) - + function addReceiver(receiver: FTReceiver) { if (receiver.instance instanceof BSV20V1) { receiver.instance.setAmt(receiver.amt) } else if (receiver.instance instanceof OrdP2PKH) { @@ -222,6 +209,14 @@ export class BSV20V1 extends SmartContract { atOutputIndex: nexts.length, }) } + if (Array.isArray(options.transfer)) { + for (let i = 0; i < options.transfer.length; i++) { + const receiver = options.transfer[i] + addReceiver(receiver) + } + } else { + addReceiver(options.transfer) + } if (tokenChangeAmt > 0n && options.skipTokenChange !== true) { const tokenChangeAddress = options.tokenChangeAddress @@ -265,7 +260,7 @@ export class BSV20V1 extends SmartContract { static async transfer( senders: Array, signer: Signer, - receivers: Array + receivers: Array ) { const ordPubKey = await signer.getDefaultPubKey() @@ -302,8 +297,10 @@ export class BSV20V1 extends SmartContract { if (receiver.instance instanceof BSV20V1) { receiver.instance.setAmt(receiver.amt) - } else { + } else if (receiver.instance instanceof OrdP2PKH) { receiver.instance.setBSV20(tick, receiver.amt) + } else { + throw new Error('unsupport receiver, only BSV20V1 or OrdP2PKH!') } tx.addOutput( diff --git a/src/contracts/onesatNFT.ts b/src/contracts/onesatNFT.ts index 794229b..203772c 100644 --- a/src/contracts/onesatNFT.ts +++ b/src/contracts/onesatNFT.ts @@ -7,18 +7,13 @@ import { SmartContract, toByteString, Utils, - UTXO, assert, prop, bsv, - MethodCallOptions, - ContractTransaction, - StatefulNext, } from 'scrypt-ts' import { Inscription } from '../types' import { Ordinal } from './ordinal' -import { signTx } from 'scryptlib' -import { OneSatApis } from './1satApis' +import { OneSatApis } from '../1satApis' export class OneSatNFT extends SmartContract { @prop(true) @@ -71,100 +66,7 @@ export class OneSatNFT extends SmartContract { }) } - async transfer(receiver: OneSatNFT, methodName: string, ...args) { - const builder = this['_txBuilders'].has(methodName) - - if (!builder) { - this.bindTxBuilder( - methodName, - async ( - current: OneSatNFT, - options: MethodCallOptions - ): Promise => { - const bsvChangeAddress = - await this.signer.getDefaultAddress() - - const nexts: StatefulNext[] = [] - const tx = new bsv.Transaction() - - tx.addInput(current.buildContractInput()) - - await receiver.connect(this.signer) - - tx.addOutput( - new bsv.Transaction.Output({ - script: receiver.lockingScript, - satoshis: 1, - }) - ) - - nexts.push({ - instance: receiver, - balance: 1, - atOutputIndex: nexts.length, - }) - - tx.change(bsvChangeAddress) - - return Promise.resolve({ - tx, - atInputIndex: 0, - nexts: nexts, - }) - } - ) - } - - return this.methods[methodName](...args) - } - - static send2Contract( - ordinalUtxo: UTXO, - ordPk: bsv.PrivateKey, - instance: SmartContract - ) { - instance.buildDeployTransaction = ( - utxos: UTXO[], - amount: number, - changeAddress?: bsv.Address | string - ): Promise => { - const deployTx = new bsv.Transaction() - - deployTx.from(ordinalUtxo).addOutput( - new bsv.Transaction.Output({ - script: instance.lockingScript, - satoshis: amount, - }) - ) - - if (changeAddress) { - deployTx.change(changeAddress) - } - const lockingScript = bsv.Script.fromHex(ordinalUtxo.script) - - const sig = signTx( - deployTx, - ordPk, - lockingScript, - amount, - 0, - bsv.crypto.Signature.ANYONECANPAY_SINGLE - ) - - deployTx.inputs[0].setScript( - bsv.Script.buildPublicKeyHashIn( - ordPk.publicKey, - bsv.crypto.Signature.fromTxFormat(Buffer.from(sig, 'hex')), - bsv.crypto.Signature.ANYONECANPAY_SINGLE - ) - ) - - return Promise.resolve(deployTx) - } - return instance.deploy(1) - } - - public static async getLatestInstanceByOrigin( + public static async getLatestInstanceByOrigin( clazz: new (...args: any) => T, origin: string ): Promise { @@ -176,7 +78,7 @@ export class OneSatNFT extends SmartContract { const insciptionScript = Ordinal.getInsciptionScript(utxo.script) - const instance = (clazz as unknown as typeof SmartContract).fromUTXO( + const instance = (clazz as unknown as typeof OneSatNFT).fromUTXO( utxo, {}, bsv.Script.fromHex(insciptionScript) diff --git a/src/contracts/ordP2PKH.ts b/src/contracts/ordP2PKH.ts index 94452a6..085bb7b 100644 --- a/src/contracts/ordP2PKH.ts +++ b/src/contracts/ordP2PKH.ts @@ -20,7 +20,7 @@ import { } from 'scrypt-ts' import { Inscription } from '../types' import { Ordinal } from './ordinal' -import { OneSatApis } from './1satApis' +import { OneSatApis } from '../1satApis' export class OrdP2PKH extends SmartContract { // Address of the recipient. @@ -77,8 +77,16 @@ export class OrdP2PKH extends SmartContract { static fromAddress(address: string | bsv.Address | bsv.PublicKey) { OrdP2PKH.loadArtifact(desc) - const s = bsv.Script.buildPublicKeyHashOut(address) - return new OrdP2PKH(Addr(toHex(s.chunks[2].buf))) + let addr: Addr + + if (typeof address === 'string') { + addr = Addr(bsv.Address.fromString(address).toByteString()) + } else if (address instanceof bsv.Address) { + addr = Addr(address.toByteString()) + } else { + addr = Addr(bsv.Address.fromPublicKey(address).toByteString()) + } + return new OrdP2PKH(addr) } static fromP2PKHUTXO(utxo: UTXO): OrdP2PKH { diff --git a/src/types.ts b/src/types.ts index ba88e95..fbc0d29 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { MethodCallOptions, SmartContract, bsv } from 'scrypt-ts' + /** Ordinal Inscription */ export type Inscription = { /** content in text */ @@ -11,3 +13,14 @@ export type BSV20Protocol = { tick: string amt: string } + +export type FTReceiver = { + instance: SmartContract + amt: bigint +} + +export interface FTMethodCallOptions extends MethodCallOptions { + transfer: Array | FTReceiver + tokenChangeAddress?: bsv.Address + skipTokenChange?: boolean +} diff --git a/tests/specs/fromTxHashPuzzle.spec.ts b/tests/specs/fromTxHashPuzzle.spec.ts index 46b4156..aebe121 100644 --- a/tests/specs/fromTxHashPuzzle.spec.ts +++ b/tests/specs/fromTxHashPuzzle.spec.ts @@ -5,7 +5,7 @@ import { HashPuzzle } from '../contracts/hashPuzzle' import { getDefaultSigner } from '../utils/txHelper' import chaiAsPromised from 'chai-as-promised' -import { OrdMethodCallOptions, OrdP2PKH, Ordinal } from '../scrypt-ord' +import { FTMethodCallOptions, OrdP2PKH, Ordinal } from '../scrypt-ord' use(chaiAsPromised) describe('Test BSV20 fromUTXO', () => { @@ -51,7 +51,7 @@ describe('Test BSV20 fromUTXO', () => { amt: 6n, }, ], - } as OrdMethodCallOptions) + } as FTMethodCallOptions) console.log('withdraw bsv20 to p2pkh: ', tx.id) } diff --git a/tests/specs/hashPuzzle.spec.ts b/tests/specs/hashPuzzle.spec.ts index c55a62a..07c8672 100644 --- a/tests/specs/hashPuzzle.spec.ts +++ b/tests/specs/hashPuzzle.spec.ts @@ -5,7 +5,7 @@ import { HashPuzzle } from '../contracts/hashPuzzle' import { getDefaultSigner } from '../utils/txHelper' import chaiAsPromised from 'chai-as-promised' -import { OrdP2PKH, TokenReceiver } from '../scrypt-ord' +import { FTMethodCallOptions, OrdP2PKH, FTReceiver } from '../scrypt-ord' use(chaiAsPromised) describe('Test SmartContract `HashPuzzle`', () => { @@ -29,19 +29,21 @@ describe('Test SmartContract `HashPuzzle`', () => { await hashPuzzle.mint(amt) }) - it('transfer to an other hashPuzzle and withdraw.', async () => { + it('transfer to an other hashPuzzle.', async () => { const callContract = async () => { for (let i = 0; i < 3; i++) { - const recipients: Array = [ + const receiver = new HashPuzzle( + tick, + max, + lim, + sha256(toByteString(`hello, sCrypt!:${i + 1}`, true)) + ) + + await receiver.connect(getDefaultSigner()) + + const recipients: Array = [ { - instance: new HashPuzzle( - tick, - max, - lim, - sha256( - toByteString(`hello, sCrypt!:${i + 1}`, true) - ) - ), + instance: receiver, amt: 10n, }, ] @@ -57,26 +59,79 @@ describe('Test SmartContract `HashPuzzle`', () => { console.log('transfer tx: ', tx.id) } + } - const withdraw = async () => { - const address = await hashPuzzle.signer.getDefaultAddress() - const recipients = [ - { - instance: OrdP2PKH.fromAddress(address), - amt: hashPuzzle.getAmt(), - }, - ] + await expect(callContract()).not.rejected + }) - const { tx } = await hashPuzzle.methods.unlock( - toByteString(`hello, sCrypt!:3`, true), - { - transfer: recipients, - } - ) + it('transfer to an other hashPuzzle with change.', async () => { + const callContract = async () => { + const receiver = new HashPuzzle( + tick, + max, + lim, + sha256(toByteString(`hello, sCrypt!`, true)) + ) - console.log('withdraw tx: ', tx.id) - } - await expect(withdraw()).not.rejected + await receiver.connect(getDefaultSigner()) + + const recipients: Array = [ + { + instance: receiver, + amt: 9n, + }, + ] + + const { tx, nexts } = await hashPuzzle.methods.unlock( + toByteString(`hello, sCrypt!:3`, true), + { + transfer: recipients, + } as FTMethodCallOptions + ) + + console.log('transfer tx: ', tx.id) + + expect(nexts.length === 2).to.be.true + + const p2pkh = nexts[1].instance as OrdP2PKH + + expect(p2pkh.getBSV20Amt()).to.be.equal(1n) + + hashPuzzle = recipients[0].instance as HashPuzzle + } + + await expect(callContract()).not.rejected + }) + + it('transfer to an other hashPuzzle without change.', async () => { + const callContract = async () => { + const receiver = new HashPuzzle( + tick, + max, + lim, + sha256(toByteString(`hello, sCrypt!`, true)) + ) + + await receiver.connect(getDefaultSigner()) + + const recipients: Array = [ + { + instance: receiver, + amt: 9n, + }, + ] + + const { tx, nexts } = await hashPuzzle.methods.unlock( + toByteString(`hello, sCrypt!`, true), + { + transfer: recipients, + skipTokenChange: true, + } as FTMethodCallOptions + ) + + console.log('transfer tx: ', tx.id) + + expect(nexts.length === 1).to.be.true } await expect(callContract()).not.rejected diff --git a/tests/specs/skipTokenChange.spec.ts b/tests/specs/skipTokenChange.spec.ts deleted file mode 100644 index 9749d43..0000000 --- a/tests/specs/skipTokenChange.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { expect, use } from 'chai' -import { sha256, toByteString } from 'scrypt-ts' -import { HashPuzzle } from '../contracts/hashPuzzle' -import { getDefaultSigner } from '../utils/txHelper' - -import chaiAsPromised from 'chai-as-promised' -import { - BSV20V1, - OrdMethodCallOptions, - OrdP2PKH, - TokenReceiver, -} from '../scrypt-ord' -use(chaiAsPromised) - -describe('Test skipTokenChange', () => { - const tick = toByteString('DOGE', true) - const max = 100000n - const lim = max / 10n - const amt = 1000n - - let hashPuzzle: HashPuzzle - before(async () => { - await HashPuzzle.loadArtifact() - hashPuzzle = new HashPuzzle( - tick, - max, - lim, - sha256(toByteString('hello, sCrypt!', true)) - ) - await hashPuzzle.connect(getDefaultSigner()) - - await hashPuzzle.deployToken() - await hashPuzzle.mint(amt) - }) - - it('transfer to an other hashPuzzle with change.', async () => { - const callContract = async () => { - const recipients: Array = [ - { - instance: new HashPuzzle( - tick, - max, - lim, - sha256(toByteString(`hello, sCrypt!`, true)) - ), - amt: 10n, - }, - ] - - const { tx, nexts } = await hashPuzzle.methods.unlock( - toByteString(`hello, sCrypt!`, true), - { - transfer: recipients, - } as OrdMethodCallOptions - ) - - console.log('transfer tx: ', tx.id) - - expect(nexts.length === 2).to.be.true - - const p2pkh = nexts[1].instance as OrdP2PKH - - expect(p2pkh.getBSV20Amt()).to.be.equal(990n) - } - - await expect(callContract()).not.rejected - }) - - it('transfer to an other hashPuzzle without change.', async () => { - const callContract = async () => { - const recipients: Array = [ - { - instance: new HashPuzzle( - tick, - max, - lim, - sha256(toByteString(`hello, sCrypt!`, true)) - ), - amt: 10n, - }, - ] - - const { tx, nexts } = await hashPuzzle.methods.unlock( - toByteString(`hello, sCrypt!`, true), - { - transfer: recipients, - skipTokenChange: true, - } as OrdMethodCallOptions - ) - - console.log('transfer tx: ', tx.id) - - expect(nexts.length === 1).to.be.true - } - - await expect(callContract()).not.rejected - }) -})