Starknet NFT Purchase

This example uses function triggers to detect calls to the matchAskWithTakerBid function, parses the related transaction and then emits the NFT purchase details.

{
    "type": "TRIGGER_TYPE_FUNCTION",
    "functionSignature": "0x03e2bd66aeb9284521dbba619e698cd99508e215c152bf788b608349e67bba61"
}
import {
    Contract,
    num,
    RpcProvider,
    shortString
} from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";

const providerString = "http://chain-provider";

const rpcProvider = new RpcProvider({ nodeUrl: providerString });

const flexContractAddr =
    "0x4b1b3fdf34d00288a7956e6342fb366a1510a9387d321c87f3301d990ac19d4";

export async function triggerHandler(context, data) {
    if (
        context.dataType !== "DATA_TYPE_TRACE" ||
        data.contract_address !== flexContractAddr
    ) {
        return;
    }

    // Flex is a PROXY contract and so has a library_call with the same method and arguments. We only parse the initial call.
    if (data.call_type != "CALL") {
        return;
    }

    // types explained here - https://docs.looksrare.org/developers/taker-orders
    const takerOrder = getTakerOrder(data.calldata);
    const makerOrder = getMakerOrder(data.calldata);
    if (
        takerOrder.lowerTokenId !== makerOrder.lowerTokenId &&
        takerOrder.higherTokenId !== makerOrder.higherTokenId
    ) {
        return {
            error: "Expected to see the same tokenId in both orders",
            taker: takerOrder,
            maker: makerOrder,
            context: context
        };
    }

    // The token that is used as a payment for the nft
    const erc20ContractAddr = makerOrder.currency;
    const paymentTokenInfo = await getPaymentTokenInfo(
        erc20ContractAddr,
        Number(context.blockNumber)
    );

    // The contract that manages the nft
    const erc721ContractAddr = makerOrder.nftCollectionContract;
    const nftInfo = await getNTFInfo(
        erc721ContractAddr,
        { low: takerOrder.lowerTokenId, high: takerOrder.higherTokenId },
        Number(context.blockNumber)
    );

    return {
        transferData: {
            paidToken: paymentTokenInfo.symbol,

            price: takerOrder.price,
            nftCollection: nftInfo.symbol,
            tokenURI: nftInfo.uri,

            taker: takerOrder.takerAddr,
            maker: makerOrder.nftCollectionContract
        },
        context: context
    };
}

function getTakerOrder(callData) {
    const startIndex = 0;
    return {
        isOrderAsk: callData[startIndex],
        takerAddr: callData[startIndex + 1],
        price: callData[startIndex + 2],
        lowerTokenId: callData[startIndex + 3], // two felt struct
        higherTokenId: callData[startIndex + 4] // two felt struct
    };
}

function getMakerOrder(callData) {
    const startIndex = 7;
    return {
        isOrderAsk: callData[startIndex],
        signer: callData[startIndex + 1],
        nftCollectionContract: callData[startIndex + 2],
        price: callData[startIndex + 3],
        lowerTokenId: callData[startIndex + 4], // two felt struct
        higherTokenId: callData[startIndex + 5], // two felt struct
        amount: callData[startIndex + 6],
        strategy: callData[startIndex + 7],
        currency: callData[startIndex + 8]
    };
}

async function getPaymentTokenInfo(contractAddress, blockNumber) {
    const erc20Abi = [
        {
            name: "symbol",
            type: "function",
            inputs: [],
            outputs: [
                {
                    name: "symbol",
                    type: "felt"
                }
            ],
            stateMutability: "view"
        }
    ];
    const erc20Contract = new Contract(erc20Abi, contractAddress, rpcProvider);
    return {
        symbol: await erc20Contract
            .symbol({ blockIdentifier: blockNumber })
            .then((result) => shortString.decodeShortString(result.symbol))
    };
}

async function getNTFInfo(contractAddress, tokenId, blockNumber) {
    const nftAbi = [
        {
            name: "Uint256",
            size: 2,
            type: "struct",
            members: [
                {
                    name: "low",
                    type: "felt",
                    offset: 0
                },
                {
                    name: "high",
                    type: "felt",
                    offset: 1
                }
            ]
        },
        {
            name: "tokenURI",
            type: "function",
            inputs: [
                {
                    name: "tokenId",
                    type: "Uint256"
                }
            ],
            outputs: [
                {
                    name: "tokenURI_len",
                    type: "felt"
                },
                {
                    name: "tokenURI",
                    type: "felt*"
                }
            ],
            stateMutability: "view"
        },
        {
            name: "symbol",
            type: "function",
            inputs: [],
            outputs: [
                {
                    name: "symbol",
                    type: "felt"
                }
            ],
            stateMutability: "view"
        }
    ];
    const nftContract = new Contract(nftAbi, contractAddress, rpcProvider);

    return {
        uri: await nftContract
            .tokenURI(tokenId, {
                blockIdentifier: blockNumber
            })
            .then((result) =>
                result.tokenURI
                    .map((shortStr) => {
                        return shortString.decodeShortString(
                            num.toHex(shortStr)
                        );
                    })
                    .join("")
            ),
        symbol: await nftContract
            .symbol({ blockIdentifier: blockNumber })
            .then((result) => shortString.decodeShortString(result.symbol))
    };
}