How to call Openbrush contract from Front-end app - smartcontracts

I implement smart contracts with ink!, substrate's WASM smart contract implementation language.
At that time, I decided to use the openbrush library. openbrush is like openzeppelin in EVM.
The smart contract was easy to implement according to the official website. [https://docs.openbrush.io/]
But I couldn't figure out how to call it from the front end.
I was able to solve it by looking at Telegram, but I will write this in the hope that it will help others.
A function defined in openbrush is declared like this:
psp34::transfer (to: TransferInput1, id: TransferInput2, data: TransferInput3)
psp34::ownerOf (id: OwnerOfInput1): Option<AccountId>

Below is a sample code that calls the above two contract functions.
const wsProvider = new WsProvider(blockchainUrl);
const api = await ApiPromise.create({ provider: wsProvider });
const contract = new ContractPromise(api, psp_abi, PSP_ADDRESS);
setApi(api);
const { gasConsumed, result, output } = await contract.query['psp34::ownerOf'](
actingAddress,
{ value: 0, gasLimit: -1 },
NFT_ID
);
const { web3FromSource } = await import("#polkadot/extension-dapp");
const wsProvider = new WsProvider(blockchainUrl);
const api = await ApiPromise.create({ provider: wsProvider });
setApi(api);
const contract = new ContractPromise(api, psp_abi, PSP_ADDRESS);
const performingAccount = accounts[0];
const injector = await web3FromSource(performingAccount.meta.source);
const flip = await contract.tx['psp34::transfer'](
{ value: 0, gasLimit: gasLimit },
"ajYMsCKsEAhEvHpeA4XqsfiA9v1CdzZPrCfS6pEfeGHW9j8",
NFT_ID,
Null
);
if (injector !== undefined) {
const unsub = await flip.signAndSend(
actingAddress,
{ signer: injector.signer },
({ events = [], status }) => {
-- snip --

Related

Problems with deploying first smart contracts

Good day everyone!
I'm trying to deploy the first smart contracts according to the helloWorld manuals, but I'm getting an error please see the snippet. Tell me, please, what can be done about this? Tried lot of way to fix it but failed.
Thanks in advance!
import { Command } from ""commander"";
import prompts, { PromptObject } from ""prompts"";
import { isNumeric, Migration } from ""./utils"";
const program = new Command();
const migration = new Migration();
async function main() {
const promptsData: PromptObject[] = [];
program
.allowUnknownOption()
.option(""-kn, --key_number <key_number>"", ""Public key number"")
.option(
""-b, --balance <balance>"",
""Initial balance in EVERs (will send from Giver)"",
);
program.parse(process.argv);
const options = program.opts();
if (!options.key_number) {
promptsData.push({
type: ""text"",
name: ""keyNumber"",
message: ""Public key number"",
validate: value => (isNumeric(value) ? true : ""Invalid number""),
});
}
if (!options.balance) {
promptsData.push({
type: ""text"",
name: ""balance"",
message: ""Initial balance (will send from Giver)"",
validate: value => (isNumeric(value) ? true : ""Invalid number""),
});
}
const response = await prompts(promptsData);
const keyNumber = +(options.key_number || response.keyNumber);
const balance = +(options.balance || response.balance);
const signer = (await locklift.keystore.getSigner(keyNumber.toString()))!;
let accountsFactory = locklift.factory.getAccountsFactory(""Account"");
const { account: Account } = await accountsFactory.deployNewAccount({
publicKey: signer.publicKey,
initParams: {
_randomNonce: locklift.utils.getRandomNonce(),
},
constructorParams: {},
value: locklift.utils.toNano(balance),
});
migration.store(Account, ""account"");
console.log(`Account deployed at: ${Account.address}`);
}
main()
.then(() => process.exit(0))
.catch(e => {
console.log(e);
process.exit(1);
});"
Crashes on an attempt to collect Account contract assembly artefacts
There is no such thing in the build folder!

How to tell if a Shopify store is using Online Store 2.0 theme?

I'm building a Shopify app
I want to know if a majority of potential clients are using Online Store 2.0 themes or not.
Is there a way to tell this by looking only at their websites? (i.e. checking if some script is loaded in network tab that only loads for online store 2.0 themes)
On the Shopify Product reviews sample app, they have this endpoint, which returns whether the current theme supports app blocks (only 2.0 themes support app blocks)
/**
* This REST endpoint is resposible for returning whether the store's current main theme supports app blocks.
*/
router.get(
"/api/store/themes/main",
verifyRequest({ authRoute: "/online/auth" }),
async (ctx) => {
const session = await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res);
const clients = {
rest: new Shopify.Clients.Rest(session.shop, session.accessToken),
graphQL: createClient(session.shop, session.accessToken),
};
// Check if App Blocks are supported
// -----------------------------------
// Specify the name of the template we want our app to integrate with
const APP_BLOCK_TEMPLATES = ["product"];
// Use `client.get` to request list of themes on store
const {
body: { themes },
} = await clients.rest.get({
path: "themes",
});
// Find the published theme
const publishedTheme = themes.find((theme) => theme.role === "main");
// Get list of assets contained within the published theme
const {
body: { assets },
} = await clients.rest.get({
path: `themes/${publishedTheme.id}/assets`,
});
// Check if template JSON files exist for the template specified in APP_BLOCK_TEMPLATES
const templateJSONFiles = assets.filter((file) => {
return APP_BLOCK_TEMPLATES.some(
(template) => file.key === `templates/${template}.json`
);
});
// Get bodies of template JSONs
const templateJSONAssetContents = await Promise.all(
templateJSONFiles.map(async (file) => {
const {
body: { asset },
} = await clients.rest.get({
path: `themes/${publishedTheme.id}/assets`,
query: { "asset[key]": file.key },
});
return asset;
})
);
// Find what section is set as 'main' for each template JSON's body
const templateMainSections = templateJSONAssetContents
.map((asset, index) => {
const json = JSON.parse(asset.value);
const main = json.sections.main && json.sections.main.type;
return assets.find((file) => file.key === `sections/${main}.liquid`);
})
.filter((value) => value);
// Request the content of each section and check if it has a schema that contains a
// block of type '#app'
const sectionsWithAppBlock = (
await Promise.all(
templateMainSections.map(async (file, index) => {
let acceptsAppBlock = false;
const {
body: { asset },
} = await clients.rest.get({
path: `themes/${publishedTheme.id}/assets`,
query: { "asset[key]": file.key },
});
const match = asset.value.match(
/\{\%\s+schema\s+\%\}([\s\S]*?)\{\%\s+endschema\s+\%\}/m
);
const schema = JSON.parse(match[1]);
if (schema && schema.blocks) {
acceptsAppBlock = schema.blocks.some((b) => b.type === "#app");
}
return acceptsAppBlock ? file : null;
})
)
).filter((value) => value);
/**
* Fetch one published product that's later used to build the editor preview url
*/
const product = await getFirstPublishedProduct(clients.graphQL);
const editorUrl = `https://${session.shop}/admin/themes/${
publishedTheme.id
}/editor?previewPath=${encodeURIComponent(
`/products/${product?.handle}`
)}`;
/**
* This is where we check if the theme supports apps blocks.
* To do so, we check if the main-product section supports blocks of type #app
*/
const supportsSe = templateJSONFiles.length > 0;
const supportsAppBlocks = supportsSe && sectionsWithAppBlock.length > 0;
ctx.body = {
theme: publishedTheme,
supportsSe,
supportsAppBlocks,
/**
* Check if each of the sample app's app blocks have been added to the product.json template
*/
containsAverageRatingAppBlock: containsAppBlock(
templateJSONAssetContents[0]?.value,
"average-rating",
process.env.THEME_APP_EXTENSION_UUID
),
containsProductReviewsAppBlock: containsAppBlock(
templateJSONAssetContents[0]?.value,
"product-reviews",
process.env.THEME_APP_EXTENSION_UUID
),
editorUrl,
};
ctx.res.statusCode = 200;
}
);

Best practice handling API calls and subsequent actions based on response data with Redux

Suppose we have this scenario: we need to make a request to an API to get data on number of books available. If books > 0, we must trigger
some sort of popup with a function which is provided to us. The books need to be stored into the redux store for other components to use. The codebase already uses redux-thunk and redux-thunk-middleware.
What would be the best implementation using a hook and why? (displayPopUp is the function that we must use to trigger the pop-up)
1)
const useBooksNotification = () => {
const dispatch = useDispatch();
const [shouldShowNotification, setShouldShowNotification] = useState(false);
const books = useSelector(selectAvailableBooks);
useEffect(() => {
if (shouldShowNotification) {
setShouldShowNotification(false);
if (books.length > 0) {
displayPopUp();
}
}
}, [shouldShowNotification, books]);
const showNotification = async () => {
if (!shouldShowNotification) {
await dispatch(fetchBooks);
setShouldShowNotification(true);
}
};
return {
showNotification,
};
};
or 2)
const useBooksNotification2 = () => {
const dispatch = useDispatch();
const showNotification = async () => {
{
const response = await dispatch(fetchBooks);
const books = response.value;
if (books.length > 0) {
displayPopUp();
}
}
};
return {
showNotification,
};
};
Personally, I prefer 2 since to me it is much more readable but someone told me 1 is preferable i.e listening to the selector for the books instead of getting the books directly from the action/API response. I am very curious as to why this is? Or if there is an even better implementation.
I would understand using a selector if there was no displayPopUp function given to us and instead there was some implementation like so:
const BooksNotification = () => {
const dispatch = useDispatch();
const books = useSelector(selectAvailableBooks);
const showNotification = () => {
dispatch(fetchBooks);
};
return books.length > 0 ? <h1>Pretend this is a pop-up</h1> : null;
};
const SomeComponent = () => {
<div>
<h1>Component</h1>
<BooksNotification />
</div>
}

Contract event listener is not firing when running hardhat tests with ethers js

Here is a very small repo to show the issue: https://github.com/adamdry/ethers-event-issue
But I'll explain it here too. This is my contract:
//SPDX-License-Identifier: UNLICENSED;
pragma solidity 0.8.4;
contract ContractA {
event TokensMinted(uint amount);
function mint(uint amount) public {
emit TokensMinted(amount);
}
}
And this is my test code:
import * as chai from 'chai'
import { BigNumber, ContractTransaction } from 'ethers'
import { ethers } from 'hardhat'
import { ContractA, ContractAFactory } from '../typechain'
const expect = chai.expect
describe("Example test", function () {
it("should fire the event", async function () {
const [owner] = await ethers.getSigners();
const contractAFactory = (await ethers.getContractFactory(
'ContractA',
owner,
)) as ContractAFactory
const contractA: ContractA = await contractAFactory.deploy()
contractA.on('TokensMinted', (amount: BigNumber) => {
// THIS LINE NEVER GETS HIT
console.log('###########')
})
const contractTx: ContractTransaction = await contractA.mint(123)
const contractReceipt: ContractReceipt = await contractTx.wait()
for (const event of contractReceipt.events!) {
console.log(JSON.stringify(event))
}
});
});
I was expecting the ########### to get printed to the console however it doesn't so the listener function isn't being executed for some reason.
If I dig into the ContractReceipt the correct event data is there:
{
"transactionIndex": 0,
"blockNumber": 2,
"transactionHash": "0x55d118548c8200e5e6c19759d9aab56cb2e6a274186a92643de776d617d51e1a",
"address": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
"topics": [
"0x772f66a00a405709c30e7f18feadcc8f123b20c09c7260165d3eec36c9f21372"
],
"data": "0x000000000000000000000000000000000000000000000000000000000000007b",
"logIndex": 0,
"blockHash": "0x808e6949118509b5a9e482e84cf47921a2fcffbcd943ebbd8ce4f6671469ee01",
"args": [
{
"type": "BigNumber",
"hex": "0x7b"
}
],
"event": "TokensMinted",
"eventSignature": "TokensMinted(uint256)"
}
The full answer is here: https://github.com/nomiclabs/hardhat/issues/1692#issuecomment-905674692
But to summarise, the reason this doesn't work is that ethers.js, by default, uses polling to get events, and the polling interval is 4 seconds. If you add this at the end of your test:
await new Promise(res => setTimeout(() => res(null), 5000));
the event should fire.
However! You can also adjust the polling interval of a given contract like this:
// at the time of this writing, ethers' default polling interval is
// 4000 ms. here we turn it down in order to speed up this test.
// see also
// https://github.com/ethers-io/ethers.js/issues/615#issuecomment-848991047
const provider = greeter.provider as EthersProviderWrapper;
provider.pollingInterval = 100;
As seen here: https://github.com/nomiclabs/hardhat/blob/master/packages/hardhat-ethers/test/index.ts#L642
However! (again) If you want to get the results from an event, the below method requires no changes to the polling or any other "time" based solutions, which in my experience can cause flakey tests:
it('testcase', async() => {
const tx = await contract.transfer(...args); // 100ms
const rc = await tx.wait(); // 0ms, as tx is already confirmed
const event = rc.events.find(event => event.event === 'Transfer');
const [from, to, value] = event.args;
console.log(from, to, value);
})
Here is my TypeScriptyfied version (on my own contract with a slightly different event and args):
const contractTx: ContractTransaction = await tokenA.mint(owner.address, 500)
const contractReceipt: ContractReceipt = await contractTx.wait()
const event = contractReceipt.events?.find(event => event.event === 'TokensMinted')
const amountMintedFromEvent: BigNumber = event?.args!['amount']
This is the event declaration in solidity that goes with the above:
event TokensMinted(uint amount);

relay subscription onNext not triggered on react-native

I am a subscription setup but onNext is not getting triggered I am not sure why since this is my first time implementing subscription and docs was not much help with the issue.
Here are the code implementations:
import {
graphql,
requestSubscription
} from 'react-relay'
import environment from '../network';
const subscription = graphql`
subscription chatCreatedSubscription{
chatCreated{
id
initiate_time
update_time
support_id
category_id
email
name
}
}
`;
function chatCreated(callback) {
const variables = {};
requestSubscription(environment, {
subscription,
variables,
onNext: () => {
console.log("onNext");
callback()
},
updater: () => {
console.log("updater");
}
});
}
module.exports = chatCreated;
and here is my network for the subscription
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import Expo from "expo";
import { SubscriptionClient } from "subscriptions-transport-ws";
import { WebSocketLink } from 'apollo-link-ws';
import { execute } from 'apollo-link';
import accessHelper from "../helper/accessToken";
const networkSubscriptions = async (operation, variables) => {
let token = await accessHelper();
if (token != null || token != undefined) {
const subscriptionClient = new SubscriptionClient("ws://localhost:3000/graphql",
{
reconnect: true,
connectionParams: {
Authorization: token,
},
});
execute(new WebSocketLink(subscriptionClient), {
query: operation.text,
variables,
});
}
}
const network = Network.create(fetchQuery, networkSubscriptions);
const store = new Store(new RecordSource());
const environment = new Environment({
network,
store
});
export default environment;
the subscription is called in a componentDidMount method on a component it executes but the onNext method inside the subscription is never triggered when new information is added to what the subscription is listening to.
so i figured out that my issue was the network js not being setup properly and the version of subscription-transport-ws. i added version 0.8.3 of the package and made the following changes to my network file:
const networkSubscriptions = async (config, variables, cacheConfig, observer) => {
const query = config.text;
let token = await accessHelper();
if (token != null || token != undefined) {
const subscriptionClient = new SubscriptionClient(`ws://${api}/graphql`,
{
reconnect: true,
connectionParams: {
Authorization: token,
},
});
subscriptionClient.subscribe({ query, variables }, (error, result) => {
observer.onNext({ data: result })
})
return {
dispose: subscriptionClient.unsubscribe
};
}
}
i hope this helps you if you get stuck with the same issue as mine.