Skip to main content

Transfer Existing NFT to a Smart Contract

Suppose you would like to transfer an existing NFT that was already inscribed in the past, which is typically locked using a P2PKH lock. You can fetch all the needed data for the transfer by either using fromUTXO or getLatestInstance. The former takes the deployed NFT's current UTXO, while the latter takes the NFT's origin.

If the deployed NFT is locked using a regular P2PKH you may unlock it like the following:

const outpoint = '036718e5c603169b9981a55f276adfa7b5d024616ac95e048b05a81258ea2388_0';

// Create a P2PKH object from a UTXO
const utxo: UTXO = await OneSatApis.fetchUTXOByOutpoint(outpoint);
const p2pkh = OrdiNFTP2PKH.fromUTXO(utxo);
// Alternatively, create a P2PKH from an origin
const p2pkh = await OrdiNFTP2PKH.getLatestInstance(outpoint);

// Construct recipient smart contract
const message = toByteString('super secret', true);
const hash = sha256(message);
const recipient = new HashLockNFT(hash);
await recipient.connect(getDefaultSigner());

// Unlock deployed NFT and send it to the recipient hash lock contract
await p2pkh.connect(getDefaultSigner());

const { tx: transferTx } = await p2pkh.methods.unlock(
(sigResps) => findSig(sigResps, `yourPubKey`),
PubKey(`yourPubKey`.toByteString()),
{
transfer: recipient,
pubKeyOrAddrToSign: `yourPubKey`,
} as OrdiMethodCallOptions<OrdiNFTP2PKH>
);

console.log("Transferred NFT: ", transferTx.id);

Alternatively, if the NFT is locked using a smart contract, i.e. HashLockNFT:

HashLockNFT.loadArtifact();

// Retrieve `HashLockNFT` instance holding the NFT
const nft = await HashLockNFT.getLatestInstance(outpoint);
await nft.connect(getDefaultSigner());

const hash = sha256(toByteString('next super secret', true));
const recipient = new HashLockNFT(hash);
await recipient.connect(getDefaultSigner());

// Send NFT to recipient
const { tx: transferTx } = await nft.methods.unlock(
toByteString('super secret', true),
{
transfer: recipient,
}
);

console.log("Transferred NFT: ", transferTx.id);

buildStateOutputFT

Any instance of an OrdinalNFT contains the buildStateOutputNFT method. In contrast to the regular buildStateOutput method, this method also removes any inscription data that might be included in the smart contract's locking script. This is necessary because, within a stateful smart contract, we don't want the next iteration to re-inscribe the ordinal. Additionally, the buildStateOutputNFT method doesn't require a satoshi amount argument, as the amount is always 1 satoshi.

Below is an example of an ordinal counter contract:

class CounterNFT extends OrdinalNFT {

@prop(true)
counter: bigint

constructor(counter: bigint) {
super()
this.init(counter)
this.counter = counter
}

@method()
public incOnchain() {
this.counter++

...

let outputs = this.buildStateOutputNFT() // Does not include inscription in the next iteration.
outputs += this.buildChangeOutput()
assert(
this.ctx.hashOutputs == hash256(outputs),
'hashOutputs check failed'
)
}

}

See the complete code on GitHub.