@@ -64,6 +64,14 @@ const INFERNO_PULSE = {
6464 kind : 101 ,
6565} as const ;
6666
67+ const STORM_ASSASIN = {
68+ metadataCid : "bafkreiavecfbxphf3qejwyktvh5eosa7eveqghcft4sjcrfugrxqat6wbi" ,
69+ imageCid : "bafybeie7ot5lpvbjkwpxnky4hzovbotim5266hsnyag7qff6ekxpeiqelm" ,
70+ localImagePath : "/nfts/storm-assasin.png" ,
71+ name : "StackUp: Storm Assasin" ,
72+ kind : 102 ,
73+ } as const ;
74+
6775type NftOverrideEntry = {
6876 name ?: string ;
6977 image ?: string ; // /public path or ipfs://...
@@ -136,6 +144,8 @@ export default function ClientPage() {
136144 > ( "idle" ) ;
137145 const [ infernoFeeUstx , setInfernoFeeUstx ] = useState < number | null > ( null ) ;
138146 const [ infernoUri , setInfernoUri ] = useState < string | null > ( null ) ;
147+ const [ stormFeeUstx , setStormFeeUstx ] = useState < number | null > ( null ) ;
148+ const [ stormUri , setStormUri ] = useState < string | null > ( null ) ;
139149 const [ nftOverrides , setNftOverrides ] = useState < NftOverrides > ( {
140150 byTokenId : { } ,
141151 byKind : { } ,
@@ -697,7 +707,13 @@ export default function ClientPage() {
697707 }
698708
699709 try {
700- const [ milestonesCV , infernoFeeCV , infernoUriCV ] = await Promise . all ( [
710+ const [
711+ milestonesCV ,
712+ infernoFeeCV ,
713+ infernoUriCV ,
714+ stormFeeCV ,
715+ stormUriCV ,
716+ ] = await Promise . all ( [
701717 fetchCallReadOnlyFunction ( {
702718 contractAddress : CONTRACT_ADDRESS ,
703719 contractName : CONTRACT_NAME ,
@@ -722,6 +738,22 @@ export default function ClientPage() {
722738 network : STACKS_NETWORK_OBJ ,
723739 senderAddress : caller ,
724740 } ) ,
741+ fetchCallReadOnlyFunction ( {
742+ contractAddress : CONTRACT_ADDRESS ,
743+ contractName : CONTRACT_NAME ,
744+ functionName : "get-mint-fee-kind" ,
745+ functionArgs : [ uintCV ( STORM_ASSASIN . kind ) ] ,
746+ network : STACKS_NETWORK_OBJ ,
747+ senderAddress : caller ,
748+ } ) ,
749+ fetchCallReadOnlyFunction ( {
750+ contractAddress : CONTRACT_ADDRESS ,
751+ contractName : CONTRACT_NAME ,
752+ functionName : "get-badge-uri" ,
753+ functionArgs : [ uintCV ( STORM_ASSASIN . kind ) ] ,
754+ network : STACKS_NETWORK_OBJ ,
755+ senderAddress : caller ,
756+ } ) ,
725757 ] ) ;
726758
727759 const ms = cvToValue ( milestonesCV ) as unknown ;
@@ -750,6 +782,20 @@ export default function ClientPage() {
750782 const uriUnwrapped = unwrapCvToValue ( cvToValue ( infernoUriCV ) as unknown ) ;
751783 setInfernoUri ( typeof uriUnwrapped === "string" ? uriUnwrapped : null ) ;
752784
785+ const stormFeeUnwrapped = unwrapCvToValue ( cvToValue ( stormFeeCV ) as unknown ) ;
786+ const stormFee =
787+ stormFeeUnwrapped === null
788+ ? null
789+ : typeof stormFeeUnwrapped === "bigint"
790+ ? Number ( stormFeeUnwrapped )
791+ : typeof stormFeeUnwrapped === "number"
792+ ? stormFeeUnwrapped
793+ : null ;
794+ setStormFeeUstx ( stormFee ) ;
795+
796+ const stormUriUnwrapped = unwrapCvToValue ( cvToValue ( stormUriCV ) as unknown ) ;
797+ setStormUri ( typeof stormUriUnwrapped === "string" ? stormUriUnwrapped : null ) ;
798+
753799 try {
754800 const kindsToLoad = BADGE_MILESTONES . map ( ( m ) => m . kind ) ;
755801 const uriPairs = await Promise . all (
@@ -1138,6 +1184,45 @@ export default function ClientPage() {
11381184 }
11391185 } ;
11401186
1187+ const mintStormAssasin = async ( ) => {
1188+ if ( ! address ) {
1189+ setError ( "Connect wallet first." ) ;
1190+ return ;
1191+ }
1192+ if ( stormFeeUstx === null ) {
1193+ setError ( "Price not loaded yet. Click “Refresh On-Chain” and try again." ) ;
1194+ return ;
1195+ }
1196+
1197+ setError ( "" ) ;
1198+ setStatus ( "Minting Storm Assasin..." ) ;
1199+
1200+ try {
1201+ openContractCall ( {
1202+ contractAddress : CONTRACT_ADDRESS ,
1203+ contractName : CONTRACT_NAME ,
1204+ functionName : "mint-paid-kind" ,
1205+ functionArgs : [ uintCV ( STORM_ASSASIN . kind ) ] ,
1206+ // This call transfers STX as a mint fee. In Allow mode, the wallet
1207+ // doesn't require us to enumerate the exact STX movement as a post-condition.
1208+ postConditionMode : PostConditionMode . Allow ,
1209+ network : STACKS_NETWORK_OBJ ,
1210+ appDetails : {
1211+ name : APP_NAME ,
1212+ icon : new URL ( APP_ICON_PATH , window . location . origin ) . toString ( ) ,
1213+ } ,
1214+ onFinish : ( ) => {
1215+ setStatus ( "Mint submitted" ) ;
1216+ scheduleRefresh ( address ) ;
1217+ } ,
1218+ onCancel : ( ) => setStatus ( "Mint cancelled" ) ,
1219+ } ) ;
1220+ } catch {
1221+ setError ( "Failed to open mint transaction." ) ;
1222+ setStatus ( "Mint failed" ) ;
1223+ }
1224+ } ;
1225+
11411226 return (
11421227 < div className = { styles . page } >
11431228 < div className = { styles . shell } >
@@ -1413,6 +1498,45 @@ export default function ClientPage() {
14131498 Mint
14141499 </ button >
14151500 </ div >
1501+
1502+ < div className = { styles . dropTile } >
1503+ < div className = { styles . dropTileThumb } >
1504+ < Image
1505+ src = { STORM_ASSASIN . localImagePath }
1506+ alt = { STORM_ASSASIN . name }
1507+ width = { 520 }
1508+ height = { 520 }
1509+ unoptimized
1510+ style = { {
1511+ width : "100%" ,
1512+ height : "100%" ,
1513+ objectFit : "contain" ,
1514+ } }
1515+ />
1516+ </ div >
1517+ < div className = { styles . dropTileBody } >
1518+ < div className = { styles . dropTileTitle } > { STORM_ASSASIN . name } </ div >
1519+ < div className = { styles . dropTileLine } >
1520+ Kind < code > u{ STORM_ASSASIN . kind } </ code >
1521+ { " · " }
1522+ { stormFeeUstx === null
1523+ ? "Price: —"
1524+ : `Price: ${ ( stormFeeUstx / 1_000_000 ) . toFixed ( 2 ) } STX` }
1525+ </ div >
1526+ < div className = { styles . dropTileLine } >
1527+ Metadata:{ " " }
1528+ < span > { stormUri ? "Configured" : "Not configured" } </ span >
1529+ </ div >
1530+ </ div >
1531+ < button
1532+ className = { `${ styles . button } ${ styles . dropTileButton } ` }
1533+ onClick = { mintStormAssasin }
1534+ disabled = { ! address || ! stormUri }
1535+ type = "button"
1536+ >
1537+ Mint
1538+ </ button >
1539+ </ div >
14161540 </ div >
14171541
14181542 { ! address ? (
0 commit comments