Skip to content

Commit

Permalink
consolidate token/nft send functions between extension/mobile (#3637)
Browse files Browse the repository at this point in the history
* consolidate nft sending

* consolidate sending transactions
  • Loading branch information
peterpme authored Apr 7, 2023
1 parent b04c358 commit 26ca764
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 373 deletions.
Original file line number Diff line number Diff line change
@@ -1,43 +1,20 @@
import { useState } from "react";
import { findMintManagerId } from "@cardinal/creator-standard";
import { programs, tryGetAccount } from "@cardinal/token-manager";
import type { RawMintString } from "@coral-xyz/common";
import {
Blockchain,
confirmTransaction,
getLogger,
metadataAddress,
SOL_NATIVE_MINT,
Solana,
} from "@coral-xyz/common";
import { Blockchain } from "@coral-xyz/common";
import { PrimaryButton, UserIcon } from "@coral-xyz/react-common";
import {
useActiveWallet,
useAvatarUrl,
useSolanaCtx,
useSolanaTokenMint,
useSolanaTransaction,
} from "@coral-xyz/recoil";
import { styles, useCustomTheme } from "@coral-xyz/themes";
import {
findMintStatePk,
MintState,
} from "@magiceden-oss/open_creator_protocol";
import {
Metadata,
TokenStandard,
} from "@metaplex-foundation/mpl-token-metadata";
import { Typography } from "@mui/material";
import type { AccountInfo, Connection } from "@solana/web3.js";
import { PublicKey } from "@solana/web3.js";
import type { BigNumber } from "ethers";

import { CopyablePublicKey } from "../../../../common/CopyablePublicKey";
import { SettingsList } from "../../../../common/Settings/List";
import { TokenAmountHeader } from "../../../../common/TokenAmountHeader";
import { Error, Sending } from "../Send";

const logger = getLogger("send-solana-confirmation-card");

const useStyles = styles((theme) => ({
confirmTableListItem: {
backgroundColor: `${theme.custom.colors.approveTransactionTableBackground} !important`,
Expand Down Expand Up @@ -72,120 +49,15 @@ export function SendSolanaConfirmationCard({
onComplete?: (txSig?: any) => void;
onViewBalances?: () => void;
}) {
const [txSignature, setTxSignature] = useState<string | null>(null);
const solanaCtx = useSolanaCtx();
const [error, setError] = useState(
"Error 422. Transaction time out. Runtime error. Reticulating splines."
);
const [cardType, setCardType] = useState<
"confirm" | "sending" | "complete" | "error"
>("confirm");
const mintInfo = useSolanaTokenMint({
publicKey: solanaCtx.walletPublicKey.toString(),
tokenAddress: token.address,
const { txSignature, onConfirm, cardType, error } = useSolanaTransaction({
token,
destinationAddress,
amount,
onComplete: (txid) => {
onComplete?.(txid);
},
});

const onConfirm = async () => {
setCardType("sending");
//
// Send the tx.
//
let txSig;

try {
const mintId = new PublicKey(token.mint?.toString() as string);
if (token.mint === SOL_NATIVE_MINT.toString()) {
txSig = await Solana.transferSol(solanaCtx, {
source: solanaCtx.walletPublicKey,
destination: new PublicKey(destinationAddress),
amount: amount.toNumber(),
});
} else if (
await isProgrammableNftToken(
solanaCtx.connection,
token.mint?.toString() as string
)
) {
txSig = await Solana.transferProgrammableNft(solanaCtx, {
destination: new PublicKey(destinationAddress),
mint: new PublicKey(token.mint!),
amount: amount.toNumber(),
decimals: token.decimals,
source: new PublicKey(token.address),
});
}
// Use an else here to avoid an extra request if we are transferring sol native mints.
else {
const ocpMintState = await isOpenCreatorProtocol(
solanaCtx.connection,
mintId,
mintInfo
);
if (ocpMintState !== null) {
txSig = await Solana.transferOpenCreatorProtocol(
solanaCtx,
{
destination: new PublicKey(destinationAddress),
amount: amount.toNumber(),
mint: new PublicKey(token.mint!),
},
ocpMintState
);
} else if (isCreatorStandardToken(mintId, mintInfo)) {
txSig = await Solana.transferCreatorStandardToken(solanaCtx, {
destination: new PublicKey(destinationAddress),
mint: new PublicKey(token.mint!),
amount: amount.toNumber(),
decimals: token.decimals,
});
} else if (
await isCardinalWrappedToken(solanaCtx.connection, mintId, mintInfo)
) {
txSig = await Solana.transferCardinalManagedToken(solanaCtx, {
destination: new PublicKey(destinationAddress),
mint: new PublicKey(token.mint!),
amount: amount.toNumber(),
decimals: token.decimals,
});
} else {
txSig = await Solana.transferToken(solanaCtx, {
destination: new PublicKey(destinationAddress),
mint: new PublicKey(token.mint!),
amount: amount.toNumber(),
decimals: token.decimals,
});
}
}
} catch (err: any) {
logger.error("solana transaction failed", err);
setError(err.toString());
setCardType("error");
return;
}

setTxSignature(txSig);

//
// Confirm the tx.
//
try {
await confirmTransaction(
solanaCtx.connection,
txSig,
solanaCtx.commitment !== "confirmed" &&
solanaCtx.commitment !== "finalized"
? "confirmed"
: solanaCtx.commitment
);
setCardType("complete");
if (onComplete) onComplete(txSig);
} catch (err: any) {
logger.error("unable to confirm", err);
setError(err.toString());
setCardType("error");
}
};

return (
<>
{cardType === "confirm" ? (
Expand Down Expand Up @@ -367,87 +239,3 @@ const ConfirmSendSolanaTable: React.FC<{
/>
);
};

export const isCardinalWrappedToken = async (
connection: Connection,
mintId: PublicKey,
mintInfo: RawMintString
) => {
const mintManagerId = (
await programs.tokenManager.pda.findMintManagerId(mintId)
)[0];
if (
!mintInfo.freezeAuthority ||
mintInfo.freezeAuthority !== mintManagerId.toString()
) {
return false;
}

// only need network calls to double confirm but the above check is likely sufficient if we assume it was created correctly
const [tokenManagerId] =
await programs.tokenManager.pda.findTokenManagerAddress(
new PublicKey(mintId)
);
const tokenManagerData = await tryGetAccount(() =>
programs.tokenManager.accounts.getTokenManager(connection, tokenManagerId)
);
if (!tokenManagerData?.parsed) {
return false;
}
try {
programs.transferAuthority.accounts.getTransferAuthority(
connection,
tokenManagerData?.parsed.transferAuthority || new PublicKey("")
);
return true;
} catch (error) {
console.log("Invalid transfer authority");
}
return false;
};

export const isCreatorStandardToken = (
mintId: PublicKey,
mintInfo: RawMintString
) => {
const mintManagerId = findMintManagerId(mintId);
// not network calls involved we can assume this token was created properly if the mint and freeze authority match
return (
mintInfo.freezeAuthority &&
mintInfo.mintAuthority &&
mintInfo.freezeAuthority === mintManagerId.toString() &&
mintInfo.mintAuthority === mintManagerId.toString()
);
};

async function isOpenCreatorProtocol(
connection: Connection,
mintId: PublicKey,
mintInfo: RawMintString
): Promise<MintState | null> {
const mintStatePk = findMintStatePk(mintId);
const accountInfo = (await connection.getAccountInfo(
mintStatePk
)) as AccountInfo<Buffer>;
return accountInfo !== null
? MintState.fromAccountInfo(accountInfo)[0]
: null;
}

async function isProgrammableNftToken(
connection: Connection,
mintAddress: string
): Promise<boolean> {
try {
const metadata = await Metadata.fromAccountAddress(
connection,
await metadataAddress(new PublicKey(mintAddress))
);

return metadata.tokenStandard == TokenStandard.ProgrammableNonFungible;
} catch (error) {
// most likely this happens if the metadata account does not exist
console.log(error);
return false;
}
}
Loading

1 comment on commit 26ca764

@vercel
Copy link

@vercel vercel bot commented on 26ca764 Apr 7, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.