mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-17 23:26:46 +00:00
Merge branch 'sendaifun:main' into feature/totalbalance
This commit is contained in:
71
src/actions/closeEmptyTokenAccounts.ts
Normal file
71
src/actions/closeEmptyTokenAccounts.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
import { closeEmptyTokenAccounts } from "../tools";
|
||||
|
||||
const closeEmptyTokenAccountsAction: Action = {
|
||||
name: "CLOSE_EMPTY_TOKEN_ACCOUNTS",
|
||||
similes: [
|
||||
"close token accounts",
|
||||
"remove empty accounts",
|
||||
"clean up token accounts",
|
||||
"close SPL token accounts",
|
||||
"clean wallet",
|
||||
],
|
||||
description: `Close empty SPL Token accounts associated with your wallet to reclaim rent.
|
||||
This action will close both regular SPL Token accounts and Token-2022 accounts that have zero balance. `,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
signature:
|
||||
"3KmPyiZvJQk8CfBVVaz8nf3c2crb6iqjQVDqNxknnusyb1FTFpXqD8zVSCBAd1X3rUcD8WiG1bdSjFbeHsmcYGXY",
|
||||
accountsClosed: 10,
|
||||
},
|
||||
explanation: "Closed 10 empty token accounts successfully.",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
signature: "",
|
||||
accountsClosed: 0,
|
||||
},
|
||||
explanation: "No empty token accounts were found to close.",
|
||||
},
|
||||
],
|
||||
],
|
||||
schema: z.object({}),
|
||||
handler: async (agent: SolanaAgentKit) => {
|
||||
try {
|
||||
const result = await closeEmptyTokenAccounts(agent);
|
||||
|
||||
if (result.size === 0) {
|
||||
return {
|
||||
status: "success",
|
||||
signature: "",
|
||||
accountsClosed: 0,
|
||||
message: "No empty token accounts found to close",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
signature: result.signature,
|
||||
accountsClosed: result.size,
|
||||
message: `Successfully closed ${result.size} empty token accounts`,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
status: "error",
|
||||
message: `Failed to close empty token accounts: ${error.message}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default closeEmptyTokenAccountsAction;
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
create_TipLink,
|
||||
listNFTForSale,
|
||||
cancelListing,
|
||||
closeEmptyTokenAccounts,
|
||||
fetchTokenReportSummary,
|
||||
fetchTokenDetailedReport,
|
||||
fetchPythPrice,
|
||||
@@ -75,6 +76,22 @@ import {
|
||||
FlashTradeParams,
|
||||
FlashCloseTradeParams,
|
||||
} from "../types";
|
||||
import {
|
||||
createCollection,
|
||||
createSingle,
|
||||
} from "../tools/create_3land_collectible";
|
||||
import {
|
||||
CreateCollectionOptions,
|
||||
CreateSingleOptions,
|
||||
StoreInitOptions,
|
||||
} from "@3land/listings-sdk/dist/types/implementation/implementationTypes";
|
||||
import { create_squads_multisig } from "../tools/squads_multisig/create_multisig";
|
||||
import { deposit_to_multisig } from "../tools/squads_multisig/deposit_to_multisig";
|
||||
import { transfer_from_multisig } from "../tools/squads_multisig/transfer_from_multisig";
|
||||
import { create_proposal } from "../tools/squads_multisig/create_proposal";
|
||||
import { approve_proposal } from "../tools/squads_multisig/approve_proposal";
|
||||
import { execute_transaction } from "../tools/squads_multisig/execute_proposal";
|
||||
import { reject_proposal } from "../tools/squads_multisig/reject_proposal";
|
||||
|
||||
/**
|
||||
* Main class for interacting with Solana blockchain
|
||||
@@ -550,6 +567,13 @@ export class SolanaAgentKit {
|
||||
return cancelListing(this, nftMint);
|
||||
}
|
||||
|
||||
async closeEmptyTokenAccounts(): Promise<{
|
||||
signature: string;
|
||||
size: number;
|
||||
}> {
|
||||
return closeEmptyTokenAccounts(this);
|
||||
}
|
||||
|
||||
async fetchTokenReportSummary(mint: string): Promise<TokenCheck> {
|
||||
return fetchTokenReportSummary(mint);
|
||||
}
|
||||
@@ -575,4 +599,72 @@ export class SolanaAgentKit {
|
||||
async flashCloseTrade(params: FlashCloseTradeParams): Promise<string> {
|
||||
return flashCloseTrade(this, params);
|
||||
}
|
||||
|
||||
async create3LandCollection(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionOpts: CreateCollectionOptions,
|
||||
): Promise<string> {
|
||||
const tx = await createCollection(optionsWithBase58, collectionOpts);
|
||||
return `Transaction: ${tx}`;
|
||||
}
|
||||
|
||||
async create3LandNft(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionAccount: string,
|
||||
createItemOptions: CreateSingleOptions,
|
||||
isMainnet: boolean,
|
||||
): Promise<string> {
|
||||
const tx = await createSingle(
|
||||
optionsWithBase58,
|
||||
collectionAccount,
|
||||
createItemOptions,
|
||||
isMainnet,
|
||||
);
|
||||
return `Transaction: ${tx}`;
|
||||
}
|
||||
|
||||
async createSquadsMultisig(creator: PublicKey): Promise<string> {
|
||||
return create_squads_multisig(this, creator);
|
||||
}
|
||||
|
||||
async depositToMultisig(
|
||||
amount: number,
|
||||
vaultIndex: number = 0,
|
||||
mint?: PublicKey,
|
||||
): Promise<string> {
|
||||
return deposit_to_multisig(this, amount, vaultIndex, mint);
|
||||
}
|
||||
|
||||
async transferFromMultisig(
|
||||
amount: number,
|
||||
to: PublicKey,
|
||||
vaultIndex: number = 0,
|
||||
mint?: PublicKey,
|
||||
): Promise<string> {
|
||||
return transfer_from_multisig(this, amount, to, vaultIndex, mint);
|
||||
}
|
||||
|
||||
async createMultisigProposal(
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
return create_proposal(this, transactionIndex);
|
||||
}
|
||||
|
||||
async approveMultisigProposal(
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
return approve_proposal(this, transactionIndex);
|
||||
}
|
||||
|
||||
async rejectMultisigProposal(
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
return reject_proposal(this, transactionIndex);
|
||||
}
|
||||
|
||||
async executeMultisigTransaction(
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
return execute_transaction(this, transactionIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,12 @@ import {
|
||||
SolanaAgentKit,
|
||||
} from "../index";
|
||||
import { create_image, FEE_TIERS, generateOrdersfromPattern } from "../tools";
|
||||
import { marketTokenMap } from "../utils/flashUtils";
|
||||
import {
|
||||
CreateCollectionOptions,
|
||||
CreateSingleOptions,
|
||||
StoreInitOptions,
|
||||
} from "@3land/listings-sdk/dist/types/implementation/implementationTypes";
|
||||
|
||||
export class SolanaBalanceTool extends Tool {
|
||||
name = "solana_balance";
|
||||
@@ -798,10 +804,14 @@ export class SolanaFlashOpenTrade extends Tool {
|
||||
if (!parsedInput.token) {
|
||||
throw new Error("Token is required, received: " + parsedInput.token);
|
||||
}
|
||||
if (!["SOL", "BTC", "ETH", "USDC"].includes(parsedInput.token)) {
|
||||
if (!Object.keys(marketTokenMap).includes(parsedInput.token)) {
|
||||
throw new Error(
|
||||
'Token must be one of ["SOL", "BTC", "ETH", "USDC"], received: ' +
|
||||
parsedInput.token,
|
||||
"Token must be one of " +
|
||||
Object.keys(marketTokenMap).join(", ") +
|
||||
", received: " +
|
||||
parsedInput.token +
|
||||
"\n" +
|
||||
"Please check https://beast.flash.trade/ for the list of supported tokens",
|
||||
);
|
||||
}
|
||||
if (!["long", "short"].includes(parsedInput.type)) {
|
||||
@@ -2250,6 +2260,434 @@ export class SolanaFetchTokenDetailedReportTool extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class Solana3LandCreateSingle extends Tool {
|
||||
name = "3land_minting_tool";
|
||||
description = `Creates an NFT and lists it on 3.land's website
|
||||
|
||||
Inputs:
|
||||
privateKey (required): represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
collectionAccount (optional): represents the account for the nft collection
|
||||
itemName (required): the name of the NFT
|
||||
sellerFee (required): the fee of the seller
|
||||
itemAmount (required): the amount of the NFTs that can be minted
|
||||
itemDescription (required): the description of the NFT
|
||||
traits (required): the traits of the NFT [{trait_type: string, value: string}]
|
||||
price (required): the price of the item, if is 0 the listing will be free
|
||||
mainImageUrl (required): the main image of the NFT
|
||||
coverImageUrl (optional): the cover image of the NFT
|
||||
splHash (optional): the hash of the spl token, if not provided listing will be in $SOL
|
||||
isMainnet (required): defines is the tx takes places in mainnet
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const privateKey = inputFormat.privateKey;
|
||||
const isMainnet = inputFormat.isMainnet;
|
||||
|
||||
const optionsWithBase58: StoreInitOptions = {
|
||||
...(privateKey && { privateKey }),
|
||||
...(isMainnet && { isMainnet }),
|
||||
};
|
||||
|
||||
const collectionAccount = inputFormat.collectionAccount;
|
||||
|
||||
const itemName = inputFormat?.itemName;
|
||||
const sellerFee = inputFormat?.sellerFee;
|
||||
const itemAmount = inputFormat?.itemAmount;
|
||||
const itemSymbol = inputFormat?.itemSymbol;
|
||||
const itemDescription = inputFormat?.itemDescription;
|
||||
const traits = inputFormat?.traits;
|
||||
const price = inputFormat?.price;
|
||||
const mainImageUrl = inputFormat?.mainImageUrl;
|
||||
const coverImageUrl = inputFormat?.coverImageUrl;
|
||||
const splHash = inputFormat?.splHash;
|
||||
|
||||
const createItemOptions: CreateSingleOptions = {
|
||||
...(itemName && { itemName }),
|
||||
...(sellerFee && { sellerFee }),
|
||||
...(itemAmount && { itemAmount }),
|
||||
...(itemSymbol && { itemSymbol }),
|
||||
...(itemDescription && { itemDescription }),
|
||||
...(traits && { traits }),
|
||||
...(price && { price }),
|
||||
...(mainImageUrl && { mainImageUrl }),
|
||||
...(coverImageUrl && { coverImageUrl }),
|
||||
...(splHash && { splHash }),
|
||||
};
|
||||
|
||||
if (!collectionAccount) {
|
||||
throw new Error("Collection account is required");
|
||||
}
|
||||
|
||||
const tx = await this.solanaKit.create3LandNft(
|
||||
optionsWithBase58,
|
||||
collectionAccount,
|
||||
createItemOptions,
|
||||
isMainnet,
|
||||
);
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `Created listing successfully ${tx}`,
|
||||
transaction: tx,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Solana3LandCreateCollection extends Tool {
|
||||
name = "3land_minting_tool";
|
||||
description = `Creates an NFT Collection that you can visit on 3.land's website (3.land/collection/{collectionAccount})
|
||||
|
||||
Inputs:
|
||||
privateKey (required): represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
isMainnet (required): defines is the tx takes places in mainnet
|
||||
collectionSymbol (required): the symbol of the collection
|
||||
collectionName (required): the name of the collection
|
||||
collectionDescription (required): the description of the collection
|
||||
mainImageUrl (required): the image of the collection
|
||||
coverImageUrl (optional): the cover image of the collection
|
||||
`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const privateKey = inputFormat.privateKey;
|
||||
const isMainnet = inputFormat.isMainnet;
|
||||
|
||||
const optionsWithBase58: StoreInitOptions = {
|
||||
...(privateKey && { privateKey }),
|
||||
...(isMainnet && { isMainnet }),
|
||||
};
|
||||
|
||||
const collectionSymbol = inputFormat?.collectionSymbol;
|
||||
const collectionName = inputFormat?.collectionName;
|
||||
const collectionDescription = inputFormat?.collectionDescription;
|
||||
const mainImageUrl = inputFormat?.mainImageUrl;
|
||||
const coverImageUrl = inputFormat?.coverImageUrl;
|
||||
|
||||
const collectionOpts: CreateCollectionOptions = {
|
||||
...(collectionSymbol && { collectionSymbol }),
|
||||
...(collectionName && { collectionName }),
|
||||
...(collectionDescription && { collectionDescription }),
|
||||
...(mainImageUrl && { mainImageUrl }),
|
||||
...(coverImageUrl && { coverImageUrl }),
|
||||
};
|
||||
|
||||
const tx = await this.solanaKit.create3LandCollection(
|
||||
optionsWithBase58,
|
||||
collectionOpts,
|
||||
);
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `Created Collection successfully ${tx}`,
|
||||
transaction: tx,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCloseEmptyTokenAccounts extends Tool {
|
||||
name = "close_empty_token_accounts";
|
||||
description = `Close all empty spl-token accounts and reclaim the rent`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(): Promise<string> {
|
||||
try {
|
||||
const { signature, size } =
|
||||
await this.solanaKit.closeEmptyTokenAccounts();
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `${size} accounts closed successfully. ${size === 48 ? "48 accounts can be closed in a single transaction try again to close more accounts" : ""}`,
|
||||
signature,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCreate2by2Multisig extends Tool {
|
||||
name = "create_2by2_multisig";
|
||||
description = `Create a 2-of-2 multisig account on Solana with the user and the agent, where both approvals will be required to run the transactions.
|
||||
|
||||
Note: For one AI agent, only one 2-by-2 multisig can be created as it is pair-wise.
|
||||
|
||||
Inputs (JSON string):
|
||||
- creator: string, the public key of the creator (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const creator = new PublicKey(inputFormat.creator);
|
||||
|
||||
const multisig = await this.solanaKit.createSquadsMultisig(creator);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "2-by-2 multisig account created successfully",
|
||||
multisig,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "CREATE_2BY2_MULTISIG_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaDepositTo2by2Multisig extends Tool {
|
||||
name = "deposit_to_2by2_multisig";
|
||||
description = `Deposit funds to a 2-of-2 multisig account on Solana with the user and the agent, where both approvals will be required to run the transactions.
|
||||
|
||||
Inputs (JSON string):
|
||||
- amount: number, the amount to deposit in SOL (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const amount = new Decimal(inputFormat.amount);
|
||||
|
||||
const tx = await this.solanaKit.depositToMultisig(amount.toNumber());
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Funds deposited to 2-by-2 multisig account successfully",
|
||||
transaction: tx,
|
||||
amount: amount.toString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "DEPOSIT_TO_2BY2_MULTISIG_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaTransferFrom2by2Multisig extends Tool {
|
||||
name = "transfer_from_2by2_multisig";
|
||||
description = `Create a transaction to transfer funds from a 2-of-2 multisig account on Solana with the user and the agent, where both approvals will be required to run the transactions.
|
||||
|
||||
Inputs (JSON string):
|
||||
- amount: number, the amount to transfer in SOL (required).
|
||||
- recipient: string, the public key of the recipient (required).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const amount = new Decimal(inputFormat.amount);
|
||||
const recipient = new PublicKey(inputFormat.recipient);
|
||||
|
||||
const tx = await this.solanaKit.transferFromMultisig(
|
||||
amount.toNumber(),
|
||||
recipient,
|
||||
);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Transaction added to 2-by-2 multisig account successfully",
|
||||
transaction: tx,
|
||||
amount: amount.toString(),
|
||||
recipient: recipient.toString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "TRANSFER_FROM_2BY2_MULTISIG_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCreateProposal2by2Multisig extends Tool {
|
||||
name = "create_proposal_2by2_multisig";
|
||||
description = `Create a proposal to transfer funds from a 2-of-2 multisig account on Solana with the user and the agent, where both approvals will be required to run the transactions.
|
||||
|
||||
If transactionIndex is not provided, the latest index will automatically be fetched and used.
|
||||
|
||||
Inputs (JSON string):
|
||||
- transactionIndex: number, the index of the transaction (optional).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const transactionIndex = inputFormat.transactionIndex ?? undefined;
|
||||
|
||||
const tx = await this.solanaKit.createMultisigProposal(transactionIndex);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Proposal created successfully",
|
||||
transaction: tx,
|
||||
transactionIndex: transactionIndex?.toString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "CREATE_PROPOSAL_2BY2_MULTISIG_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaApproveProposal2by2Multisig extends Tool {
|
||||
name = "approve_proposal_2by2_multisig";
|
||||
description = `Approve a proposal to transfer funds from a 2-of-2 multisig account on Solana with the user and the agent, where both approvals will be required to run the transactions.
|
||||
|
||||
If proposalIndex is not provided, the latest index will automatically be fetched and used.
|
||||
|
||||
Inputs (JSON string):
|
||||
- proposalIndex: number, the index of the proposal (optional).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const proposalIndex = inputFormat.proposalIndex ?? undefined;
|
||||
|
||||
const tx = await this.solanaKit.approveMultisigProposal(proposalIndex);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Proposal approved successfully",
|
||||
transaction: tx,
|
||||
proposalIndex: proposalIndex.toString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "APPROVE_PROPOSAL_2BY2_MULTISIG_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaRejectProposal2by2Multisig extends Tool {
|
||||
name = "reject_proposal_2by2_multisig";
|
||||
description = `Reject a proposal to transfer funds from a 2-of-2 multisig account on Solana with the user and the agent, where both approvals will be required to run the transactions.
|
||||
|
||||
If proposalIndex is not provided, the latest index will automatically be fetched and used.
|
||||
|
||||
Inputs (JSON string):
|
||||
- proposalIndex: number, the index of the proposal (optional).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const proposalIndex = inputFormat.proposalIndex ?? undefined;
|
||||
|
||||
const tx = await this.solanaKit.rejectMultisigProposal(proposalIndex);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Proposal rejected successfully",
|
||||
transaction: tx,
|
||||
proposalIndex: proposalIndex.toString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "REJECT_PROPOSAL_2BY2_MULTISIG_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaExecuteProposal2by2Multisig extends Tool {
|
||||
name = "execute_proposal_2by2_multisig";
|
||||
description = `Execute a proposal/transaction to transfer funds from a 2-of-2 multisig account on Solana with the user and the agent, where both approvals will be required to run the transactions.
|
||||
|
||||
If proposalIndex is not provided, the latest index will automatically be fetched and used.
|
||||
|
||||
Inputs (JSON string):
|
||||
- proposalIndex: number, the index of the proposal (optional).`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const inputFormat = JSON.parse(input);
|
||||
const proposalIndex = inputFormat.proposalIndex ?? undefined;
|
||||
|
||||
const tx = await this.solanaKit.executeMultisigTransaction(proposalIndex);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Proposal executed successfully",
|
||||
transaction: tx,
|
||||
proposalIndex: proposalIndex.toString(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "EXECUTE_PROPOSAL_2BY2_MULTISIG_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
return [
|
||||
new SolanaBalanceTool(solanaKit),
|
||||
@@ -2300,11 +2738,22 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaTipLinkTool(solanaKit),
|
||||
new SolanaListNFTForSaleTool(solanaKit),
|
||||
new SolanaCancelNFTListingTool(solanaKit),
|
||||
new SolanaCloseEmptyTokenAccounts(solanaKit),
|
||||
new SolanaFetchTokenReportSummaryTool(solanaKit),
|
||||
new SolanaFetchTokenDetailedReportTool(solanaKit),
|
||||
new Solana3LandCreateSingle(solanaKit),
|
||||
new Solana3LandCreateCollection(solanaKit),
|
||||
new SolanaPerpOpenTradeTool(solanaKit),
|
||||
new SolanaPerpCloseTradeTool(solanaKit),
|
||||
new SolanaFlashOpenTrade(solanaKit),
|
||||
new SolanaFlashCloseTrade(solanaKit),
|
||||
new Solana3LandCreateSingle(solanaKit),
|
||||
new SolanaCreate2by2Multisig(solanaKit),
|
||||
new SolanaDepositTo2by2Multisig(solanaKit),
|
||||
new SolanaTransferFrom2by2Multisig(solanaKit),
|
||||
new SolanaCreateProposal2by2Multisig(solanaKit),
|
||||
new SolanaApproveProposal2by2Multisig(solanaKit),
|
||||
new SolanaRejectProposal2by2Multisig(solanaKit),
|
||||
new SolanaExecuteProposal2by2Multisig(solanaKit),
|
||||
];
|
||||
}
|
||||
|
||||
103
src/tools/close_empty_token_accounts.ts
Normal file
103
src/tools/close_empty_token_accounts.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
PublicKey,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import {
|
||||
AccountLayout,
|
||||
createCloseAccountInstruction,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
|
||||
/**
|
||||
* Close Empty SPL Token accounts of the agent
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @returns transaction signature and total number of accounts closed
|
||||
*/
|
||||
export async function closeEmptyTokenAccounts(
|
||||
agent: SolanaAgentKit,
|
||||
): Promise<{ signature: string; size: number }> {
|
||||
try {
|
||||
const spl_token = await create_close_instruction(agent, TOKEN_PROGRAM_ID);
|
||||
const token_2022 = await create_close_instruction(
|
||||
agent,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
);
|
||||
const transaction = new Transaction();
|
||||
|
||||
const MAX_INSTRUCTIONS = 40; // 40 instructions can be processed in a single transaction without failing
|
||||
|
||||
spl_token
|
||||
.slice(0, Math.min(MAX_INSTRUCTIONS, spl_token.length))
|
||||
.forEach((instruction) => transaction.add(instruction));
|
||||
|
||||
token_2022
|
||||
.slice(0, Math.max(0, MAX_INSTRUCTIONS - spl_token.length))
|
||||
.forEach((instruction) => transaction.add(instruction));
|
||||
|
||||
const size = spl_token.length + token_2022.length;
|
||||
|
||||
if (size === 0) {
|
||||
return {
|
||||
signature: "",
|
||||
size: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const signature = await agent.connection.sendTransaction(transaction, [
|
||||
agent.wallet,
|
||||
]);
|
||||
|
||||
return { signature, size };
|
||||
} catch (error) {
|
||||
throw new Error(`Error closing empty token accounts: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* creates the close instuctions of a spl token account
|
||||
* @param agnet SolanaAgentKit instance
|
||||
* @param token_program Token Program Id
|
||||
* @returns close instuction array
|
||||
*/
|
||||
|
||||
async function create_close_instruction(
|
||||
agent: SolanaAgentKit,
|
||||
token_program: PublicKey,
|
||||
): Promise<TransactionInstruction[]> {
|
||||
const instructions = [];
|
||||
|
||||
const ata_accounts = await agent.connection.getTokenAccountsByOwner(
|
||||
agent.wallet_address,
|
||||
{ programId: token_program },
|
||||
"confirmed",
|
||||
);
|
||||
|
||||
const tokens = ata_accounts.value;
|
||||
|
||||
const accountExceptions = [
|
||||
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC
|
||||
];
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token_data = AccountLayout.decode(tokens[i].account.data);
|
||||
if (
|
||||
token_data.amount === BigInt(0) &&
|
||||
!accountExceptions.includes(token_data.mint.toString())
|
||||
) {
|
||||
const closeInstruction = createCloseAccountInstruction(
|
||||
ata_accounts.value[i].pubkey,
|
||||
agent.wallet_address,
|
||||
agent.wallet_address,
|
||||
[],
|
||||
token_program,
|
||||
);
|
||||
|
||||
instructions.push(closeInstruction);
|
||||
}
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
69
src/tools/create_3land_collectible.ts
Normal file
69
src/tools/create_3land_collectible.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { createCollectionImp, createSingleImp } from "@3land/listings-sdk";
|
||||
import {
|
||||
StoreInitOptions,
|
||||
CreateCollectionOptions,
|
||||
CreateSingleOptions,
|
||||
} from "@3land/listings-sdk/dist/types/implementation/implementationTypes";
|
||||
|
||||
/**
|
||||
* Create a collection on 3Land
|
||||
* @param optionsWithBase58 represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
* @param collectionOpts represents the options for the collection creation
|
||||
* @returns
|
||||
*/
|
||||
export async function createCollection(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionOpts: CreateCollectionOptions,
|
||||
) {
|
||||
try {
|
||||
const collection = await createCollectionImp(
|
||||
optionsWithBase58,
|
||||
collectionOpts,
|
||||
);
|
||||
return collection;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Collection creation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single edition on 3Land
|
||||
* @param optionsWithBase58 represents the privateKey of the wallet - can be an array of numbers, Uint8Array or base58 string
|
||||
* @param collectionAccount represents the account for the nft collection
|
||||
* @param createItemOptions the options for the creation of the single NFT listing
|
||||
* @returns
|
||||
*/
|
||||
export async function createSingle(
|
||||
optionsWithBase58: StoreInitOptions,
|
||||
collectionAccount: string,
|
||||
createItemOptions: CreateSingleOptions,
|
||||
isMainnet: boolean,
|
||||
) {
|
||||
try {
|
||||
const landStore = isMainnet
|
||||
? "AmQNs2kgw4LvS9sm6yE9JJ4Hs3JpVu65eyx9pxMG2xA"
|
||||
: "GyPCu89S63P9NcCQAtuSJesiefhhgpGWrNVJs4bF2cSK";
|
||||
|
||||
const singleEditionTx = await createSingleImp(
|
||||
optionsWithBase58,
|
||||
landStore,
|
||||
collectionAccount,
|
||||
createItemOptions,
|
||||
);
|
||||
return singleEditionTx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Single edition creation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Buy a single edition on 3Land
|
||||
* @param
|
||||
* @returns
|
||||
*/
|
||||
// export async function buySingle() {
|
||||
// try {
|
||||
// } catch (error: any) {
|
||||
// throw new Error(`Buying single edition failed: ${error.message}`);
|
||||
// }
|
||||
// }
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ComputeBudgetProgram } from "@solana/web3.js";
|
||||
import { PoolConfig, Privilege, Side } from "flash-sdk";
|
||||
import { PoolConfig, Side } from "flash-sdk";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
import {
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getNftTradingAccountInfo,
|
||||
fetchOraclePrice,
|
||||
createPerpClient,
|
||||
get_flash_privilege,
|
||||
} from "../utils/flashUtils";
|
||||
import { FlashCloseTradeParams } from "../types";
|
||||
|
||||
@@ -93,7 +94,7 @@ export async function flashCloseTrade(
|
||||
priceWithSlippage,
|
||||
sideEnum,
|
||||
poolConfig,
|
||||
Privilege.Referral,
|
||||
get_flash_privilege(agent),
|
||||
tradingAccounts.nftTradingAccountPk,
|
||||
tradingAccounts.nftReferralAccountPK,
|
||||
tradingAccounts.nftOwnerRebateTokenAccountPk,
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
PerpetualsClient,
|
||||
OraclePrice,
|
||||
PoolConfig,
|
||||
Privilege,
|
||||
Side,
|
||||
CustodyAccount,
|
||||
Custody,
|
||||
@@ -18,6 +17,7 @@ import {
|
||||
OPEN_POSITION_CU,
|
||||
fetchOraclePrice,
|
||||
createPerpClient,
|
||||
get_flash_privilege,
|
||||
} from "../utils/flashUtils";
|
||||
import { FlashTradeParams } from "../types";
|
||||
|
||||
@@ -141,7 +141,7 @@ export async function flashOpenTrade(
|
||||
positionSize,
|
||||
side === "long" ? Side.Long : Side.Short,
|
||||
poolConfig,
|
||||
Privilege.Referral,
|
||||
get_flash_privilege(agent),
|
||||
tradingAccounts.nftTradingAccountPk,
|
||||
tradingAccounts.nftReferralAccountPK,
|
||||
tradingAccounts.nftOwnerRebateTokenAccountPk!,
|
||||
|
||||
@@ -9,17 +9,14 @@ export async function getTokenDataByAddress(
|
||||
throw new Error("Mint address is required");
|
||||
}
|
||||
|
||||
const response = await fetch("https://tokens.jup.ag/tokens?tags=verified", {
|
||||
const response = await fetch(`https://tokens.jup.ag/token/${mint}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = (await response.json()) as JupiterTokenData[];
|
||||
const token = data.find((token: JupiterTokenData) => {
|
||||
return token.address === mint.toBase58();
|
||||
});
|
||||
const token = (await response.json()) as JupiterTokenData;
|
||||
return token;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error fetching token data: ${error.message}`);
|
||||
|
||||
@@ -41,7 +41,12 @@ export * from "./send_compressed_airdrop";
|
||||
export * from "./stake_with_jup";
|
||||
export * from "./stake_with_solayer";
|
||||
export * from "./tensor_trade";
|
||||
|
||||
export * from "./close_empty_token_accounts";
|
||||
|
||||
export * from "./trade";
|
||||
export * from "./transfer";
|
||||
export * from "./flash_open_trade";
|
||||
export * from "./flash_close_trade";
|
||||
|
||||
export * from "./create_3land_collectible";
|
||||
|
||||
52
src/tools/squads_multisig/approve_proposal.ts
Normal file
52
src/tools/squads_multisig/approve_proposal.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Approves a proposal in a Solana multisig wallet.
|
||||
*
|
||||
* @param {SolanaAgentKit} agent - The Solana agent kit instance.
|
||||
* @param {number | bigint} [transactionIndex] - The index of the transaction to approve. If not provided, the current transaction index will be used.
|
||||
* @returns {Promise<string>} - A promise that resolves to the transaction ID of the approved proposal.
|
||||
* @throws {Error} - Throws an error if the approval process fails.
|
||||
*/
|
||||
export async function approve_proposal(
|
||||
agent: SolanaAgentKit,
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
if (!transactionIndex) {
|
||||
transactionIndex = BigInt(currentTransactionIndex);
|
||||
} else if (typeof transactionIndex !== "bigint") {
|
||||
transactionIndex = BigInt(transactionIndex);
|
||||
}
|
||||
// const [proposalPda, proposalBump] = multisig.getProposalPda({
|
||||
// multisigPda,
|
||||
// transactionIndex,
|
||||
// });
|
||||
const multisigTx = multisig.transactions.proposalApprove({
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet.publicKey,
|
||||
multisigPda,
|
||||
transactionIndex: transactionIndex,
|
||||
member: agent.wallet.publicKey,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
62
src/tools/squads_multisig/create_multisig.ts
Normal file
62
src/tools/squads_multisig/create_multisig.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import * as multisig from "@sqds/multisig";
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
|
||||
/**
|
||||
* Creates a new Squads multisig account.
|
||||
*
|
||||
* @param agent - The SolanaAgentKit instance containing the connection and wallet information.
|
||||
* @param creator - The public key of the creator who will be a member of the multisig.
|
||||
* @returns A promise that resolves to the transaction ID of the multisig creation transaction.
|
||||
*
|
||||
* @throws Will throw an error if the transaction fails.
|
||||
*/
|
||||
export async function create_squads_multisig(
|
||||
agent: SolanaAgentKit,
|
||||
creator: PublicKey,
|
||||
): Promise<string> {
|
||||
const connection = agent.connection;
|
||||
const createKey = agent.wallet; // can be any keypair, using the agent wallet as only one multisig is required
|
||||
console.log("Multisig Create Key:", createKey.publicKey.toBase58());
|
||||
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
|
||||
const programConfigPda = multisig.getProgramConfigPda({})[0];
|
||||
|
||||
const programConfig =
|
||||
await multisig.accounts.ProgramConfig.fromAccountAddress(
|
||||
connection,
|
||||
programConfigPda,
|
||||
);
|
||||
|
||||
const configTreasury = programConfig.treasury;
|
||||
const tx = multisig.transactions.multisigCreateV2({
|
||||
blockhash: (await connection.getLatestBlockhash()).blockhash,
|
||||
treasury: configTreasury,
|
||||
createKey: createKey.publicKey,
|
||||
creator: agent.wallet.publicKey,
|
||||
multisigPda,
|
||||
configAuthority: null,
|
||||
timeLock: 0,
|
||||
threshold: 2,
|
||||
rentCollector: null,
|
||||
members: [
|
||||
{
|
||||
key: agent.wallet.publicKey,
|
||||
permissions: multisig.types.Permissions.all(),
|
||||
},
|
||||
{
|
||||
key: creator,
|
||||
permissions: multisig.types.Permissions.all(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
tx.sign([agent.wallet, createKey]);
|
||||
|
||||
const txId = connection.sendRawTransaction(tx.serialize());
|
||||
|
||||
return txId;
|
||||
}
|
||||
48
src/tools/squads_multisig/create_proposal.ts
Normal file
48
src/tools/squads_multisig/create_proposal.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Creates a proposal for a multisig transaction.
|
||||
*
|
||||
* @param {SolanaAgentKit} agent - The Solana agent kit instance.
|
||||
* @param {number | bigint} [transactionIndex] - Optional transaction index. If not provided, the current transaction index will be used.
|
||||
* @returns {Promise<string>} - The transaction ID of the created proposal.
|
||||
* @throws {Error} - Throws an error if the proposal creation fails.
|
||||
*/
|
||||
export async function create_proposal(
|
||||
agent: SolanaAgentKit,
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
if (!transactionIndex) {
|
||||
transactionIndex = BigInt(currentTransactionIndex);
|
||||
} else if (typeof transactionIndex !== "bigint") {
|
||||
transactionIndex = BigInt(transactionIndex);
|
||||
}
|
||||
const multisigTx = multisig.transactions.proposalCreate({
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet_address,
|
||||
multisigPda,
|
||||
transactionIndex,
|
||||
creator: agent.wallet_address,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
91
src/tools/squads_multisig/deposit_to_multisig.ts
Normal file
91
src/tools/squads_multisig/deposit_to_multisig.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import { PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
|
||||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||
import {
|
||||
getAssociatedTokenAddress,
|
||||
createTransferInstruction,
|
||||
getMint,
|
||||
createAssociatedTokenAccountInstruction,
|
||||
} from "@solana/spl-token";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
|
||||
/**
|
||||
* Transfer SOL or SPL tokens to a multisig vault.
|
||||
* @param agent SolanaAgentKit instance
|
||||
* @param amount Amount to transfer
|
||||
* @param vaultIndex Optional vault index, default is 0
|
||||
* @param mint Optional mint address for SPL tokens
|
||||
* @returns Transaction signature
|
||||
*/
|
||||
export async function deposit_to_multisig(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
vaultIndex?: number,
|
||||
mint?: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
let tx: string;
|
||||
if (!vaultIndex) {
|
||||
vaultIndex = 0;
|
||||
}
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const [vaultPda] = multisig.getVaultPda({
|
||||
multisigPda,
|
||||
index: vaultIndex,
|
||||
});
|
||||
const to = vaultPda;
|
||||
if (!mint) {
|
||||
// Transfer native SOL
|
||||
const transaction = new Transaction().add(
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: agent.wallet_address,
|
||||
toPubkey: to,
|
||||
lamports: amount * LAMPORTS_PER_SOL,
|
||||
}),
|
||||
);
|
||||
|
||||
tx = await agent.connection.sendTransaction(transaction, [agent.wallet]);
|
||||
} else {
|
||||
// Transfer SPL token
|
||||
const fromAta = await getAssociatedTokenAddress(
|
||||
mint,
|
||||
agent.wallet_address,
|
||||
);
|
||||
let transaction = new Transaction();
|
||||
const toAta = await getAssociatedTokenAddress(mint, to, true);
|
||||
const toTokenAccountInfo = await agent.connection.getAccountInfo(toAta);
|
||||
// Create associated token account if it doesn't exist
|
||||
if (!toTokenAccountInfo) {
|
||||
transaction.add(
|
||||
createAssociatedTokenAccountInstruction(
|
||||
agent.wallet_address,
|
||||
toAta,
|
||||
to,
|
||||
mint,
|
||||
),
|
||||
);
|
||||
}
|
||||
// Get mint info to determine decimals
|
||||
const mintInfo = await getMint(agent.connection, mint);
|
||||
const adjustedAmount = amount * Math.pow(10, mintInfo.decimals);
|
||||
|
||||
transaction.add(
|
||||
createTransferInstruction(
|
||||
fromAta,
|
||||
toAta,
|
||||
agent.wallet_address,
|
||||
adjustedAmount,
|
||||
),
|
||||
);
|
||||
|
||||
tx = await agent.connection.sendTransaction(transaction, [agent.wallet]);
|
||||
}
|
||||
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
49
src/tools/squads_multisig/execute_proposal.ts
Normal file
49
src/tools/squads_multisig/execute_proposal.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Executes a transaction on the Solana blockchain using the provided agent.
|
||||
*
|
||||
* @param {SolanaAgentKit} agent - The Solana agent kit instance containing the wallet and connection.
|
||||
* @param {number | bigint} [transactionIndex] - Optional transaction index to execute. If not provided, the current transaction index from the multisig account will be used.
|
||||
* @returns {Promise<string>} - A promise that resolves to the transaction signature string.
|
||||
* @throws {Error} - Throws an error if the transaction execution fails.
|
||||
*/
|
||||
export async function execute_transaction(
|
||||
agent: SolanaAgentKit,
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
if (!transactionIndex) {
|
||||
transactionIndex = BigInt(currentTransactionIndex);
|
||||
} else if (typeof transactionIndex !== "bigint") {
|
||||
transactionIndex = BigInt(transactionIndex);
|
||||
}
|
||||
const multisigTx = await multisig.transactions.vaultTransactionExecute({
|
||||
connection: agent.connection,
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet.publicKey,
|
||||
multisigPda,
|
||||
transactionIndex,
|
||||
member: agent.wallet.publicKey,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
52
src/tools/squads_multisig/reject_proposal.ts
Normal file
52
src/tools/squads_multisig/reject_proposal.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Rejects a proposal in a Solana multisig setup.
|
||||
*
|
||||
* @param agent - The SolanaAgentKit instance containing the wallet and connection.
|
||||
* @param transactionIndex - Optional. The index of the transaction to reject. If not provided, the current transaction index will be used.
|
||||
* @returns A promise that resolves to the transaction ID of the rejection transaction.
|
||||
* @throws Will throw an error if the transaction fails.
|
||||
*/
|
||||
export async function reject_proposal(
|
||||
agent: SolanaAgentKit,
|
||||
transactionIndex?: number | bigint,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
if (!transactionIndex) {
|
||||
transactionIndex = BigInt(currentTransactionIndex);
|
||||
} else if (typeof transactionIndex !== "bigint") {
|
||||
transactionIndex = BigInt(transactionIndex);
|
||||
}
|
||||
// const [proposalPda, proposalBump] = multisig.getProposalPda({
|
||||
// multisigPda,
|
||||
// transactionIndex,
|
||||
// });
|
||||
const multisigTx = multisig.transactions.proposalReject({
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet.publicKey,
|
||||
multisigPda,
|
||||
transactionIndex: transactionIndex,
|
||||
member: agent.wallet.publicKey,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
98
src/tools/squads_multisig/transfer_from_multisig.ts
Normal file
98
src/tools/squads_multisig/transfer_from_multisig.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { SolanaAgentKit } from "../../index";
|
||||
import {
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
TransactionInstruction,
|
||||
TransactionMessage,
|
||||
} from "@solana/web3.js";
|
||||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||
import {
|
||||
getAssociatedTokenAddress,
|
||||
createTransferInstruction,
|
||||
getMint,
|
||||
} from "@solana/spl-token";
|
||||
import * as multisig from "@sqds/multisig";
|
||||
const { Multisig } = multisig.accounts;
|
||||
|
||||
/**
|
||||
* Transfer SOL or SPL tokens to a recipient from a multisig vault.
|
||||
* @param agent - SolanaAgentKit instance.
|
||||
* @param amount - Amount to transfer.
|
||||
* @param to - Recipient's public key.
|
||||
* @param vaultIndex - Optional vault index, default is 0.
|
||||
* @param mint - Optional mint address for SPL tokens.
|
||||
* @returns Transaction signature.
|
||||
*/
|
||||
export async function transfer_from_multisig(
|
||||
agent: SolanaAgentKit,
|
||||
amount: number,
|
||||
to: PublicKey,
|
||||
vaultIndex: number = 0,
|
||||
mint?: PublicKey,
|
||||
): Promise<string> {
|
||||
try {
|
||||
let transferInstruction: TransactionInstruction;
|
||||
|
||||
const createKey = agent.wallet;
|
||||
const [multisigPda] = multisig.getMultisigPda({
|
||||
createKey: createKey.publicKey,
|
||||
});
|
||||
const multisigInfo = await Multisig.fromAccountAddress(
|
||||
agent.connection,
|
||||
multisigPda,
|
||||
);
|
||||
const currentTransactionIndex = Number(multisigInfo.transactionIndex);
|
||||
const transactionIndex = BigInt(currentTransactionIndex + 1);
|
||||
const [vaultPda] = multisig.getVaultPda({
|
||||
multisigPda,
|
||||
index: vaultIndex,
|
||||
});
|
||||
|
||||
if (!mint) {
|
||||
// Transfer native SOL
|
||||
transferInstruction = SystemProgram.transfer({
|
||||
fromPubkey: agent.wallet_address,
|
||||
toPubkey: to,
|
||||
lamports: amount * LAMPORTS_PER_SOL,
|
||||
});
|
||||
} else {
|
||||
// Transfer SPL token
|
||||
const fromAta = await getAssociatedTokenAddress(mint, vaultPda, true);
|
||||
const toAta = await getAssociatedTokenAddress(mint, to, true);
|
||||
const mintInfo = await getMint(agent.connection, mint);
|
||||
const adjustedAmount = amount * Math.pow(10, mintInfo.decimals);
|
||||
|
||||
transferInstruction = createTransferInstruction(
|
||||
fromAta,
|
||||
toAta,
|
||||
agent.wallet_address,
|
||||
adjustedAmount,
|
||||
);
|
||||
}
|
||||
|
||||
const transferMessage = new TransactionMessage({
|
||||
payerKey: vaultPda,
|
||||
recentBlockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
instructions: [transferInstruction],
|
||||
});
|
||||
|
||||
const multisigTx = multisig.transactions.vaultTransactionCreate({
|
||||
blockhash: (await agent.connection.getLatestBlockhash()).blockhash,
|
||||
feePayer: agent.wallet_address,
|
||||
multisigPda,
|
||||
transactionIndex,
|
||||
creator: agent.wallet_address,
|
||||
vaultIndex: 0,
|
||||
ephemeralSigners: 0,
|
||||
transactionMessage: transferMessage,
|
||||
});
|
||||
|
||||
multisigTx.sign([agent.wallet]);
|
||||
const tx = await agent.connection.sendRawTransaction(
|
||||
multisigTx.serialize(),
|
||||
);
|
||||
return tx;
|
||||
} catch (error: any) {
|
||||
throw new Error(`Transfer failed: ${error}`);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export interface Config {
|
||||
OPENAI_API_KEY?: string;
|
||||
JUPITER_REFERRAL_ACCOUNT?: string;
|
||||
JUPITER_FEE_BPS?: number;
|
||||
FLASH_PRIVILEGE?: string;
|
||||
}
|
||||
|
||||
export interface Creator {
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { HermesClient } from "@pythnetwork/hermes-client";
|
||||
import { OraclePrice } from "flash-sdk";
|
||||
import { AnchorProvider, BN, Wallet } from "@coral-xyz/anchor";
|
||||
import { PoolConfig, Token, Referral, PerpetualsClient } from "flash-sdk";
|
||||
import {
|
||||
PoolConfig,
|
||||
Token,
|
||||
Referral,
|
||||
PerpetualsClient,
|
||||
Privilege,
|
||||
} from "flash-sdk";
|
||||
import { Cluster, PublicKey, Connection, Keypair } from "@solana/web3.js";
|
||||
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
|
||||
import { SolanaAgentKit } from "../index";
|
||||
|
||||
const POOL_NAMES = [
|
||||
"Crypto.1",
|
||||
@@ -278,3 +285,16 @@ export function createPerpClient(
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export function get_flash_privilege(agent: SolanaAgentKit): Privilege {
|
||||
const FLASH_PRIVILEGE = agent.config.FLASH_PRIVILEGE || "None";
|
||||
|
||||
switch (FLASH_PRIVILEGE.toLowerCase()) {
|
||||
case "referral":
|
||||
return Privilege.Referral;
|
||||
case "nft":
|
||||
return Privilege.NFT;
|
||||
default:
|
||||
return Privilege.None;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user