Skip to content

Commit

Permalink
Fix web app to work on testnet (#174)
Browse files Browse the repository at this point in the history
* Inject env vars into cadence-loader

* Show tx status in web UI

* Return transaction status from useTransaction

* Change executing status text
  • Loading branch information
psiemens authored Dec 16, 2022
1 parent 7346304 commit e95ee33
Show file tree
Hide file tree
Showing 16 changed files with 162 additions and 78 deletions.
5 changes: 5 additions & 0 deletions .changeset/light-boats-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'freshmint': minor
---

Show claim transaction status in web UI
5 changes: 5 additions & 0 deletions .changeset/sixty-kings-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@freshmint/cadence-loader': minor
---

Inject environment variables into cadence-loader
5 changes: 5 additions & 0 deletions .changeset/three-peas-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@freshmint/react': minor
---

Return transaction status from useTransaction
33 changes: 13 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 19 additions & 10 deletions packages/cadence-loader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { withPrefix } from '@onflow/fcl';
// @ts-ignore
import { extractImports } from '@onflow/flow-cadut';

import envsubst from '@tuplo/envsubst';
import { validate } from 'schema-utils';

const schema: any = {
Expand All @@ -19,17 +18,25 @@ const schema: any = {
required: ['flowConfig'],
};

function parseFlowConfigFile(configPath: string) {
const rawConfig = fs.readFileSync(configPath).toString('utf8');
const substitutedConfig = envsubst(rawConfig);
return JSON.parse(substitutedConfig);
function envsubst(s: string, env: { [key: string]: string }): string {
for (const key in env) {
s = s.replace(`$\{${key}}`, env[key]);
}

return s;
}

function parseFlowConfigFiles(configPaths: string[]) {
const configs = configPaths.map((configPath) => parseFlowConfigFile(configPath));
function parseFlowConfigFiles(configPaths: string[], env: { [key: string]: string }) {
const configs = configPaths.map((configPath) => parseFlowConfigFile(configPath, env));
return mergeDeep({}, ...configs);
}

function parseFlowConfigFile(configPath: string, env: { [key: string]: string }) {
const rawConfig = fs.readFileSync(configPath).toString('utf8');
const substitutedConfig = envsubst(rawConfig, env);
return JSON.parse(substitutedConfig);
}

function isObject(item: any): boolean {
return item && typeof item === 'object' && !Array.isArray(item);
}
Expand Down Expand Up @@ -63,10 +70,10 @@ function mergeDeep(target: any, ...sources: any[]): any {
type AddressMap = { [contractName: string]: string };
type AddressMaps = { [network: string]: AddressMap };

function getAddressMapsFromFlowConfig(configPath: string | string[]): AddressMaps {
function getAddressMapsFromFlowConfig(configPath: string | string[], env: { [key: string]: string }): AddressMaps {
const configPaths = Array.isArray(configPath) ? configPath : [configPath];

const config = parseFlowConfigFiles(configPaths);
const config = parseFlowConfigFiles(configPaths, env);

return {
emulator: getAddressMapForNetwork(config, 'emulator'),
Expand Down Expand Up @@ -208,7 +215,9 @@ export default function loader(this: WebpackLoader, source: string): string {

validate(schema, options);

const addressMaps = getAddressMapsFromFlowConfig(options.flowConfig);
const env = options.env ?? {};

const addressMaps = getAddressMapsFromFlowConfig(options.flowConfig, env);

const extractedImports = extractImports(source);

Expand Down
1 change: 0 additions & 1 deletion packages/cadence-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"dependencies": {
"@onflow/fcl": "^1.2.0",
"@onflow/flow-cadut": "^0.2.0-alpha.9",
"@tuplo/envsubst": "^1.15.0",
"react": "^18.2.0",
"schema-utils": "^4.0.0"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/freshmint/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ export class MissingContractAccountForNetworkError extends FreshmintError {

constructor(network: string) {
super(
`Please specify a contract account for the "${network}" network.\n\nExample in ${chalk.green(
'freshmint.yaml',
)}:${chalk.cyan(`\n\ncontract:\n account:\n ${network}: your-account-name (as defined in flow.json)`)}`,
`Please specify a contract account for the "${network}" network.\n\nExample in freshmint.yaml:${chalk.cyan(
`\n\ncontract:\n account:\n ${network}: your-account-name (as defined in flow.json)`,
)}`,
);
this.network = network;
}
Expand Down
66 changes: 44 additions & 22 deletions packages/freshmint/generate/templates/web/components/NFTDrop.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useRouter } from 'next/router';
import { Box, Text, Button } from '@chakra-ui/react';
import { useFCL, useScript, useTransaction, TransactionResult } from '@freshmint/react';
import { useFCL, useScript, useTransaction, TransactionResult, TransactionStatus } from '@freshmint/react';

import getDrop from '../../cadence/scripts/get_drop.cdc';
import claimNFT from '../../cadence/transactions/claim_nft.cdc';
Expand All @@ -13,25 +13,6 @@ interface DropInfo {
paymentType: string;
}

function parseDropResult(result: any | null): DropInfo | null {
if (result === null) {
return null;
}

return {
id: result.id,
supply: parseInt(result.supply, 10),
size: parseInt(result.size, 10),
price: parseFloat(result.price),
paymentType: result.paymentVaultType.typeID,
};
}

function parseClaimResult(result: TransactionResult): string {
const event = result.events.find((e) => e.type.includes('FreshmintClaimSaleV2.NFTClaimed'))!;
return event.data.nftID;
}

interface DropProps {
address: string;
id?: string;
Expand All @@ -43,7 +24,7 @@ export default function NFTDrop({ address, id = 'default' }: DropProps) {

const [drop, isLoading] = useScript({ cadence: getDrop, args: [address, id] }, parseDropResult);

const [nftId, claim] = useTransaction(claimNFT, parseClaimResult);
const [nftId, claim, status] = useTransaction(claimNFT, parseClaimResult);

if (nftId && currentUser) {
router.push(`/nfts/${currentUser.address}/${nftId}`);
Expand All @@ -58,9 +39,50 @@ export default function NFTDrop({ address, id = 'default' }: DropProps) {
<Text fontSize="lg" mb={4} color="gray">
{drop.supply > 0 ? `${drop.supply} / ${drop.size} NFTs available.` : `All ${drop.size} NFTs have been claimed.`}
</Text>
<Button disabled={drop.supply === 0} colorScheme="blue" size="lg" onClick={() => claim(address, id)}>
<Button
isLoading={status !== TransactionStatus.UNKNOWN}
loadingText={getLoadingText(status)}
disabled={drop.supply === 0}
colorScheme="blue"
size="lg"
onClick={() => claim(address, id)}
>
{drop.supply > 0 ? `Claim for ${drop.price} FLOW` : 'Drop is sold out'}
</Button>
</Box>
);
}

function parseDropResult(result: any | null): DropInfo | null {
if (result === null) {
return null;
}

return {
id: result.id,
supply: parseInt(result.supply, 10),
size: parseInt(result.size, 10),
price: parseFloat(result.price),
paymentType: result.paymentVaultType.typeID,
};
}

function parseClaimResult(result: TransactionResult): string {
const event = result.events.find((e) => e.type.includes('FreshmintClaimSaleV2.NFTClaimed'))!;
return event.data.nftID;
}

function getLoadingText(status: TransactionStatus) {
switch (status) {
case TransactionStatus.SUBMITTED:
return 'Submitting...';
case TransactionStatus.PENDING:
return 'Executing...';
case TransactionStatus.EXECUTED:
return 'Verifying...';
case TransactionStatus.SEALED:
return 'Complete';
}

return 'Loading...';
}
5 changes: 4 additions & 1 deletion packages/freshmint/generate/templates/web/eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"extends": ["next/core-web-vitals", "prettier"]
"extends": ["next/core-web-vitals", "prettier"],
"rules": {
"@typescript-eslint/no-non-null-assertion": "off"
}
}
12 changes: 8 additions & 4 deletions packages/freshmint/generate/templates/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ function getDefaults(network) {
return {
ACCESS_API: 'http://localhost:8888',
WALLET_DISCOVERY: 'http://localhost:8701/fcl/authn',
MINTER_ADDRESS: '0xf8d6e0586b0a20c7'
CONTRACT_ADDRESS: '0xf8d6e0586b0a20c7'
}
case 'testnet':
return {
ACCESS_API: 'https://rest-testnet.onflow.org',
WALLET_DISCOVERY: 'https://fcl-discovery.onflow.org/testnet/authn',
MINTER_ADDRESS: ''
CONTRACT_ADDRESS: ''
}
}

Expand All @@ -44,7 +44,11 @@ const nextConfig = {
path.resolve('../flow.json'),
path.resolve('../flow.testnet.json'),
path.resolve('../flow.mainnet.json'),
]
],
env: {
FLOW_TESTNET_ADDRESS: process.env.CONTRACT_ADDRESS,
FLOW_MAINNET_ADDRESS: process.env.CONTRACT_ADDRESS
}
},
},
]
Expand All @@ -61,7 +65,7 @@ const nextConfig = {
DESCRIPTION: process.env.DESCRIPTION || project.description,
ACCESS_API: process.env.ACCESS_API || defaults.ACCESS_API,
WALLET_DISCOVERY: process.env.WALLET_DISCOVERY || defaults.WALLET_DISCOVERY,
MINTER_ADDRESS: process.env.MINTER_ADDRESS || defaults.MINTER_ADDRESS
CONTRACT_ADDRESS: process.env.CONTRACT_ADDRESS || defaults.CONTRACT_ADDRESS
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/freshmint/generate/templates/web/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Home: NextPage = () => {
<Text fontSize="lg" mb={3}>
{process.env.DESCRIPTION}
</Text>
<NFTDrop address={process.env.MINTER_ADDRESS!} />
<NFTDrop address={process.env.CONTRACT_ADDRESS!} />
<UserInfo />
</Container>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/freshmint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"scripts": {
"build": "tsup index.ts --clean --minify --format esm,cjs",
"postbuild": "cpx --include-empty-dirs \"generate/templates/**\" dist/templates",
"dev": "npm run build -- --watch --onSuccess \"npm run postbuild\"",
"dev": "tsup index.ts --clean --format esm,cjs --watch --onSuccess \"npm run postbuild\"",
"lint": "eslint . --ext .ts"
},
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/react/hooks/useFCL.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import { useState, useEffect } from 'react';

// @ts-ignore
import * as fcl from '@onflow/fcl';
Expand All @@ -16,7 +16,7 @@ export interface FCL {
}

export function useFCL(): FCL {
const [currentUser, setCurrentUser] = React.useState<FCLUser | null>(null);
const [currentUser, setCurrentUser] = useState<FCLUser | null>(null);

useEffect(() => {
// Only subscribe to user updates if running in a browser environment
Expand Down
Loading

0 comments on commit e95ee33

Please sign in to comment.