forked from mito-systems/sol-mem-gen
196 lines
4.9 KiB
TypeScript
196 lines
4.9 KiB
TypeScript
/**
|
|
* 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,
|
|
} from '@solana/spl-token';
|
|
import {
|
|
AnchorProvider,
|
|
BN,
|
|
Program,
|
|
Wallet,
|
|
web3,
|
|
workspace,
|
|
} from '@coral-xyz/anchor';
|
|
import { AccountMeta, Connection, TransactionExpiredTimeoutError } 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.NEXT_PUBLIC_SOLANA_RPC_URL);
|
|
|
|
const connection = new Connection(process.env.NEXT_PUBLIC_SOLANA_RPC_URL);
|
|
|
|
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 {
|
|
ownerKeypair,
|
|
tokenMint,
|
|
isAssertion,
|
|
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: OptionRemainingAccountsInfoData | null = 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 {
|
|
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 TransactionExpiredTimeoutError) {
|
|
console.error('Transaction confirmation delayed for', error.signature);
|
|
console.log('Confirming the transaction again...');
|
|
const confirmedTransaction = await connection.getTransaction(error.signature, {
|
|
commitment: 'confirmed',
|
|
maxSupportedTransactionVersion: 0
|
|
});
|
|
|
|
if(confirmedTransaction === null) {
|
|
console.error('Transaction failed for', error.signature);
|
|
}
|
|
|
|
return escrow;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|