Guibescos/aptos example 2 (#327)

* Add example

* Subfolder for my example

* Ready to merge

* Cleanup

* Gitignore right

* Renaming

* Named address reset

* Bump pyth-common-js

* Cleanup

* More cleanup

* Cleanup

* dev addresses

* Link to addresses

* Rename app

* Add link

* Fix

* refactor

* Renamed

* Rename

* Add import statement for Price

* Update to pass vaa to get_update_fee

* Update README

* Rename directory

* Comments

* Add recency check

* Format

* Cleanup, remove bad practices

* Better comments

* Get rid of recency check

* Update README
This commit is contained in:
guibescos 2022-10-13 11:48:57 -05:00 committed by GitHub
parent f77987df70
commit 4b37fda80c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 29300 additions and 0 deletions

2
aptos/examples/mint_nft/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.aptos
build

View File

@ -0,0 +1,23 @@
[package]
name = "Examples"
version = "0.0.0"
upgrade_policy = "compatible"
[dependencies]
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework/", rev = "main" }
AptosToken = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-token/", rev = "main" }
Pyth = { git = "https://github.com/pyth-network/pyth-crosschain.git", subdir = "aptos/contracts", rev = "main" }
[addresses]
# These are testnet addresses https://docs.pyth.network/consume-data/aptos#addresses
aptos_framework = "0x1"
mint_nft = "_"
pyth = "0xaa706d631cde8c634fe1876b0c93e4dec69d0c6ccac30a734e9e257042e81541"
deployer = "0xb138581594ebd7763cfa3c3e455050139b7304c6d41e7094a1c78da4e6761ed8"
wormhole = "0x1b1752e26b65fc24971ee5ec9718d2ccdd36bf20486a10b2973ea6dedc6cd197"
[dev-addresses]
mint_nft = "0xb138581594ebd7763cfa3c3e455050139b7304c6d41e7094a1c78da4e6761ed8"
pyth = "0xe2f37b8ac45d29d5ea23eb7d16dd3f7a7ab6426f5a998d6c23ecd3ae8d9d29eb"
deployer = "0x277fa055b6a73c42c0662d5236c65c864ccbf2d4abd21f174a30c8b786eab84b"
wormhole = "0x251011524cd0f76881f16e7c2d822f0c1c9510bfd2430ba24e1b3d52796df204"

View File

@ -0,0 +1,24 @@
# Example Full-Stack App: 100$ USD Mint
The goal of this contract is managing an NFT mint where the mint is paid in native currency but the cost of one NFT is always 100$.
This example is intended to be run on Aptos testnet because it depends on Pyth and Wormhole existing onchain.
### Important files :
- `./sources/minting.move` has the smart contract logic (the code that will run onchain)
- `./app/src/App.tsx` has the React application. The core logic of how the frontend will interact with the wallet and the blockchain.
Both combined contain the key pieces of code needed to make an Aptos fullstack app using Pyth!
### How to deploy the smart contract :
- Use `aptos init` with rest_url : `https://testnet.aptoslabs.com/` and faucet `https://faucet.testnet.aptoslabs.com` to generate a new keypair.
- Use a faucet to airdrop testnet APT to your newly created account by calling `aptos account fund-with-faucet --account default`. If this doesn't work, I have had success importing my private key from `.aptos/config.yaml` into Petra and clicking the airdrop button. Otherwise send APT from another account.
- Get your account address from `.aptos/config.yaml` and replace `mint_nft="_"` by `mint_nft="<ADDRESS>"` in `Move.toml`
- `aptos move compile`
- `aptos move publish`
### How to run the webapp :
- In `app/src/App.tsx` replace `const MINT_NFT_MODULE = "_"` by `const MINT_NFT_MODULE = "<ADDRESS>"` the address of your module from above.
- `npm install`
- `npm run start`
- Go to `http://localhost:3000/` in your browser and use Petra wallet to transact with the app.

23
aptos/examples/mint_nft/app/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

28732
aptos/examples/mint_nft/app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
{
"name": "mint-nft",
"version": "0.1.0",
"private": true,
"dependencies": {
"@pythnetwork/pyth-aptos-js": "^0.2.0",
"@pythnetwork/pyth-common-js": "^1.1.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.11.64",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"buffer": "^6.0.3",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"typescript": "^4.8.4",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"format": "prettier --write src/"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -0,0 +1,20 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -0,0 +1,134 @@
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { Price, PriceFeed } from "@pythnetwork/pyth-common-js";
import { AptosClient } from "aptos";
import { AptosPriceServiceConnection } from "@pythnetwork/pyth-aptos-js";
// Please read https://docs.pyth.network/consume-data before building on Pyth
// Rpc endpoint
const TESTNET_PRICE_SERVICE = "https://xc-testnet.pyth.network";
// Connection
const testnetConnection = new AptosPriceServiceConnection(
TESTNET_PRICE_SERVICE
); // Price service client used to retrieve the offchain VAAs to update the onchain price
// Price id : this is not an aptos account but instead an opaque identifier for each price https://pyth.network/developers/price-feed-ids/#pyth-cross-chain-testnet
const ETH_USD_TESTNET_PRICE_ID =
"0xca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6";
// Aptos modules : These are testnet addresses https://docs.pyth.network/consume-data/aptos#addresses
const MINT_NFT_MODULE = "_";
/// React component that shows the offchain price and confidence interval
function PriceText(props: { price: Price | undefined }) {
let price = props.price;
if (price) {
return (
<div>
{" "}
<p>
{" "}
Current ETH/USD price:{" "}
<span style={{ color: "green" }}>
{" "}
{price.getPriceAsNumberUnchecked().toFixed(3) +
" ± " +
price.getConfAsNumberUnchecked().toFixed(3)}{" "}
</span>
</p>
<p>
{" "}
Current NFT price:{" "}
<span style={{ color: "green" }}>
{" "}
{(100 / price.getPriceAsNumberUnchecked()).toFixed(5)} APT{" "}
</span>{" "}
</p>{" "}
</div>
);
} else {
return <span style={{ color: "red" }}> Failed to fetch price </span>;
}
}
function App() {
const [isConnected, setIsConnected] = React.useState<boolean>(false);
// Disconnect right at the beginning to clear previous wallet connections
React.useEffect(() => {
window.aptos.disconnect();
}, []);
const [pythOffChainPrice, setPythOffChainPrice] = React.useState<
Price | undefined
>(undefined);
// Subscribe to offchain prices. These are the prices that a typical frontend will want to show.
testnetConnection.subscribePriceFeedUpdates(
[ETH_USD_TESTNET_PRICE_ID],
(priceFeed: PriceFeed) => {
const price = priceFeed.getPriceUnchecked(); // Fine to use unchecked (not checking for staleness) because this must be a recent price given that it comes from a websocket subscription.
setPythOffChainPrice(price);
}
);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>Mint your Pythian NFT</p>
<PriceText price={pythOffChainPrice} />
<div>
<button
onClick={async () => {
setIsConnected(true);
await window.aptos.connect();
}}
disabled={isConnected}
>
{" "}
Connect{" "}
</button>
<button
onClick={async () => {
setIsConnected(false);
await window.aptos.disconnect();
}}
disabled={!isConnected}
>
{" "}
Disconnect{" "}
</button>
<button
onClick={async () => {
await sendMintTransaction();
}}
disabled={!isConnected || !pythOffChainPrice}
>
{" "}
Mint{" "}
</button>{" "}
</div>
</header>
</div>
);
}
async function sendMintTransaction() {
const priceFeedUpdateData = await testnetConnection.getPriceFeedsUpdateData([
ETH_USD_TESTNET_PRICE_ID,
]);
const mintTransaction = {
type: "entry_function_payload",
function: MINT_NFT_MODULE + `::minting::mint_nft`,
arguments: [priceFeedUpdateData], // Minting requires updating the price first, so we are passing the VAA containing the verifiable price as an argument. The `mint_nft` module use the VAA to update the Pyth price before the caller pays for the mint.
type_arguments: [],
};
await window.aptos.signAndSubmitTransaction(mintTransaction);
}
export default App;

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -0,0 +1,25 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
declare global {
interface Window { aptos: any; }
}
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
window.addEventListener('load', () => {
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
});
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@ -0,0 +1,7 @@
<svg width="142" height="219" viewBox="0 0 142 219" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M80.4977 84.5341C85.0782 84.5361 89.5563 83.1775 93.3646 80.6305C97.1729 78.0835 100.14 74.4626 101.89 70.2267C103.64 65.9907 104.095 61.3302 103.196 56.8357C102.297 52.3411 100.085 48.2147 96.8402 44.9791C93.5956 41.7436 89.4644 39.5445 84.9699 38.6605C80.4753 37.7765 75.8197 38.2474 71.5928 40.0134C67.3658 41.7794 63.7578 44.7612 61.2258 48.5809C58.6938 52.4007 57.3517 56.8865 57.3696 61.4702C57.3987 67.5921 59.8473 73.4536 64.1806 77.775C68.514 82.0963 74.38 84.5262 80.4977 84.5341ZM80.4977 46.2581C83.4854 46.2562 86.4068 47.1399 88.893 48.7979C91.3793 50.4559 93.319 52.8138 94.4674 55.5739C95.6157 58.3341 95.9212 61.3728 95.3454 64.3066C94.7695 67.2403 93.3381 69.9376 91.2317 72.058C89.1254 74.1784 86.4385 75.6268 83.5102 76.2205C80.582 76.8143 77.5436 76.5266 74.7786 75.3939C72.0136 74.2612 69.6459 72.3342 67.9743 69.8561C66.3026 67.378 65.4021 64.46 65.3862 61.4702C65.3823 59.4809 65.7701 57.5103 66.5275 55.671C67.2849 53.8317 68.397 52.1599 69.8004 50.7509C71.2038 49.3419 72.8708 48.2234 74.7063 47.4594C76.5418 46.6954 78.5097 46.3008 80.4977 46.2982V46.2581Z" fill="#8246FA"/>
<path d="M80.4976 103.707C88.8576 103.707 97.0297 101.226 103.98 96.5772C110.93 91.9284 116.347 85.3211 119.544 77.5912C122.74 69.8613 123.574 61.3562 121.94 53.152C120.305 44.9477 116.276 37.413 110.361 31.501C104.446 25.589 96.9115 21.5655 88.711 19.9395C80.5105 18.3135 72.0122 19.1581 64.2915 22.3664C56.5708 25.5747 49.9745 31.0026 45.3371 37.9632C40.6998 44.9239 38.2299 53.1046 38.2398 61.4704V176.288C38.2398 177.352 38.6621 178.372 39.4138 179.125C40.1655 179.877 41.185 180.3 42.2481 180.3C43.3112 180.3 44.3307 179.877 45.0824 179.125C45.8341 178.372 46.2564 177.352 46.2564 176.288V86.1587C50.1678 91.5878 55.3117 96.0097 61.2646 99.0606C67.2175 102.111 73.8094 103.704 80.4976 103.707V103.707ZM80.4976 27.1653C87.2682 27.1653 93.8868 29.1739 99.5169 32.9372C105.147 36.7005 109.536 42.0497 112.129 48.3084C114.721 54.5672 115.402 61.4546 114.084 68.1002C112.766 74.7459 109.509 80.8514 104.724 85.6451C99.9392 90.4387 93.8418 93.7052 87.2023 95.0319C80.5628 96.3585 73.6793 95.6857 67.4218 93.0984C61.1644 90.5111 55.8139 86.1255 52.0466 80.496C48.2793 74.8664 46.2644 68.2456 46.2564 61.4704C46.2538 52.3782 49.8592 43.6572 56.2801 37.2243C62.701 30.7914 71.4118 27.1733 80.4976 27.1653V27.1653Z" fill="#8246FA"/>
<path d="M80.4975 0C64.2162 0.018598 48.6077 6.50246 37.0998 18.0278C25.5918 29.5531 19.1252 45.1776 19.1199 61.4703V214.514C19.1199 215.578 19.5422 216.598 20.2939 217.35C21.0456 218.103 22.0651 218.525 23.1282 218.525C24.1913 218.525 25.2108 218.103 25.9625 217.35C26.7142 216.598 27.1365 215.578 27.1365 214.514V61.4703C27.1365 50.9091 30.2661 40.5852 36.1295 31.804C41.9929 23.0227 50.3267 16.1786 60.0771 12.137C69.8276 8.09545 80.5567 7.03801 90.9077 9.09838C101.259 11.1588 110.767 16.2444 118.229 23.7122C125.692 31.1801 130.774 40.6947 132.833 51.0528C134.892 61.411 133.835 72.1475 129.797 81.9047C125.758 91.6619 118.918 100.002 110.143 105.869C101.368 111.736 91.0513 114.868 80.4975 114.868C79.4344 114.868 78.4149 115.291 77.6632 116.043C76.9115 116.795 76.4891 117.815 76.4891 118.879C76.4891 119.943 76.9115 120.963 77.6632 121.716C78.4149 122.468 79.4344 122.89 80.4975 122.89C96.7758 122.89 112.387 116.419 123.898 104.901C135.409 93.3823 141.875 77.7599 141.875 61.4703C141.875 45.1806 135.409 29.5582 123.898 18.0397C112.387 6.52115 96.7758 0.0501266 80.4975 0.0501266V0Z" fill="#8246FA"/>
<path d="M4.00833 76.512C2.94526 76.512 1.92575 76.9346 1.17404 77.6868C0.422333 78.439 0 79.4593 0 80.5231V157.065C0 158.129 0.422333 159.149 1.17404 159.901C1.92575 160.654 2.94526 161.076 4.00833 161.076C5.07141 161.076 6.09095 160.654 6.84266 159.901C7.59437 159.149 8.01667 158.129 8.01667 157.065V80.5231C8.01667 79.4593 7.59437 78.439 6.84266 77.6868C6.09095 76.9346 5.07141 76.512 4.00833 76.512Z" fill="#8246FA"/>
<path d="M61.3777 153.094C60.3146 153.094 59.2951 153.517 58.5434 154.269C57.7917 155.021 57.3694 156.041 57.3694 157.105V195.381C57.3694 196.445 57.7917 197.465 58.5434 198.217C59.2951 198.97 60.3146 199.392 61.3777 199.392C62.4408 199.392 63.4603 198.97 64.212 198.217C64.9638 197.465 65.3861 196.445 65.3861 195.381V157.105C65.3861 156.041 64.9638 155.021 64.212 154.269C63.4603 153.517 62.4408 153.094 61.3777 153.094Z" fill="#8246FA"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

View File

@ -0,0 +1,109 @@
module mint_nft::minting {
use std::signer;
use std::string::{Self, String};
use std::vector;
use aptos_framework::account;
use aptos_framework::event::EventHandle;
use aptos_framework::coin;
use aptos_framework::aptos_coin;
use pyth::pyth;
use pyth::price_identifier;
use pyth::i64;
use pyth::price::{Self,Price};
use aptos_std::math64::pow;
use aptos_token::token::{Self, TokenDataId};
// WARNING This is actually the ETH/USD while APT is not listed
// For the entire list of price_ids head to https://pyth.network/developers/price-feed-ids/#pyth-cross-chain-testnet
// TODO : Update to the real APT/USD when it's out
const APTOS_USD_PRICE_FEED_IDENTIFIER : vector<u8> = x"ca80ba6dc32e08d06f1aa886011eed1d77c77be9eb761cc10d72b7d0a2fd57a6";
// This event stores the receiver of the NFT and the TokenDataId of the NFT
struct TokenMintingEvent has drop, store {
token_receiver_address: address,
token_data_id: TokenDataId,
}
// This struct stores an NFT relevant information and the signer capability required to mint the NFT
struct CollectionTokenMinter has key {
token_data_id: TokenDataId,
token_minting_events: EventHandle<TokenMintingEvent>,
signer_cap: account::SignerCapability
}
/// Octas per aptos coin
const OCTAS_PER_APTOS: u64 = 100000000;
/// Initialize this module: create a resource account, a collection, and a token data id, all the NFTs minted are editions of the same TokenDataId
fun init_module(resource_account: &signer) {
let collection_name = string::utf8(b"Pythians");
let description = string::utf8(b"Pythians");
let collection_uri = string::utf8(b"https://pyth.network/");
let token_name = string::utf8(b"Pythian by @EgorNaive");
let token_uri = string::utf8(b"https://pbs.twimg.com/media/FeVw9JPWYAAsiI6?format=jpg&name=medium");
// Create the resource account that we'll use to create tokens
let (resource_signer, resource_signer_cap) = account::create_resource_account(resource_account, b"candy-machine");
// Create the nft collection
let maximum_supply = 1; // There's only 1 NFT in the collection
let mutate_setting = vector<bool>[ false, false, false ];
let resource_account_address = signer::address_of(&resource_signer);
token::create_collection(&resource_signer, collection_name, description, collection_uri, maximum_supply, mutate_setting);
// Create a token data id to specify which token will be minted
let token_data_id = token::create_tokendata(
&resource_signer,
collection_name,
token_name,
string::utf8(b""),
0, // 0 means the supply is infinite
token_uri,
resource_account_address,
0,
0,
// We don't allow any mutation to the token
token::create_token_mutability_config(
&vector<bool>[ false, false, false, false, true ]
),
vector::empty<String>(),
vector::empty<vector<u8>>(),
vector::empty<String>(),
);
move_to(resource_account, CollectionTokenMinter {
token_data_id,
token_minting_events: account::new_event_handle<TokenMintingEvent>(resource_account),
signer_cap : resource_signer_cap
});
}
/// Mint an edition of the Pythian NFT pay 100 USD in native APT
public entry fun mint_nft(receiver : &signer, vaas : vector<vector<u8>>) acquires CollectionTokenMinter{
// Fetch the signer capability to mint the NFT
let collection_token_minter = borrow_global_mut<CollectionTokenMinter>(@mint_nft);
let resource_signer = account::create_signer_with_capability(&collection_token_minter.signer_cap);
let token_id = token::mint_token(&resource_signer, collection_token_minter.token_data_id, 1); // Mint the NFT
token::direct_transfer(&resource_signer, receiver, token_id, 1); // Transfer the NFT to the caller
let price = update_and_fetch_price(receiver, vaas);
let price_positive = i64::get_magnitude_if_positive(&price::get_price(&price)); // This will fail if the price is negative
let expo_magnitude = i64::get_magnitude_if_negative(&price::get_expo(&price)); // This will fail if the exponent is positive
let price_in_aptos_coin = (100 * OCTAS_PER_APTOS * pow(10, expo_magnitude)) / price_positive; // 100 USD in AptosCoin
coin::transfer<aptos_coin::AptosCoin>(receiver, @mint_nft, price_in_aptos_coin); // Pay for the NFT
}
/// Please read https://docs.pyth.network/consume-data/best-practices before using a `Price` in your application
fun update_and_fetch_price(receiver : &signer, vaas : vector<vector<u8>>) : Price {
let coins = coin::withdraw<aptos_coin::AptosCoin>(receiver, pyth::get_update_fee(&vaas)); // Get coins to pay for the update
pyth::update_price_feeds(vaas, coins); // Update price feed with the provided vaas
pyth::get_price(price_identifier::from_byte_vec(APTOS_USD_PRICE_FEED_IDENTIFIER)) // Get recent price (will fail if price is too old)
}
}