Skip to content

Commit

Permalink
Merge pull request #232 from sunrise-stake/develop
Browse files Browse the repository at this point in the history
0.7.1 Earth Day Release
  • Loading branch information
dankelleher authored Apr 22, 2023
2 parents 41ec892 + 23a531b commit 115b5e4
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 35 deletions.
3 changes: 2 additions & 1 deletion packages/app/src/api/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const buildTransferRecord = (transfer: TransferResponse): Transfer => ({
const buildMintRecord = (mint: MintResponse): Mint => ({
timestamp: new Date(mint.timestamp),
recipient: new PublicKey(mint.recipient),
sender: mint.sender !== undefined ? new PublicKey(mint.sender) : undefined,
amount: mint.amount,
});
const getDBData = async <T>(
Expand Down Expand Up @@ -60,7 +61,7 @@ const getDBData = async <T>(
}).then(async (resp) => resp.json());
};
export const getAccountMints = async (address: PublicKey): Promise<Mint[]> =>
getDBData<MintResponse>("mints", ["recipient"], address)
getDBData<MintResponse>("mints", ["sender", "recipient"], address)
.then((resp) => resp.documents)
.then((mints) => mints.map(buildMintRecord));
export const getAccountTransfers = async (
Expand Down
19 changes: 15 additions & 4 deletions packages/app/src/api/forest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
getGsolBalance,
getLockedBalance,
getTotals,
mintsToSelf,
mintsWithRecipientsAsTransfers,
prune,
} from "./util";
import { type SunriseClientWrapper } from "../common/sunriseClientWrapper";
Expand Down Expand Up @@ -104,20 +106,29 @@ export class ForestService {
getAccountTransfers(address),
]
);
const received = transfers.filter((t) => t.recipient.equals(address));
const sent = transfers.filter((t) => t.sender.equals(address));

// get all transfers including gsol minted directly into a recipient's account
const enrichedTransfers = [
...transfers,
...mintsWithRecipientsAsTransfers(mints),
];
const filteredMints = mintsToSelf(mints);
const received = enrichedTransfers.filter((t) =>
t.recipient.equals(address)
);
const sent = enrichedTransfers.filter((t) => t.sender.equals(address));
const totals = getTotals(
currentBalance,
lockedBalance,
mints,
filteredMints,
received,
sent
);
const startDate = earliest([...mints, ...transfers]);

return {
address,
mints,
mints: filteredMints,
sent,
received,
totals,
Expand Down
4 changes: 3 additions & 1 deletion packages/app/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type PublicKey } from "@solana/web3.js";

