diff --git a/src/contracts/bsv20V2.ts b/src/contracts/bsv20V2.ts index 8bf87e2..53b9616 100644 --- a/src/contracts/bsv20V2.ts +++ b/src/contracts/bsv20V2.ts @@ -188,9 +188,9 @@ export abstract class BSV20V2 extends SmartContract { | FTReceiver const tokenChangeAmt = Array.isArray(recipients) ? current.getAmt() - - recipients.reduce((acc, receiver) => { - return (acc += receiver.amt) - }, 0n) + recipients.reduce((acc, receiver) => { + return (acc += receiver.amt) + }, 0n) : current.getAmt() - recipients.amt if (tokenChangeAmt < 0n) { throw new Error(`Not enough tokens`) @@ -316,4 +316,29 @@ export abstract class BSV20V2 extends SmartContract { cloned.prependNOPScript(this.getPrependNOPScript()) return cloned } + + /** + * recover a `BSV20V2` instance from the transaction + * if the contract contains onchain properties of type `HashedMap` or `HashedSet` + * it's required to pass all their offchain raw data at this transaction moment + * @param tx transaction + * @param atOutputIndex output index of `tx` + * @param offchainValues the value of offchain properties, the raw data of onchain `HashedMap` and `HashedSet` properties, at this transaction moment + */ + static override fromTx( + this: new (...args: any[]) => T, + tx: bsv.Transaction, + atOutputIndex: number, + offchainValues?: Record + ): T { + const outputScript = tx.outputs[atOutputIndex].script + const nopScript = Ordinal.nopScriptFromScript(outputScript) + const instance = super.fromTx( + tx, + atOutputIndex, + offchainValues, + nopScript + ) + return instance as T + } } diff --git a/tests/specs/fromTx.spec.ts b/tests/specs/fromTx.spec.ts new file mode 100644 index 0000000..9289190 --- /dev/null +++ b/tests/specs/fromTx.spec.ts @@ -0,0 +1,72 @@ +import { Addr, PubKey, findSig, fromByteString, toByteString } from "scrypt-ts" +import { BSV20V1P2PKH, BSV20V2P2PKH, Ordinal } from "../scrypt-ord" +import { myAddress, myPublicKey } from "../utils/privateKey" +import { getDefaultSigner } from "../utils/txHelper" +import { expect, use } from "chai" +import chaiAsPromised from 'chai-as-promised' +use(chaiAsPromised) + +describe('Test `fromTx`', () => { + + it('BSV20V1P2PKH', async () => { + const tick = toByteString('abcd', true) + const max = 100n + const lim = 10n + const dec = 0n + const addr = Addr(myAddress.toByteString()) + // mint token + const instance = new BSV20V1P2PKH(tick, max, lim, dec, addr) + await instance.connect(getDefaultSigner()) + await instance.deployToken() + const mintTx = await instance.mint(lim) + // recover instance from the mint tx + const recoveredInstance = BSV20V1P2PKH.fromTx(mintTx, 0) + await recoveredInstance.connect(getDefaultSigner()) + // call + const callContract = async () => recoveredInstance.methods.unlock( + (sigResps) => findSig(sigResps, myPublicKey), + PubKey(myPublicKey.toHex()), + { + pubKeyOrAddrToSign: myPublicKey, + transfer: { + instance: new BSV20V1P2PKH(tick, max, lim, dec, addr), + amt: 1n + } + } + ) + return expect(callContract()).not.rejected + }) + + it('BSV20V2P2PKH', async () => { + const sym = toByteString('abcd', true) + const amt = 100n + const dec = 0n + const addr = Addr(myAddress.toByteString()) + // deploy token + const instance = new BSV20V2P2PKH(toByteString(''), sym, amt, dec, addr) + await instance.connect(getDefaultSigner()) + instance.prependNOPScript(Ordinal.createDeployV2( + fromByteString(instance.sym), + instance.max, + instance.dec, + )) + const deployTx = await instance.deploy(1) + const tokenId = toByteString(`${deployTx.id}_0`, true) + // recover instance from the deploy tx + const recoveredInstance = BSV20V2P2PKH.fromTx(deployTx, 0) + await recoveredInstance.connect(getDefaultSigner()) + // call + const callContract = async () => recoveredInstance.methods.unlock( + (sigResps) => findSig(sigResps, myPublicKey), + PubKey(myPublicKey.toHex()), + { + pubKeyOrAddrToSign: myPublicKey, + transfer: { + instance: new BSV20V2P2PKH(tokenId, sym, amt, dec, addr), + amt: 1n + } + } + ) + return expect(callContract()).not.rejected + }) +})