diff --git a/gno/p/dao_voting_group/voting_group_test.gno b/gno/p/dao_voting_group/voting_group_test.gno
index cfb40125bd..379770d31d 100644
--- a/gno/p/dao_voting_group/voting_group_test.gno
+++ b/gno/p/dao_voting_group/voting_group_test.gno
@@ -1,7 +1,6 @@
package dao_voting_group
import (
- "std"
"testing"
dao_interfaces "gno.land/p/teritori/dao_interfaces"
diff --git a/gno/p/markdown_utils/markdown_utils.gno b/gno/p/markdown_utils/markdown_utils.gno
index b593884a83..cc1f134526 100644
--- a/gno/p/markdown_utils/markdown_utils.gno
+++ b/gno/p/markdown_utils/markdown_utils.gno
@@ -25,4 +25,4 @@ func Indent(markdown string) string {
// thanks copilot this is perfect xD
// I just renamed it, AddIndentationLevelToMarkdownTitles was too long
-// blockchain + ai, invest quick!!!!
\ No newline at end of file
+// blockchain + ai, invest quick!!!!
diff --git a/gno/r/dao_realm/dao_realm.gno b/gno/r/dao_realm/dao_realm.gno
index 886fbe4547..859fc27d5d 100644
--- a/gno/r/dao_realm/dao_realm.gno
+++ b/gno/r/dao_realm/dao_realm.gno
@@ -10,6 +10,7 @@ import (
"gno.land/p/teritori/dao_utils"
voting_group "gno.land/p/teritori/dao_voting_group"
"gno.land/r/teritori/dao_registry"
+ "gno.land/r/teritori/social_feeds"
"gno.land/r/teritori/tori"
)
@@ -66,6 +67,9 @@ func init() {
func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler {
return tori.NewChangeAdminHandler()
},
+ func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler {
+ return social_feeds.NewCreatePostHandler()
+ },
}
daoCore = dao_core.NewDAOCore(votingModuleFactory, proposalModulesFactories, messageHandlersFactories)
diff --git a/gno/r/dao_realm/dao_realm_test.gno b/gno/r/dao_realm/dao_realm_test.gno
index 7338a03d31..202b4772a7 100644
--- a/gno/r/dao_realm/dao_realm_test.gno
+++ b/gno/r/dao_realm/dao_realm_test.gno
@@ -2,16 +2,11 @@ package dao_realm
import (
"fmt"
- "std"
"testing"
"gno.land/p/demo/json"
- dao_core "gno.land/p/teritori/dao_core"
- dao_interfaces "gno.land/p/teritori/dao_interfaces"
- proposal_single "gno.land/p/teritori/dao_proposal_single"
"gno.land/p/teritori/dao_voting_group"
"gno.land/p/teritori/havl"
- "gno.land/r/demo/users"
)
func TestInit(t *testing.T) {
diff --git a/gno/r/dao_realm/gno.mod b/gno/r/dao_realm/gno.mod
index 9547f1573d..580eea7310 100644
--- a/gno/r/dao_realm/gno.mod
+++ b/gno/r/dao_realm/gno.mod
@@ -8,7 +8,7 @@ require (
gno.land/p/teritori/dao_utils v0.0.0-latest
gno.land/p/teritori/dao_voting_group v0.0.0-latest
gno.land/p/teritori/havl v0.0.0-latest
- gno.land/r/demo/users v0.0.0-latest
gno.land/r/teritori/dao_registry v0.0.0-latest
+ gno.land/r/teritori/social_feeds v0.0.0-latest
gno.land/r/teritori/tori v0.0.0-latest
)
diff --git a/gno/r/social_feeds/feeds.gno b/gno/r/social_feeds/feeds.gno
index 352a9555cd..675b3c51e0 100644
--- a/gno/r/social_feeds/feeds.gno
+++ b/gno/r/social_feeds/feeds.gno
@@ -18,4 +18,5 @@ var (
//----------------------------------------
// Constants
+// Feed name must be 3-30 characters long, starting with a lowercase letter
var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`)
diff --git a/gno/r/social_feeds/feeds_test.gno b/gno/r/social_feeds/feeds_test.gno
index 532a131926..5d85e732c5 100644
--- a/gno/r/social_feeds/feeds_test.gno
+++ b/gno/r/social_feeds/feeds_test.gno
@@ -1,14 +1,10 @@
package social_feeds
import (
- "encoding/base64"
- "fmt"
"std"
- "strconv"
"strings"
"testing"
- "gno.land/p/demo/avl"
"gno.land/p/demo/testutils"
ujson "gno.land/p/teritori/ujson"
)
diff --git a/gno/r/social_feeds/messages.gno b/gno/r/social_feeds/messages.gno
index a60bf8e803..cb95fe4ca6 100644
--- a/gno/r/social_feeds/messages.gno
+++ b/gno/r/social_feeds/messages.gno
@@ -1,6 +1,7 @@
package social_feeds
import (
+ "strconv"
"strings"
"gno.land/p/demo/json"
@@ -63,8 +64,7 @@ func (msg *ExecutableMessageBanPost) String() string {
return strings.Join(ss, "\n---\n")
}
-type BanPostHandler struct {
-}
+type BanPostHandler struct{}
var _ dao_interfaces.MessageHandler = (*BanPostHandler)(nil)
@@ -84,3 +84,76 @@ func (h BanPostHandler) Type() string {
func (h BanPostHandler) Instantiate() dao_interfaces.ExecutableMessage {
return &ExecutableMessageBanPost{}
}
+
+// Create a new post
+type ExecutableMessageCreatePost struct {
+ FeedID FeedID
+ ParentID PostID
+ Category uint64
+ Metadata string
+}
+
+var _ dao_interfaces.ExecutableMessage = (*ExecutableMessageCreatePost)(nil)
+
+func (msg ExecutableMessageCreatePost) Type() string {
+ return "gno.land/r/teritori/social_feeds.CreatePost"
+}
+
+func (msg *ExecutableMessageCreatePost) ToJSON() *json.Node {
+ return json.ObjectNode("", map[string]*json.Node{
+ "feedId": jsonutil.IntNode(int(msg.FeedID)),
+ "parentId": jsonutil.IntNode(int(msg.ParentID)),
+ "category": jsonutil.IntNode(int(msg.Category)),
+ "metadata": json.StringNode("", msg.Metadata),
+ })
+}
+
+func (msg *ExecutableMessageCreatePost) FromJSON(ast *json.Node) {
+ obj := ast.MustObject()
+ msg.FeedID = FeedID(jsonutil.MustInt(obj["feedId"]))
+ msg.ParentID = PostID(jsonutil.MustInt(obj["parentId"]))
+ msg.Category = uint64(jsonutil.MustInt(obj["category"]))
+ msg.Metadata = obj["metadata"].MustString()
+}
+
+func (msg *ExecutableMessageCreatePost) String() string {
+ var ss []string
+ ss = append(ss, msg.Type())
+
+ feed := getFeed(msg.FeedID)
+ s := ""
+
+ if feed != nil {
+ s += "Feed: " + feed.name + " (" + feed.id.String() + ")"
+ s += "\nParent: " + msg.ParentID.String()
+ s += "\nCategory: " + strconv.Itoa(int(msg.Category))
+ s += "\nMetadata: " + msg.Metadata
+ } else {
+ s += "Feed: " + msg.FeedID.String() + " (not found)"
+ }
+
+ ss = append(ss, s)
+
+ return strings.Join(ss, "\n---\n")
+}
+
+type CreatePostHandler struct{}
+
+var _ dao_interfaces.MessageHandler = (*CreatePostHandler)(nil)
+
+func NewCreatePostHandler() *CreatePostHandler {
+ return &CreatePostHandler{}
+}
+
+func (h *CreatePostHandler) Execute(iMsg dao_interfaces.ExecutableMessage) {
+ msg := iMsg.(*ExecutableMessageCreatePost)
+ CreatePost(msg.FeedID, msg.ParentID, msg.Category, msg.Metadata)
+}
+
+func (h CreatePostHandler) Type() string {
+ return ExecutableMessageCreatePost{}.Type()
+}
+
+func (h CreatePostHandler) Instantiate() dao_interfaces.ExecutableMessage {
+ return &ExecutableMessageCreatePost{}
+}
diff --git a/packages/components/dao/ProposalActions.tsx b/packages/components/dao/ProposalActions.tsx
index 54ec940e56..edc58024fe 100644
--- a/packages/components/dao/ProposalActions.tsx
+++ b/packages/components/dao/ProposalActions.tsx
@@ -1,3 +1,4 @@
+import { upperFirst } from "lodash";
import { View } from "react-native";
import { useFeedbacks } from "../../context/FeedbacksProvider";
@@ -10,15 +11,15 @@ import { useIsDAOMember } from "../../hooks/dao/useDAOMember";
import { useDAOFirstProposalModule } from "../../hooks/dao/useDAOProposalModules";
import { useInvalidateDAOProposals } from "../../hooks/dao/useDAOProposals";
import {
- useInvalidateDAOVoteInfo,
useDAOVoteInfo,
+ useInvalidateDAOVoteInfo,
} from "../../hooks/dao/useDAOVoteInfo";
import { useSelectedNetworkId } from "../../hooks/useSelectedNetwork";
import useSelectedWallet from "../../hooks/useSelectedWallet";
import { NetworkKind, getNetwork, parseUserId } from "../../networks";
import { adenaVMCall } from "../../utils/gno";
import { GnoDAOVoteRequest } from "../../utils/gnodao/messages";
-import { neutral77, primaryColor, errorColor } from "../../utils/style/colors";
+import { errorColor, neutral77, primaryColor } from "../../utils/style/colors";
import { fontSemibold14 } from "../../utils/style/fonts";
import { BrandText } from "../BrandText";
import { PrimaryButton } from "../buttons/PrimaryButton";
@@ -80,34 +81,24 @@ export const ProposalActions: React.FC<{
case NetworkKind.Gno: {
const walletAddress = selectedWallet.address;
const [, pkgPath] = parseUserId(daoId);
- let gnoVote;
- switch (v) {
- case "yes": {
- gnoVote = 0;
- break;
- }
- case "no": {
- gnoVote = 1;
- break;
- }
- case "abstain": {
- gnoVote = 2;
- break;
- }
- default:
- throw new Error("invalid vote");
+ if (["yes", "no", "abstain"].indexOf(v) === -1) {
+ throw new Error("invalid vote");
}
const msg: GnoDAOVoteRequest = {
- vote: gnoVote,
+ vote: upperFirst(v),
rationale: "Me like it",
};
- await adenaVMCall(networkId, {
- caller: walletAddress,
- send: "",
- pkg_path: pkgPath,
- func: "VoteJSON",
- args: ["0", proposal.id.toString(), JSON.stringify(msg)],
- });
+ await adenaVMCall(
+ networkId,
+ {
+ caller: walletAddress,
+ send: "",
+ pkg_path: pkgPath,
+ func: "VoteJSON",
+ args: ["0", proposal.id.toString(), JSON.stringify(msg)],
+ },
+ { gasWanted: 10000000 },
+ );
break;
}
default:
diff --git a/packages/components/modals/NotEnoughFundModal.tsx b/packages/components/modals/NotEnoughFundModal.tsx
index 90b9cebb35..39b9bc47c8 100644
--- a/packages/components/modals/NotEnoughFundModal.tsx
+++ b/packages/components/modals/NotEnoughFundModal.tsx
@@ -6,7 +6,12 @@ import { Coin } from "../../api/teritori-chain/cosmos/base/v1beta1/coin";
import { useBalances } from "../../hooks/useBalances";
import { useSelectedNetworkInfo } from "../../hooks/useSelectedNetwork";
import useSelectedWallet from "../../hooks/useSelectedWallet";
-import { getCurrency, getNativeCurrency, getNetwork } from "../../networks";
+import {
+ getCurrency,
+ getNativeCurrency,
+ getNetwork,
+ parseUserId,
+} from "../../networks";
import { prettyPrice } from "../../utils/coins";
import { neutral77 } from "../../utils/style/colors";
import { fontSemibold14, fontSemibold20 } from "../../utils/style/fonts";
@@ -28,13 +33,15 @@ export const NotEnoughFundsModal: FC<{
cost?: Coin;
label?: string;
visible?: boolean;
+ userId?: string;
onClose?: () => void;
-}> = ({ label = "Not enough funds", cost, visible, onClose }) => {
+}> = ({ label = "Not enough funds", cost, visible, onClose, userId }) => {
const selectedWallet = useSelectedWallet();
const selectedNetwork = useSelectedNetworkInfo();
+ const [userNetwork, userAddress] = parseUserId(userId);
const { balances } = useBalances(
- selectedNetwork?.id,
- selectedWallet?.address,
+ userNetwork?.id || selectedNetwork?.id,
+ userAddress || selectedWallet?.address,
);
const costBalance = balances.find((bal) => bal.denom === cost?.denom);
const currency = getCurrency(selectedNetwork?.id, cost?.denom);
@@ -75,7 +82,7 @@ export const NotEnoughFundsModal: FC<{
- {cost && costBalance && selectedNetwork && (
+ {cost && selectedNetwork && (
<>
{prettyPrice(
selectedNetwork.id,
- costBalance.amount,
- costBalance.denom,
+ costBalance?.amount || "0",
+ cost.denom,
)}
diff --git a/packages/components/socialFeed/NewsFeed/NewsFeed.tsx b/packages/components/socialFeed/NewsFeed/NewsFeed.tsx
index 43172b9cf2..d37a4d73e3 100644
--- a/packages/components/socialFeed/NewsFeed/NewsFeed.tsx
+++ b/packages/components/socialFeed/NewsFeed/NewsFeed.tsx
@@ -1,8 +1,8 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import {
LayoutChangeEvent,
- View,
useWindowDimensions,
+ View,
ViewStyle,
} from "react-native";
import Animated, {
diff --git a/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx b/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx
index 99288b0254..3139edde4c 100644
--- a/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx
+++ b/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx
@@ -41,9 +41,9 @@ import { useIpfs } from "@/hooks/useIpfs";
import { useMaxResolution } from "@/hooks/useMaxResolution";
import { useSelectedNetworkInfo } from "@/hooks/useSelectedNetwork";
import useSelectedWallet from "@/hooks/useSelectedWallet";
-import { getNetworkFeature, getUserId, NetworkFeature } from "@/networks";
+import { NetworkFeature, getNetworkFeature } from "@/networks";
import { selectNFTStorageAPI } from "@/store/slices/settings";
-import { feedPostingStep, FeedPostingStepId } from "@/utils/feed/posting";
+import { FeedPostingStepId, feedPostingStep } from "@/utils/feed/posting";
import { generatePostMetadata, getPostCategory } from "@/utils/feed/queries";
import { generateIpfsKey } from "@/utils/ipfs";
import {
@@ -141,7 +141,7 @@ export const NewsFeedInput = React.forwardRef<
const selectedNetwork = useSelectedNetworkInfo();
const selectedNetworkId = selectedNetwork?.id || "teritori";
const selectedWallet = useSelectedWallet();
- const userId = getUserId(selectedNetworkId, selectedWallet?.address);
+ const authorId = daoId || selectedWallet?.userId;
const inputRef = useRef(null);
const { setToastError } = useFeedbacks();
const [isUploadLoading, setIsUploadLoading] = useState(false);
@@ -182,7 +182,7 @@ export const NewsFeedInput = React.forwardRef<
setStep,
} = useFeedPosting(
selectedNetwork?.id,
- userId,
+ authorId,
postCategory,
onPostCreationSuccess,
);
@@ -210,16 +210,19 @@ export const NewsFeedInput = React.forwardRef<
});
return;
}
+
if (!canPayForPost) {
showNotEnoughFundsModal({
action,
cost: {
- amount: publishingFee.toString(),
+ amount: publishingFee.amount.toString(),
denom: publishingFee.denom || "",
},
+ userId: authorId,
});
return;
}
+
setIsUploadLoading(true);
setIsProgressBarShown(true);
onSubmitInProgress && onSubmitInProgress();
@@ -258,7 +261,7 @@ export const NewsFeedInput = React.forwardRef<
setStep(feedPostingStep(FeedPostingStepId.GENERATING_KEY));
const pinataJWTKey =
- userIPFSKey || (await generateIpfsKey(selectedNetworkId, userId));
+ userIPFSKey || (await generateIpfsKey(selectedNetworkId, authorId));
if (pinataJWTKey) {
setStep(feedPostingStep(FeedPostingStepId.UPLOADING_FILES));
diff --git a/packages/components/socialFeed/modals/FlagConfirmModal.tsx b/packages/components/socialFeed/modals/FlagConfirmModal.tsx
index 5a6d576825..05a21853e8 100644
--- a/packages/components/socialFeed/modals/FlagConfirmModal.tsx
+++ b/packages/components/socialFeed/modals/FlagConfirmModal.tsx
@@ -63,7 +63,7 @@ export const FlagConfirmModal: React.FC = ({
const moduleIndex = "0";
const voteJSON: GnoDAOVoteRequest = {
- vote: vote === "banPost" ? 0 : 1,
+ vote: vote === "banPost" ? "yes" : "no",
rationale: "",
};
diff --git a/packages/context/WalletControlProvider.tsx b/packages/context/WalletControlProvider.tsx
index 61d982f121..b19c030fd0 100644
--- a/packages/context/WalletControlProvider.tsx
+++ b/packages/context/WalletControlProvider.tsx
@@ -8,6 +8,7 @@ import { NetworkFeature } from "../networks";
interface ControlledWalletFundsParams {
cost: Coin;
action: string;
+ userId?: string;
}
interface ControlledWalletConnectedParams {
@@ -37,6 +38,7 @@ export const WalletControlContextProvider: React.FC<{
useState(false);
const [isConnectWalletVisible, setIsConnectWalletVisible] = useState(false);
const [cost, setCost] = useState();
+ const [userId, setUserId] = useState();
const showConnectWalletModal = ({
forceNetworkFeature,
@@ -50,9 +52,11 @@ export const WalletControlContextProvider: React.FC<{
const showNotEnoughFundsModal = ({
cost,
action,
+ userId,
}: ControlledWalletFundsParams) => {
setCost(cost);
setAction(action);
+ setUserId(userId);
setIsNotEnoughFundModalVisible(true);
setIsConnectWalletVisible(false);
};
@@ -69,6 +73,7 @@ export const WalletControlContextProvider: React.FC<{
cost={cost}
visible={isNotEnoughFundModalVisible}
onClose={() => setIsNotEnoughFundModalVisible(false)}
+ userId={userId}
/>
{
"${network.daoUtilsPkgPath}"
voting_group "${network.votingGroupPkgPath}"
"${network.daoRegistryPkgPath}"
+ "${network.socialFeedsPkgPath}"
)
var (
@@ -79,6 +80,9 @@ func init() {
propMod := core.ProposalModules()[0]
return proposal_single.NewUpdateSettingsHandler(propMod.Module.(*proposal_single.DAOProposalSingle))
},
+ func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler {
+ return social_feeds.NewCreatePostHandler()
+ },
}
daoCore = dao_core.NewDAOCore(votingModuleFactory, proposalModulesFactories, messageHandlersFactories)
diff --git a/packages/utils/gnodao/messages.ts b/packages/utils/gnodao/messages.ts
index 4f79c61ef7..3818ca3ed2 100644
--- a/packages/utils/gnodao/messages.ts
+++ b/packages/utils/gnodao/messages.ts
@@ -7,7 +7,7 @@ export interface GnoSingleChoiceProposal {
}
export interface GnoDAOVoteRequest {
- vote: number;
+ vote: string;
rationale: string;
}
@@ -61,6 +61,16 @@ export interface GnoBanPostMessage {
};
}
+export interface GnoCreatePostMessage {
+ type: "gno.land/r/teritori/social_feeds.CreatePost";
+ payload: {
+ feedId: string;
+ parentId: string;
+ category: string;
+ metadata: string;
+ };
+}
+
export interface GnoAddMemberMessage {
type: "gno.land/r/teritori/groups.AddMember";
payload: {