Skip to content

Commit cb02dad

Browse files
authored
chore: refactor to invert logic to injectable (#1584)
* chore: refactor to invert logic to injectable * feat: do auto from airdrop * feat: make it so the adding self signer is automatically * chore: adding in the other transaction filtering based on the keys * fix: edge case on key may not be signer
1 parent a8a7491 commit cb02dad

File tree

2 files changed

+106
-39
lines changed

2 files changed

+106
-39
lines changed

account-kit/react/src/hooks/useSolanaTransaction.ts

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,33 @@ import { useSyncExternalStore } from "react";
1717
import { useAlchemyAccountContext } from "./useAlchemyAccountContext.js";
1818
import type { PromiseOrValue } from "../../../../aa-sdk/core/dist/types/types.js";
1919

20+
/** Used right before we send the transaction out, this is going to be the signer. */
2021
export type PreSend = (
21-
transaction: VersionedTransaction | Transaction,
22-
next: PreSend
22+
this: void,
23+
transaction: VersionedTransaction | Transaction
2324
) => PromiseOrValue<VersionedTransaction | Transaction>;
25+
/**
26+
* Used in the sendTransaction, will transform either the instructions (or the transfer -> instructions) into a transaction
27+
*/
28+
export type TransformInstruction = (
29+
this: void,
30+
instructions: TransactionInstruction[]
31+
) => PromiseOrValue<Transaction | VersionedTransaction>;
32+
export type SolanaTransactionParamOptions = {
33+
preSend?: PreSend;
34+
transformInstruction?: TransformInstruction;
35+
};
2436
export type SolanaTransactionParams =
2537
| {
2638
transfer: {
2739
amount: number;
2840
toAddress: string;
2941
};
30-
preSend?: PreSend;
42+
transactionComponents?: SolanaTransactionParamOptions;
3143
}
3244
| {
3345
instructions: TransactionInstruction[];
34-
preSend?: PreSend;
46+
transactionComponents?: SolanaTransactionParamOptions;
3547
};
3648
/**
3749
* We wanted to make sure that this will be using the same useMutation that the
@@ -106,39 +118,88 @@ export function useSolanaTransaction(
106118
() => getSolanaConnection(config)
107119
);
108120
const mutation = useMutation({
109-
mutationFn: async (params: SolanaTransactionParams) => {
110-
if (!signer) throw new Error("Not ready");
111-
if (!connection) throw new Error("Not ready");
112-
const instructions =
113-
"instructions" in params
114-
? params.instructions
115-
: [
116-
SystemProgram.transfer({
117-
fromPubkey: new PublicKey(signer.address),
118-
toPubkey: new PublicKey(params.transfer.toAddress),
119-
lamports: params.transfer.amount,
120-
}),
121-
];
122-
const policyId =
123-
"policyId" in opts ? opts.policyId : backupConnection?.policyId;
124-
let transaction: VersionedTransaction | Transaction = policyId
125-
? await signer.addSponsorship(instructions, connection, policyId)
126-
: await signer.createTransfer(instructions, connection);
121+
mutationFn: async ({
122+
transactionComponents: {
123+
preSend,
124+
transformInstruction = mapTransformInstructions.default,
125+
} = {},
126+
...params
127+
}: SolanaTransactionParams) => {
128+
const instructions = getInstructions();
129+
let transaction: VersionedTransaction | Transaction =
130+
await transformInstruction(instructions);
127131

128-
const iSign: PreSend = async (t) => {
129-
await signer.addSignature(t);
130-
return t;
131-
};
132-
const preSend = params.preSend || iSign;
133-
transaction = await preSend(transaction, iSign);
132+
transaction = (await preSend?.(transaction)) || transaction;
134133

135-
const hash = await solanaNetwork.broadcast(connection, transaction);
134+
if (needsSignerToSign()) {
135+
await signer?.addSignature(transaction);
136+
}
137+
138+
const localConnection = connection || missing("connection");
139+
const hash = await solanaNetwork.broadcast(localConnection, transaction);
136140
return { hash };
141+
142+
function getInstructions() {
143+
if ("instructions" in params) {
144+
return params.instructions;
145+
}
146+
return [
147+
SystemProgram.transfer({
148+
fromPubkey: new PublicKey(
149+
signer?.address || missing("signer.address")
150+
),
151+
toPubkey: new PublicKey(params.transfer.toAddress),
152+
lamports: params.transfer.amount,
153+
}),
154+
];
155+
}
156+
157+
function needsSignerToSign() {
158+
if ("message" in transaction) {
159+
const message = transaction.message;
160+
return message.staticAccountKeys.some(
161+
(key, index) =>
162+
(() => {
163+
debugger;
164+
return false;
165+
})() ||
166+
(key.toBase58() === signer?.address &&
167+
message?.isAccountSigner(index))
168+
);
169+
}
170+
return transaction.instructions.some((x) =>
171+
x.keys.some(
172+
(x) => x.pubkey.toBase58() === signer?.address && x.isSigner
173+
)
174+
);
175+
}
137176
},
138177
...opts.mutation,
139178
});
140179
const signer: null | SolanaSigner = opts?.signer || fallbackSigner;
141180
const connection = opts?.connection || backupConnection?.connection || null;
181+
const policyId =
182+
"policyId" in opts ? opts.policyId : backupConnection?.policyId;
183+
const mapTransformInstructions: Record<string, TransformInstruction> = {
184+
async addSponsorship(instructions: TransactionInstruction[]) {
185+
return await (signer || missing("signer")).addSponsorship(
186+
instructions,
187+
connection || missing("connection"),
188+
policyId || missing("policyId")
189+
);
190+
},
191+
async createTransfer(instructions: TransactionInstruction[]) {
192+
return await (signer || missing("signer")).createTransfer(
193+
instructions,
194+
connection || missing("connection")
195+
);
196+
},
197+
get default() {
198+
return policyId
199+
? mapTransformInstructions.addSponsorship
200+
: mapTransformInstructions.createTransfer;
201+
},
202+
};
142203

143204
return {
144205
connection,
@@ -148,3 +209,7 @@ export function useSolanaTransaction(
148209
sendTransactionAsync: mutation.mutateAsync,
149210
};
150211
}
212+
213+
function missing(message: string): never {
214+
throw new Error(message);
215+
}

examples/ui-demo/src/components/small-cards/SolanaNftCard.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ import { UserAddressTooltip } from "../user-connection-avatar/UserAddressLink";
3232
import { ExternalLinkIcon } from "lucide-react";
3333
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
3434
type TransactionState = "idle" | "signing" | "sponsoring" | "complete";
35-
async function PK<T>(t: T) {
36-
return t;
37-
}
3835

3936
const states = [
4037
{
@@ -72,7 +69,6 @@ export const SolanaNftCard = () => {
7269
);
7370
},
7471
});
75-
7672
const handleCollectNFT = async () => {
7773
try {
7874
if (!solanaSigner) throw new Error("No signer found");
@@ -101,12 +97,13 @@ export const SolanaNftCard = () => {
10197

10298
const mint = stakeAccount.publicKey;
10399
const tx = await sendTransactionAsync({
104-
preSend: async (transaction, next) => {
105-
if ("version" in transaction) {
106-
transaction.sign([stakeAccount]);
107-
} else {
108-
}
109-
return next(transaction, PK);
100+
transactionComponents: {
101+
preSend: async (transaction) => {
102+
if ("version" in transaction) {
103+
transaction.sign([stakeAccount]);
104+
}
105+
return transaction;
106+
},
110107
},
111108
instructions: [
112109
SystemProgram.createAccount({
@@ -156,6 +153,11 @@ export const SolanaNftCard = () => {
156153
https://explorer.solana.com/address/${mint.toBase58()}?cluster=devnet
157154
`);
158155

156+
setToast({
157+
type: "success",
158+
text: "Transaction sent",
159+
open: true,
160+
});
159161
setTransactionState("complete");
160162
} catch (error) {
161163
console.log(error);

0 commit comments

Comments
 (0)