forked from mito-systems/sol-mem-gen
Add api to create a lock
This commit is contained in:
1 parent
fa168e5c61
commit
303d238d6c
12 files changed
+7471
-13
No files matched your search
@@ -88,6 +88,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
|
||||
// Extract the image URL from the response
|
||||
const imageUrl = result.data?.images?.[0]?.url
|
||||
console.log(imageUrl);
|
||||
|
||||
if (!imageUrl) {
|
||||
console.error('No image URL in response:', result)
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { Connection, Keypair, ParsedInstruction, ParsedTransactionWithMeta, PartiallyDecodedInstruction, PublicKey } from "@solana/web3.js";
|
||||
import { createLock } from "../../../utils/create-lock";
|
||||
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import assert from "assert";
|
||||
import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes";
|
||||
import { BN, min } from "bn.js";
|
||||
|
||||
assert(process.env.NEXT_PUBLIC_SOLANA_RPC_URL);
|
||||
|
||||
const connection = new Connection(process.env.NEXT_PUBLIC_SOLANA_RPC_URL);
|
||||
const MTM_MINT_ADDRESS = process.env.NEXT_PUBLIC_MTM_TOKEN_MINT;
|
||||
|
||||
function isParsedInstruction(
|
||||
instruction: ParsedInstruction | PartiallyDecodedInstruction
|
||||
): instruction is ParsedInstruction {
|
||||
return (instruction as ParsedInstruction).parsed !== undefined;
|
||||
}
|
||||
|
||||
async function extractMTMTransferDetails(
|
||||
connection: Connection,
|
||||
signature: string,
|
||||
mtmMintAddress: string // Mint address of the MTM token
|
||||
) {
|
||||
try {
|
||||
// Fetch the transaction details using the signature
|
||||
const transaction: ParsedTransactionWithMeta | null = await connection.getParsedTransaction(signature, {
|
||||
maxSupportedTransactionVersion: 0, // Ensure compatibility with legacy transactions
|
||||
});
|
||||
|
||||
if (!transaction) {
|
||||
throw new Error('Transaction not found');
|
||||
}
|
||||
|
||||
// Extract the "from", "to", and "amount" for the MTM token transfer
|
||||
let fromAddress: string | null = null;
|
||||
let toAddress: string | null = null;
|
||||
let tokenAmount: number | null = null;
|
||||
|
||||
// Function to process instructions
|
||||
const processInstructions = (instructions: ParsedInstruction[]) => {
|
||||
instructions.forEach((instruction: ParsedInstruction) => {
|
||||
if (instruction.programId.equals(new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'))) {
|
||||
// Check if the instruction is a transfer instruction
|
||||
if (instruction.parsed.type === 'transfer' || instruction.parsed.type === 'transferChecked') {
|
||||
const parsedInfo = instruction.parsed.info;
|
||||
const mint = parsedInfo.mint;
|
||||
|
||||
// Check if the mint address matches the provided MTM mint address
|
||||
if (mint === mtmMintAddress) {
|
||||
fromAddress = parsedInfo.source;
|
||||
toAddress = parsedInfo.destination;
|
||||
tokenAmount = parsedInfo.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Filter out PartiallyDecodedInstruction and process only ParsedInstruction
|
||||
const parsedInstructions = transaction.transaction.message.instructions.filter(isParsedInstruction);
|
||||
|
||||
// Process top-level instructions
|
||||
processInstructions(parsedInstructions);
|
||||
|
||||
// Process inner instructions (if any)
|
||||
if (transaction.meta?.innerInstructions) {
|
||||
transaction.meta.innerInstructions.forEach((inner) => {
|
||||
const innerParsedInstructions = inner.instructions.filter(isParsedInstruction);
|
||||
processInstructions(innerParsedInstructions);
|
||||
});
|
||||
}
|
||||
|
||||
if (!fromAddress || !toAddress || !tokenAmount) {
|
||||
throw new Error('No matching MTM token transfer found in the transaction');
|
||||
}
|
||||
|
||||
return { fromAddress, toAddress, tokenAmount };
|
||||
} catch (error) {
|
||||
console.error('Error extracting MTM transfer details:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
// Get signature from URL params
|
||||
const { searchParams } = new URL(req.url);
|
||||
const signature = searchParams.get('signature') || '4HBtnoNUuMGpmbhD9cPiJtbxkhux31pfZs3HYud5eopAU69RaC4UbJsYdj83eafFxV6eH8pSaRgqELrwyjrWp7yz';
|
||||
let amount = new BN(0);
|
||||
|
||||
if (!signature) {
|
||||
return NextResponse.json(
|
||||
{ error: "Transaction signature is required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!MTM_MINT_ADDRESS) {
|
||||
return NextResponse.json(
|
||||
{ error: "MTM_MINT_ADDRESS environment variable is not set" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// Extract transaction details
|
||||
// const result = await extractMTMTransferDetails(
|
||||
// connection,
|
||||
// signature,
|
||||
// MTM_MINT_ADDRESS
|
||||
// );
|
||||
|
||||
// if (!result) {
|
||||
// return NextResponse.json(
|
||||
// { error: "Failed to extract transaction details" },
|
||||
// { status: 500 }
|
||||
// );
|
||||
// }
|
||||
|
||||
// console.log({ result });
|
||||
|
||||
// // Validate extracted values
|
||||
// if (!result.fromAddress || result.tokenAmount <= 0) {
|
||||
// return NextResponse.json(
|
||||
// { error: "Invalid transaction details extracted" },
|
||||
// { status: 400 }
|
||||
// );
|
||||
// }
|
||||
|
||||
assert(process.env.USER_PRIVATE_KEY, 'USER_PRIVATE_KEY is required');
|
||||
assert(process.env.CLIFF_TIME, 'CLIFF_TIME is required');
|
||||
|
||||
const USER_PRIVATE_KEY = process.env.USER_PRIVATE_KEY;
|
||||
const CLIFF_TIME = process.env.CLIFF_TIME;
|
||||
|
||||
const duration = new BN(CLIFF_TIME).add(new BN(Math.floor(Date.now() / 1000)));
|
||||
const tokenLockerKeypair = Keypair.fromSecretKey(bs58.decode(USER_PRIVATE_KEY));
|
||||
const recipientPublicKey = new PublicKey('Bnnq8n3rRKZe8NJAYn4vBkxp1v8Bnc6zXpUPpDeujCu');
|
||||
|
||||
amount = amount.add(new BN(1000000));
|
||||
|
||||
// Call createLock function with extracted values
|
||||
let escrow;
|
||||
try {
|
||||
escrow = await createLock(tokenLockerKeypair, recipientPublicKey, duration, amount);
|
||||
} catch (error) {
|
||||
console.error('Error creating lock:', error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to create lock" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log({ escrow });
|
||||
|
||||
// Return successful response
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
sender: recipientPublicKey,
|
||||
amount: amount,
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('API route error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* Methods from jup-lock:
|
||||
* - createLockerProgram
|
||||
* - deriveEscrow
|
||||
* - createVestingPlanV2
|
||||
* Reference: https://github.com/jup-ag/jup-lock/blob/main/tests/locker_utils/index.ts
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import 'dotenv/config';
|
||||
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
createAssociatedTokenAccountInstruction,
|
||||
getAssociatedTokenAddressSync,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from '@solana/spl-token';
|
||||
import {
|
||||
AnchorProvider,
|
||||
BN,
|
||||
Program,
|
||||
Wallet,
|
||||
web3,
|
||||
workspace,
|
||||
} from '@coral-xyz/anchor';
|
||||
import { AccountMeta, Connection, SendTransactionError } from '@solana/web3.js';
|
||||
|
||||
// TODO: Generate type file from IDL json
|
||||
import { Locker } from '../../target/types/locker';
|
||||
import { TokenExtensionUtil } from './token-2022/token-extensions';
|
||||
import {
|
||||
OptionRemainingAccountsInfoData,
|
||||
RemainingAccountsBuilder,
|
||||
RemainingAccountsType,
|
||||
} from './token-2022/remaining-accounts';
|
||||
|
||||
assert(process.env.RPC_ENDPOINT);
|
||||
|
||||
const connection = new Connection(process.env.RPC_ENDPOINT);
|
||||
|
||||
const ESCROW_USE_SPL_TOKEN = 0;
|
||||
|
||||
const MEMO_PROGRAM = new web3.PublicKey(
|
||||
"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
|
||||
);
|
||||
|
||||
export function createLockerProgram(wallet: Wallet): Program<Locker> {
|
||||
const provider = new AnchorProvider(connection, wallet, {
|
||||
maxRetries: 3,
|
||||
});
|
||||
provider.opts.commitment = 'confirmed';
|
||||
|
||||
return workspace.Locker as Program<Locker>;
|
||||
}
|
||||
|
||||
export function deriveEscrow(base: web3.PublicKey, programId: web3.PublicKey) {
|
||||
return web3.PublicKey.findProgramAddressSync(
|
||||
[Buffer.from('escrow'), base.toBuffer()],
|
||||
programId
|
||||
);
|
||||
}
|
||||
|
||||
export interface CreateVestingPlanParams {
|
||||
ownerKeypair: web3.Keypair;
|
||||
tokenMint: web3.PublicKey;
|
||||
isAssertion: boolean;
|
||||
vestingStartTime: BN;
|
||||
cliffTime: BN;
|
||||
frequency: BN;
|
||||
cliffUnlockAmount: BN;
|
||||
amountPerPeriod: BN;
|
||||
numberOfPeriod: BN;
|
||||
recipient: web3.PublicKey;
|
||||
updateRecipientMode: number;
|
||||
cancelMode: number;
|
||||
tokenProgram?: web3.PublicKey;
|
||||
}
|
||||
|
||||
// V2 instructions
|
||||
export async function createVestingPlanV2(params: CreateVestingPlanParams) {
|
||||
let {
|
||||
tokenMint,
|
||||
ownerKeypair,
|
||||
vestingStartTime,
|
||||
cliffTime,
|
||||
frequency,
|
||||
cliffUnlockAmount,
|
||||
amountPerPeriod,
|
||||
numberOfPeriod,
|
||||
recipient,
|
||||
updateRecipientMode,
|
||||
cancelMode,
|
||||
tokenProgram,
|
||||
} = params;
|
||||
|
||||
const program = createLockerProgram(new Wallet(ownerKeypair));
|
||||
|
||||
const baseKP = web3.Keypair.generate();
|
||||
|
||||
let [escrow] = deriveEscrow(baseKP.publicKey, program.programId);
|
||||
|
||||
const senderToken = getAssociatedTokenAddressSync(
|
||||
tokenMint,
|
||||
ownerKeypair.publicKey,
|
||||
false,
|
||||
tokenProgram,
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
const escrowToken = getAssociatedTokenAddressSync(
|
||||
tokenMint,
|
||||
escrow,
|
||||
true,
|
||||
tokenProgram,
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
let remainingAccountsInfo = null;
|
||||
let remainingAccounts: AccountMeta[] = [];
|
||||
if (tokenProgram == TOKEN_2022_PROGRAM_ID) {
|
||||
let inputTransferHookAccounts =
|
||||
await TokenExtensionUtil.getExtraAccountMetasForTransferHook(
|
||||
program.provider.connection,
|
||||
tokenMint,
|
||||
senderToken,
|
||||
escrowToken,
|
||||
ownerKeypair.publicKey,
|
||||
TOKEN_2022_PROGRAM_ID
|
||||
);
|
||||
|
||||
[remainingAccountsInfo, remainingAccounts] = new RemainingAccountsBuilder()
|
||||
.addSlice(
|
||||
RemainingAccountsType.TransferHookEscrow,
|
||||
inputTransferHookAccounts
|
||||
)
|
||||
.build() as [OptionRemainingAccountsInfoData, AccountMeta[]];
|
||||
}
|
||||
|
||||
assert(tokenProgram);
|
||||
|
||||
try {
|
||||
const transaction = await program.methods
|
||||
.createVestingEscrowV2(
|
||||
{
|
||||
vestingStartTime,
|
||||
cliffTime,
|
||||
frequency,
|
||||
cliffUnlockAmount,
|
||||
amountPerPeriod,
|
||||
numberOfPeriod,
|
||||
updateRecipientMode,
|
||||
cancelMode,
|
||||
},
|
||||
remainingAccountsInfo
|
||||
)
|
||||
.accounts({
|
||||
base: baseKP.publicKey,
|
||||
senderToken,
|
||||
escrowToken,
|
||||
recipient,
|
||||
tokenMint,
|
||||
sender: ownerKeypair.publicKey,
|
||||
tokenProgram,
|
||||
systemProgram: web3.SystemProgram.programId,
|
||||
escrow,
|
||||
// TODO: Fix type error for escrowToken
|
||||
} as any)
|
||||
.remainingAccounts(remainingAccounts ? remainingAccounts : [])
|
||||
.preInstructions([
|
||||
createAssociatedTokenAccountInstruction(
|
||||
ownerKeypair.publicKey,
|
||||
escrowToken,
|
||||
escrow,
|
||||
tokenMint,
|
||||
tokenProgram,
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
),
|
||||
])
|
||||
.signers([baseKP, ownerKeypair])
|
||||
.rpc();
|
||||
|
||||
return escrow;
|
||||
} catch (error) {
|
||||
if (error instanceof SendTransactionError) {
|
||||
console.error('Transaction failed:', error.message);
|
||||
console.error('Logs:', error.logs);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ClaimTokenParamsV2 {
|
||||
isAssertion: boolean;
|
||||
escrow: web3.PublicKey;
|
||||
recipient: web3.Keypair;
|
||||
maxAmount: BN;
|
||||
recipientToken: web3.PublicKey;
|
||||
tokenProgram: web3.PublicKey;
|
||||
}
|
||||
|
||||
export async function claimTokenV2(params: ClaimTokenParamsV2) {
|
||||
let { isAssertion, escrow, recipient, maxAmount, recipientToken } = params;
|
||||
const program = createLockerProgram(new Wallet(recipient));
|
||||
const escrowState = await program.account.vestingEscrow.fetch(escrow);
|
||||
const tokenProgram =
|
||||
escrowState.tokenProgramFlag == ESCROW_USE_SPL_TOKEN
|
||||
? TOKEN_PROGRAM_ID
|
||||
: TOKEN_2022_PROGRAM_ID;
|
||||
|
||||
const escrowToken = getAssociatedTokenAddressSync(
|
||||
escrowState.tokenMint,
|
||||
escrow,
|
||||
true,
|
||||
tokenProgram,
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
let remainingAccountsInfo = null;
|
||||
let remainingAccounts: AccountMeta[] | undefined = [];
|
||||
if (tokenProgram == TOKEN_2022_PROGRAM_ID) {
|
||||
let claimTransferHookAccounts =
|
||||
await TokenExtensionUtil.getExtraAccountMetasForTransferHook(
|
||||
program.provider.connection,
|
||||
escrowState.tokenMint,
|
||||
escrowToken,
|
||||
recipientToken,
|
||||
escrow,
|
||||
TOKEN_2022_PROGRAM_ID
|
||||
);
|
||||
|
||||
[remainingAccountsInfo, remainingAccounts] = new RemainingAccountsBuilder()
|
||||
.addSlice(
|
||||
RemainingAccountsType.TransferHookEscrow,
|
||||
claimTransferHookAccounts
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
const tx = await program.methods
|
||||
.claimV2(maxAmount, remainingAccountsInfo)
|
||||
.accounts({
|
||||
tokenProgram,
|
||||
tokenMint: escrowState.tokenMint,
|
||||
memoProgram: MEMO_PROGRAM,
|
||||
escrow,
|
||||
escrowToken,
|
||||
recipient: recipient.publicKey,
|
||||
recipientToken,
|
||||
} as any)
|
||||
.remainingAccounts(remainingAccounts ? remainingAccounts : [])
|
||||
.signers([recipient])
|
||||
.rpc();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Reference: https://github.com/jup-ag/jup-lock/blob/main/tests/locker_utils/index.ts
|
||||
|
||||
import { AccountMeta } from '@solana/web3.js';
|
||||
|
||||
export enum RemainingAccountsType {
|
||||
TransferHookEscrow = 'transferHookEscrow',
|
||||
}
|
||||
|
||||
type RemainingAccountsAnchorType = { transferHookEscrow: {} };
|
||||
|
||||
export type RemainingAccountsSliceData = {
|
||||
accountsType: RemainingAccountsAnchorType;
|
||||
length: number;
|
||||
};
|
||||
|
||||
export type RemainingAccountsInfoData = {
|
||||
slices: RemainingAccountsSliceData[];
|
||||
};
|
||||
|
||||
// Option<RemainingAccountsInfoData> on Rust
|
||||
// null is treated as None in Rust. undefined doesn't work.
|
||||
export type OptionRemainingAccountsInfoData = RemainingAccountsInfoData | null;
|
||||
|
||||
export class RemainingAccountsBuilder {
|
||||
private remainingAccounts: AccountMeta[] = [];
|
||||
private slices: RemainingAccountsSliceData[] = [];
|
||||
|
||||
constructor() {}
|
||||
|
||||
addSlice(
|
||||
accountsType: RemainingAccountsType,
|
||||
accounts?: AccountMeta[]
|
||||
): this {
|
||||
if (!accounts || accounts.length === 0) return this;
|
||||
|
||||
this.slices.push({
|
||||
accountsType: { [accountsType]: {} } as RemainingAccountsAnchorType,
|
||||
length: accounts.length,
|
||||
});
|
||||
this.remainingAccounts.push(...accounts);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): [OptionRemainingAccountsInfoData, AccountMeta[] | undefined] {
|
||||
return this.slices.length === 0
|
||||
? [null, undefined]
|
||||
: [{ slices: this.slices }, this.remainingAccounts];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Reference: https://github.com/jup-ag/jup-lock/blob/main/tests/locker_utils/index.ts
|
||||
|
||||
import {
|
||||
AccountMeta,
|
||||
Connection,
|
||||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import {
|
||||
addExtraAccountsToInstruction,
|
||||
getMint,
|
||||
getTransferHook,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
} from '@solana/spl-token';
|
||||
|
||||
export class TokenExtensionUtil {
|
||||
public static async getExtraAccountMetasForTransferHook(
|
||||
connection: Connection,
|
||||
tokenMint: PublicKey,
|
||||
source: PublicKey,
|
||||
destination: PublicKey,
|
||||
owner: PublicKey,
|
||||
tokenProgram: PublicKey
|
||||
): Promise<AccountMeta[] | undefined> {
|
||||
let mint = await getMint(connection, tokenMint, 'confirmed', tokenProgram);
|
||||
const transferHook = getTransferHook(mint);
|
||||
|
||||
if (!transferHook) return undefined;
|
||||
|
||||
const instruction = new TransactionInstruction({
|
||||
programId: TOKEN_2022_PROGRAM_ID,
|
||||
keys: [
|
||||
{ pubkey: source, isSigner: false, isWritable: false },
|
||||
{
|
||||
pubkey: tokenMint,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{ pubkey: destination, isSigner: false, isWritable: false },
|
||||
{ pubkey: owner, isSigner: false, isWritable: false },
|
||||
{ pubkey: owner, isSigner: false, isWritable: false },
|
||||
],
|
||||
});
|
||||
|
||||
// Note:
|
||||
await addExtraAccountsToInstruction(
|
||||
connection,
|
||||
instruction,
|
||||
tokenMint,
|
||||
'confirmed',
|
||||
transferHook.programId,
|
||||
);
|
||||
|
||||
const extraAccountMetas = instruction.keys.slice(5);
|
||||
return extraAccountMetas.length > 0 ? extraAccountMetas : undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import assert from 'assert';
|
||||
import BN from 'bn.js';
|
||||
import 'dotenv/config';
|
||||
import bs58 from 'bs58';
|
||||
|
||||
import * as anchor from "@coral-xyz/anchor";
|
||||
import {
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
|
||||
import { createVestingPlanV2 } from '../locker-utils';
|
||||
|
||||
assert(process.env.RPC_ENDPOINT);
|
||||
assert(process.env.USER_PRIVATE_KEY);
|
||||
//assert(process.env.RECIPIENT_PUBLIC_KEY);
|
||||
assert(process.env.MTM_MINT);
|
||||
|
||||
const RPC_ENDPOINT= process.env.RPC_ENDPOINT;
|
||||
const MTM_MINT = process.env.MTM_MINT;
|
||||
const USER_PRIVATE_KEY = process.env.USER_PRIVATE_KEY;
|
||||
|
||||
const userKP = anchor.web3.Keypair.fromSecretKey(bs58.decode(USER_PRIVATE_KEY));
|
||||
|
||||
const connection = new Connection(RPC_ENDPOINT);
|
||||
const token = new PublicKey(MTM_MINT);
|
||||
|
||||
const provider = new anchor.AnchorProvider(
|
||||
connection,
|
||||
new anchor.Wallet(userKP),
|
||||
// Commitment level required for simulating transaction
|
||||
{ preflightCommitment: 'processed' }
|
||||
);
|
||||
|
||||
anchor.setProvider(provider);
|
||||
|
||||
export async function getMTMBalance (senderKeypair: anchor.web3.Keypair): Promise<BN> {
|
||||
const mintPublicKey = new PublicKey(MTM_MINT);
|
||||
const publicKey = senderKeypair.publicKey;
|
||||
const tokenAccounts = await connection.getTokenAccountsByOwner(publicKey, { mint: mintPublicKey });
|
||||
|
||||
let balance = new BN(0);
|
||||
for (const tokenAccount of tokenAccounts.value) {
|
||||
const accountInfo = await connection.getParsedAccountInfo(
|
||||
tokenAccount.pubkey,
|
||||
"confirmed"
|
||||
);
|
||||
|
||||
if (!accountInfo.value || Buffer.isBuffer(accountInfo.value.data)) {
|
||||
console.warn(
|
||||
`Token account ${tokenAccount.pubkey.toBase58()} data is not parsed.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const tokenAmount =
|
||||
(accountInfo.value.data as any).parsed?.info?.tokenAmount?.amount || "0";
|
||||
balance = balance.add(new BN(tokenAmount));
|
||||
}
|
||||
|
||||
return balance;
|
||||
};
|
||||
|
||||
export async function createLock(tokenLockerKeypair: anchor.web3.Keypair, recipientPubKey: anchor.web3.PublicKey, duration: BN, balance: BN): Promise<anchor.web3.PublicKey | void> {
|
||||
//const balance = await getMTMBalance(tokenLockerKeypair);
|
||||
|
||||
if (balance.eq(new BN(0))) {
|
||||
console.log('No balance available to create lock, skipping...');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Creating a lock...');
|
||||
|
||||
const escrow = await createVestingPlanV2({
|
||||
ownerKeypair: tokenLockerKeypair,
|
||||
vestingStartTime: new BN(Math.floor(Date.now() / 1000) - 60), // Start immediately
|
||||
tokenMint: token,
|
||||
isAssertion: true,
|
||||
cliffTime: duration,
|
||||
frequency: new BN(1), // Not needed since full unlock happens at cliff
|
||||
cliffUnlockAmount: balance, // The entire amount should be released at cliff
|
||||
amountPerPeriod: new BN(0), // No tokens should be released before cliff
|
||||
numberOfPeriod: new BN(1), // Only release tokens once
|
||||
recipient: recipientPubKey,
|
||||
updateRecipientMode: 0,
|
||||
cancelMode: 1,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
});
|
||||
|
||||
if (escrow) {
|
||||
console.log('Lock created successfully: ', escrow.toString());
|
||||
}
|
||||
|
||||
return escrow;
|
||||
}
|
||||
+32
-11
@@ -1,28 +1,49 @@
|
||||
import { PinataSDK } from 'pinata-web3';
|
||||
import PinataClient from "@pinata/sdk"
|
||||
import 'dotenv/config';
|
||||
import assert from 'assert';
|
||||
|
||||
import { FluxGenerationResult } from '../services/fluxService';
|
||||
import axios from 'axios';
|
||||
import FormData from 'form-data';
|
||||
|
||||
assert(process.env.PINATA_JWT, "PINATA_JWT is required");
|
||||
assert(process.env.PINATA_GATEWAY, "PINATA_GATEWAY is required");
|
||||
assert(process.env.PINATA_API_KEY, "PINATA_API_KEY is required");
|
||||
assert(process.env.PINATA_SECRET_KEY, "PINATA_SECRET_KEY is required");
|
||||
assert(process.env.PINATA_JWT, "PINATA_SECRET_KEY is required");
|
||||
assert(process.env.PINATA_GATEWAY);
|
||||
|
||||
const pinata = new PinataSDK({
|
||||
pinataJwt: process.env.PINATA_JWT,
|
||||
pinataGateway: process.env.PINATA_GATEWAY,
|
||||
const pinata = new PinataClient({
|
||||
pinataApiKey: process.env.PINATA_API_KEY,
|
||||
pinataSecretApiKey: process.env.PINATA_SECRET_KEY,
|
||||
pinataJWTKey: process.env.PINATA_JWT
|
||||
});
|
||||
|
||||
|
||||
export async function uploadToPinata(imageUrl: string): Promise<FluxGenerationResult> {
|
||||
try {
|
||||
const upload = await pinata.upload.url(imageUrl);
|
||||
const response = await axios.get(imageUrl, {
|
||||
responseType: 'arraybuffer'
|
||||
});
|
||||
|
||||
const publicURL = await pinata.gateways.convert(upload.IpfsHash);
|
||||
// Create form data
|
||||
const formData = new FormData();
|
||||
formData.append('file', Buffer.from(response.data), {
|
||||
filename: `image-${Date.now()}.jpg`,
|
||||
contentType: 'image/jpeg',
|
||||
});
|
||||
|
||||
// Use the correct Pinata method for file upload
|
||||
const result = await pinata.pinFileToIPFS(formData);
|
||||
|
||||
if (!result.IpfsHash) {
|
||||
throw new Error('IPFS hash not received from Pinata');
|
||||
}
|
||||
|
||||
const publicURL = `${process.env.PINATA_GATEWAY}/ipfs/${result.IpfsHash}`;
|
||||
|
||||
return { imageUrl: publicURL };
|
||||
} catch (error) {
|
||||
console.error('Error uploading to Pinata:', error)
|
||||
console.error('Error uploading to Pinata:', error);
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'Upload failed'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user