export interface MintResponse {
timestamp: string;
sender?: string; // the wallet that pays for the mint (assume = the recipient if missing)
recipient: string;
amount: number;
}
Expand All @@ -19,7 +20,8 @@ export interface MongoResponse<T> {

export interface Mint {
timestamp: Date;
recipient: PublicKey;
sender?: PublicKey; // the wallet that pays for the mint (assume = the recipient if missing)
recipient: PublicKey; // the wallet that receives the minted gSOL
amount: number;
}

Expand Down
21 changes: 21 additions & 0 deletions packages/app/src/api/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ export const filterFirstTransfersForSenderAndRecipient = (
return Object.values(firstTransfers);
};

const isMintTransfer = (mint: Mint): mint is Required<Mint> =>
mint.sender !== undefined && !mint.sender.equals(mint.recipient);

/**
* Find all mints for which the recipient is not the sender and return them as transfers
* This ensures that transferred gSOL and gSOL that is minted directly into someone else's account
* are treated the same way
*/
export const mintsWithRecipientsAsTransfers = (mints: Mint[]): Transfer[] => {
return mints.filter(isMintTransfer).map((mint) => ({
amount: mint.amount,
sender: mint.sender,
recipient: mint.recipient,
timestamp: mint.timestamp,
}));
};
export const mintsToSelf = (mints: Mint[]): Mint[] =>
mints.filter(
(mint) => mint.sender === undefined || mint.sender.equals(mint.recipient)
);

export const prune = (forest: Forest): Forest => {
const seen: string[] = [];
// if a tree node is in the seen array, it's a duplicate, remove it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ const SendGSolModal: FC<ModalProps & SendGSolModalProps> = ({
{currency === "SOL" ? (
<div className="mt-2 mb-4 text-sm text-grey">
<MdInfo className="inline stroke-grey" />
SOL gets staked and send as gSOL
SOL will be staked and sent as gSOL
</div>
) : null}
<div className="">
Expand Down
6 changes: 1 addition & 5 deletions packages/app/src/common/components/notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,7 @@ export const notifyTransaction = (n: {
{n.txid !== undefined ? (
<div className="flex flex-row text-outset">
<a
href={
"https://explorer.solana.com/tx/" +
n.txid +
`?cluster=mainnet`
}
href={"https://solana.fm/tx/" + n.txid + `?cluster=mainnet`}
target="_blank"
rel="noreferrer"
className="flex flex-row link link-accent"
Expand Down
39 changes: 25 additions & 14 deletions packages/app/src/common/context/NFTsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,12 @@ const isEmptyQuery = (query: NFTQuery): boolean =>

interface NFTsContextValue {
nfts: UnloadedNFT[];
refresh: () => Promise<void>;
loadNFTMetadata: (nft: UnloadedNFT) => Promise<GenericNFT>;
}
const NFTsContext = createContext<NFTsContextValue>({
nfts: [],
refresh: async () => {},
loadNFTMetadata: async (nft: UnloadedNFT) =>
Promise.resolve(nft as GenericNFT),
});
Expand All @@ -193,18 +195,20 @@ export const NFTsProvider: FC<{ children: ReactNode }> = ({ children }) => {
const { publicKey: owner } = useWallet();
const [nfts, setNfts] = useState<UnloadedNFT[]>([]);

const loadAllNFTs = async (): Promise<void> => {
if (owner === null) return;
const nonCompressedNFTs = getAllNFTs(owner, connection);
const compressedNFTs = getAllCompressedNFTs(owner);
return Promise.all([nonCompressedNFTs, compressedNFTs])
.then((arrays) => arrays.flat())
.then(setNfts);
};

/**
* When the connected wallet changes, load all NFTs for that owner.
*/
useEffect(() => {
void (async () => {
if (owner === null) return;
const nonCompressedNFTs = getAllNFTs(owner, connection);
const compressedNFTs = getAllCompressedNFTs(owner);
return Promise.all([nonCompressedNFTs, compressedNFTs])
.then((arrays) => arrays.flat())
.then(setNfts);
})();
loadAllNFTs().catch(console.error);
}, [owner?.toBase58()]);

/**
Expand All @@ -224,7 +228,9 @@ export const NFTsProvider: FC<{ children: ReactNode }> = ({ children }) => {
};

return (
<NFTsContext.Provider value={{ nfts, loadNFTMetadata }}>
<NFTsContext.Provider
value={{ nfts, loadNFTMetadata, refresh: loadAllNFTs }}
>
{children}
</NFTsContext.Provider>
);
Expand All @@ -235,21 +241,26 @@ export const NFTsProvider: FC<{ children: ReactNode }> = ({ children }) => {
* NFTs that match the query.
* @param query
*/
export const useNFTs = (query: NFTQuery): GenericNFT[] => {
const { nfts, loadNFTMetadata } = useContext(NFTsContext);
export const useNFTs = (
query?: NFTQuery
): { nfts: GenericNFT[]; refresh: () => Promise<void> } => {
const { nfts, loadNFTMetadata, refresh } = useContext(NFTsContext);
const [filteredNfts, setFilteredNfts] = useState<GenericNFT[]>([]);

const loadFilteredNFTs = async (): Promise<GenericNFT[]> => {
const filteredNfts = nfts.filter(nftFilter(query));
const filteredNfts =
query && !isEmptyQuery(query) ? nfts.filter(nftFilter(query)) : nfts;
return Promise.all(filteredNfts.map(loadNFTMetadata));
};

useEffect(() => {
void (async () => {
if (isEmptyQuery(query)) return;
await loadFilteredNFTs().then(setFilteredNfts);
})();
}, [nfts.length]);

return filteredNfts;
return {
nfts: filteredNfts,
refresh,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const useNFTsFilteredByOffchainMetadata = (
const matches = (nft: GenericNFT): boolean =>
isSubset(nft.json, query.jsonFilter);

const nfts = useNFTs(query);
const { nfts } = useNFTs(query);

return nfts.filter(matches);
};
7 changes: 3 additions & 4 deletions packages/app/src/locking/ImpactNFT.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import { useNFTs } from "../common/context/NFTsContext";
export const ImpactNFT: FC<{ details: Details["impactNFTDetails"] }> = ({
details,
}) => {
const nft = useNFTs({ mintAddress: details?.mint })[0];

console.log("nft", nft);
const { nfts } = useNFTs({ mintAddress: details?.mint });
const nft = nfts?.[0];

return (
<a
href={`https://solscan.io/token/${details?.mint.toBase58() ?? ""}`}
href={`https://solana.fm/address/${details?.mint.toBase58() ?? ""}`}
target="_blank"
rel="noreferrer"
>
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/locking/LockingApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { tooltips } from "../common/content/tooltips";
import { AppRoute } from "../Routes";
import { useHelp } from "../common/context/HelpContext";
import { useWallet } from "@solana/wallet-adapter-react";
import { useNFTs } from "../common/context/NFTsContext";

const hasLockedBalance = (details: Details | undefined): boolean =>
details?.lockDetails?.amountLocked
Expand Down Expand Up @@ -72,6 +73,7 @@ const _LockingApp: ForwardRefRenderFunction<
const { currentHelpRoute } = useHelp();
const [, updateZenMode] = useZenMode();
const { myTree } = useForest();
const { refresh } = useNFTs();

const navigate = useNavigate();
const wallet = useWallet();
Expand Down Expand Up @@ -110,6 +112,7 @@ const _LockingApp: ForwardRefRenderFunction<
return client
.lockGSol(solToLamports(amount))
.then((txes) => {
refresh().catch(console.error); // refresh NFTs so that the impact NFT shows up
txes.forEach((tx: string, index) => {
notifyTransaction({
type: NotificationType.success,
Expand Down Expand Up @@ -144,6 +147,7 @@ const _LockingApp: ForwardRefRenderFunction<
return client
.updateLockAccount()
.then((txes) => {
refresh().catch(console.error); // refresh NFTs so that the impact NFT shows up
txes.forEach((tx: string, index) => {
notifyTransaction({
type: NotificationType.success,
Expand Down
7 changes: 3 additions & 4 deletions packages/app/src/tipjar/components/DonatableArtistNFT.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ export const DonatableArtistNFT: FC<Props> = ({ query, onDonate }) => {
Recognition for those who deserve it.
</h1>
<p>
Once you received your Earth Day Sunrise x DRiP NFT on{" "}
<u>22nd of April</u>, you&apos;ll be able to drop some SOL in your
favourite artist&apos;s wallet. DRiP is all about free art, so
there&apos;s no obligation. Every lamport is appreciated!
Received an Earth Day NFT? Drop some SOL in your artist&apos;s
wallet. DRiP is all about free art, so there&apos;s no obligation.
Every lamport is appreciated!
</p>
</div>
) : null}
Expand Down

1 comment on commit 115b5e4

@vercel
Copy link

@vercel vercel bot commented on 115b5e4 Apr 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

app – ./

app.sunrisestake.com
app-sunrise-stake.vercel.app
app-git-main-sunrise-stake.vercel.app

Please sign in to comment.