Skip to content

Commit dd05ab3

Browse files
committed
added treasury amount input + transfer
1 parent 5de84ba commit dd05ab3

File tree

8 files changed

+73
-89
lines changed

8 files changed

+73
-89
lines changed

src/app/api/createCompanyVesting/route.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { NextResponse } from 'next/server';
22
import { Connection, Cluster, clusterApiUrl, PublicKey, Transaction, Keypair, Signer } from '@solana/web3.js';
3-
import { TOKEN_PROGRAM_ID, createMintToInstruction } from '@solana/spl-token';
3+
import { TOKEN_PROGRAM_ID, createMintToInstruction, createTransferInstruction, getAssociatedTokenAddressSync } from '@solana/spl-token';
44
import * as anchor from "@coral-xyz/anchor";
55
import { Program, AnchorProvider } from "@coral-xyz/anchor";
66
import { Vesting, IDL } from "../../types/vesting";
7+
import { getDecimalsAndSupplyToken } from '@/app/lib/utils';
78

89
export async function POST(req: Request) {
910
const contentType = req.headers.get('content-type');
@@ -12,54 +13,61 @@ export async function POST(req: Request) {
1213
return NextResponse.json({ success: false, error: 'Content-Type must be application/json' }, { status: 400 });
1314
}
1415

15-
const { company_name, mint, beneficiary } = await req.json();
16+
const { company_name, mint, signer, treasuryAmount } = await req.json();
1617

17-
// Validate input
18-
if (!company_name || !mint || !beneficiary) {
18+
if (!company_name || !mint || !signer || !treasuryAmount) {
1919
return NextResponse.json({ success: false, error: 'Missing required fields' }, { status: 400 });
2020
}
2121

2222
try {
2323
const cluster = 'devnet' as Cluster;
2424
const connection = new Connection(clusterApiUrl(cluster), "confirmed");
25-
const beneficiaryPubKey = new PublicKey(beneficiary);
26-
const wall = { publicKey: beneficiaryPubKey } as anchor.Wallet;
25+
const vestingAccountOwner = new PublicKey(signer);
26+
const wall = { publicKey: vestingAccountOwner } as anchor.Wallet;
2727
const provider = new AnchorProvider(connection, wall);
2828

2929
anchor.setProvider(provider);
30-
// const program = anchor.workspace.Vesting as Program<Vesting>;
3130
const program = new Program<Vesting>(IDL as Vesting, provider);
3231

3332
const createVestingAccIxn = await program.methods.createVestingAccount(company_name)
3433
.accounts({
35-
signer: beneficiaryPubKey,
34+
signer: provider.wallet.publicKey,
3635
mint: new PublicKey(mint),
3736
tokenProgram: TOKEN_PROGRAM_ID
3837
})
3938
.instruction();
4039

41-
let [treasuryTokenAccount] = PublicKey.findProgramAddressSync(
42-
[Buffer.from("vesting treasury"), Buffer.from(company_name)],
43-
program.programId
44-
);
40+
const sourceATA = getAssociatedTokenAddressSync(
41+
new PublicKey(mint),
42+
vestingAccountOwner
43+
);
44+
45+
let [treasuryTokenAccount] = PublicKey.findProgramAddressSync(
46+
[Buffer.from("vesting treasury"), Buffer.from(company_name)],
47+
program.programId
48+
);
49+
50+
let decimals = 9
51+
const tokenData = await getDecimalsAndSupplyToken(connection, mint);
52+
if(tokenData){
53+
decimals = tokenData.decimals;
54+
}
4555

46-
const amount = 10_000 * 10 ** 9;
47-
const mintTokensIxn = createMintToInstruction(
48-
new PublicKey(mint),
56+
const amount = treasuryAmount * (10**decimals);
57+
const transferTokensToTreasury = createTransferInstruction(
58+
sourceATA,
4959
treasuryTokenAccount,
50-
beneficiaryPubKey,
60+
provider.wallet.publicKey,
5161
amount,
52-
[],
53-
TOKEN_PROGRAM_ID,
5462
);
5563

5664
const tx = new Transaction();
5765
tx.add(createVestingAccIxn);
58-
tx.add(mintTokensIxn);
66+
tx.add(transferTokensToTreasury);
5967

6068
const { blockhash } = await connection.getLatestBlockhash();
6169
const keypair = Keypair.generate();
62-
tx.feePayer = new PublicKey(beneficiary);
70+
tx.feePayer = provider.wallet.publicKey;
6371
tx.recentBlockhash = blockhash;
6472

6573
// tx.partialSign(keypair);

src/app/api/createEmployeeVesting/route.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,24 @@ export async function POST(req: Request) {
1212
return NextResponse.json({ success: false, error: 'Content-Type must be application/json' }, { status: 400 });
1313
}
1414

15-
const { start_time, end_time, total_allocation_amount, cliff, beneficiary, account } = await req.json();
15+
const { start_time, end_time, total_allocation_amount, cliff, beneficiary, account, signer } = await req.json();
1616

17-
if (!start_time || !end_time || !beneficiary || !total_allocation_amount || !cliff || !account) {
17+
if (!start_time || !end_time || !beneficiary || !total_allocation_amount || !cliff || !account || !signer) {
1818
return NextResponse.json({ success: false, error: 'Missing required fields' }, { status: 400 });
1919
}
2020

2121
try {
2222
const cluster = 'devnet' as Cluster;
2323
const connection = new Connection(clusterApiUrl(cluster), "confirmed");
24-
const beneficiaryPubKey = new PublicKey(beneficiary);
25-
const wall = { publicKey: beneficiaryPubKey } as anchor.Wallet;
24+
const signerPubKey = new PublicKey(signer)
25+
const wall = { publicKey: signerPubKey } as anchor.Wallet;
2626
const provider = new AnchorProvider(connection, wall);
27+
console.log("Provider should have signer wallet: ",provider)
2728

2829
anchor.setProvider(provider);
2930
const program = new Program<Vesting>(IDL as Vesting, provider);
3031

31-
const ixn = await program.methods.createEmployeeVesting(new BN(start_time), new BN(end_time), new BN(total_allocation_amount), new BN(cliff), beneficiaryPubKey)
32+
const ixn = await program.methods.createEmployeeVesting(new BN(start_time), new BN(end_time), new BN(total_allocation_amount), new BN(cliff))
3233
.accounts({
3334
beneficiary: new PublicKey(beneficiary),
3435
vestingAccount: new PublicKey(account),
@@ -40,7 +41,8 @@ export async function POST(req: Request) {
4041
tx.add(ixn);
4142

4243
const { blockhash } = await connection.getLatestBlockhash();
43-
tx.feePayer = new PublicKey(beneficiary);
44+
tx.feePayer = provider.wallet.publicKey;
45+
console.log("txn fee payer: ", tx.feePayer.toBase58())
4446
tx.recentBlockhash = blockhash;
4547

4648
const serializedTx = tx.serialize({

src/app/lib/utils.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,7 @@ export async function getDecimalsAndSupplyToken(solanaConnection: Connection, to
6767
let mintPublicKey;
6868
let mintAccountInfo;
6969

70-
try {
7170
mintPublicKey = new PublicKey(tokenMintAddress);
72-
} catch (error) {
73-
console.error(`mintPublicKey: `, error);
74-
return null;
75-
}
7671

7772

7873
try {

src/app/types/vesting.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ export type Vesting = {
197197
]
198198
},
199199
{
200-
"name": "beneficiary"
200+
"name": "beneficiary",
201+
"writable": true
201202
},
202203
{
203204
"name": "vestingAccount"
@@ -260,10 +261,6 @@ export type Vesting = {
260261
{
261262
"name": "cliff",
262263
"type": "i64"
263-
},
264-
{
265-
"name": "benef",
266-
"type": "pubkey"
267264
}
268265
]
269266
},
@@ -668,7 +665,8 @@ export const IDL = {
668665
]
669666
},
670667
{
671-
"name": "beneficiary"
668+
"name": "beneficiary",
669+
"writable": true
672670
},
673671
{
674672
"name": "vesting_account"
@@ -731,10 +729,6 @@ export const IDL = {
731729
{
732730
"name": "cliff",
733731
"type": "i64"
734-
},
735-
{
736-
"name": "benef",
737-
"type": "pubkey"
738732
}
739733
]
740734
},

src/components/vesting/vesting-card.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { TimeInput } from "@nextui-org/date-input";
2020
import useTokenDecimals from "../../hooks/useTokenDecimals";
2121
import { getUnixTimestamp, cliffPeriodToCliffTime } from "@/app/lib/utils"
2222

23+
//move to mainnet, add jupiter tokens list
24+
2325
export default function VestingCard({ account }: { account: string }){
2426
const { getVestingAccountStateQuery, createEmployeeAccountMutation } = useVestingProgramAccount({account: new PublicKey(account)})
2527
const [startDate, setStartDate] = useState<Date>();

src/components/vesting/vesting-data-access.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,16 @@ export function useVestingProgram() {
107107
// create vesting account TXN
108108
const createVestingAccountMutation = useMutation({
109109
mutationKey: ["vestingAccount", "create", { cluster }],
110-
mutationFn: async({ company_name, mint }: CreateVestingArgs) => {
110+
mutationFn: async({ company_name, mint, treasuryAmount }: CreateVestingArgs) => {
111111
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
112112

113113
const apiEndpoint = `${endpoint}/api/createCompanyVesting`
114114

115115
const txn_metadata = await axios.post(apiEndpoint, {
116116
company_name: company_name,
117117
mint: mint,
118-
beneficiary: wallet.publicKey?.toString()!
118+
signer: wallet.publicKey?.toString()!,
119+
treasuryAmount: treasuryAmount,
119120
}, {
120121
headers: {'Content-Type': 'application/json'},
121122
})
@@ -237,7 +238,8 @@ export function useVestingProgramAccount({ account }: { account: PublicKey }) {
237238
total_allocation_amount: total_allocation_amount,
238239
cliff: cliff,
239240
beneficiary: beneficiary,
240-
account: account.toString()
241+
account: account.toString(),
242+
signer: wallet.publicKey?.toString(),
241243
}, {
242244
headers: {'Content-Type': 'application/json'},
243245
})

src/components/vesting/vesting-ui.tsx

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import { ExternalLink } from 'lucide-react'
1313
export function VestingCreate() {
1414
const [newCompany, setNewCompany] = useState('')
1515
const [newMintAddress, setNewMintAddress] = useState('');
16+
const [treasuryAmount, setTreasuryAmount] = useState('');
1617
const {createVestingAccountMutation} = useVestingProgram();
1718

1819
return (
19-
<main className="container relative bg-white w-full py-8 pb-8 px-32 mt--32 rounded-3xl flex justify-center">
20+
<main className="container relative bg-white w-full py-8 pb-8 px-32 rounded-3xl flex justify-center">
2021
<div className="space-y-8 w-full max-w-3xl">
2122
<div className="grid grid-cols-4 items-center gap-4">
2223
<Label htmlFor="company" className="text-right text-base text-black">
@@ -26,30 +27,12 @@ export function VestingCreate() {
2627
id="company"
2728
value={newCompany}
2829
onChange={(e) => setNewCompany(e.target.value)}
29-
className="
30-
col-span-3
31-
rounded-full
32-
h-[3.5rem]
33-
w-full
34-
px-4
35-
py-3
36-
text-base
37-
sm:text-sm
38-
md:text-base
39-
lg:text-lg
40-
xl:text-xl
41-
border
42-
border-gray-300
43-
focus:border-blue-500
44-
focus:ring-2
45-
focus:ring-blue-200
46-
transition-all
47-
duration-300
48-
"
30+
className="col-span-3 rounded-full h-14 w-full px-4 py-3 text-base border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-300"
4931
placeholder="Company Name"
5032
autoComplete="off"
5133
/>
5234
</div>
35+
5336
<div className="grid grid-cols-4 items-center gap-4">
5437
<Label htmlFor="mintAddress" className="text-right text-base text-black">
5538
Token Mint
@@ -58,42 +41,39 @@ export function VestingCreate() {
5841
id="mintAddress"
5942
value={newMintAddress}
6043
onChange={(e) => setNewMintAddress(e.target.value)}
61-
className="
62-
col-span-3
63-
rounded-full
64-
h-[3.5rem]
65-
w-full
66-
px-4
67-
py-3
68-
text-base
69-
sm:text-sm
70-
md:text-base
71-
lg:text-lg
72-
xl:text-xl
73-
border
74-
border-gray-300
75-
focus:border-blue-500
76-
focus:ring-2
77-
focus:ring-blue-200
78-
transition-all
79-
duration-300
80-
"
44+
className="col-span-3 rounded-full h-14 w-full px-4 py-3 text-base border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-300"
8145
placeholder="Mint Address"
8246
autoComplete="off"
8347
/>
8448
</div>
49+
50+
<div className="grid grid-cols-4 items-center gap-4">
51+
<Label htmlFor="treasuryAmount" className="text-right text-base text-black">
52+
Treasury Amount
53+
</Label>
54+
<Input
55+
id="treasuryAmount"
56+
type="number"
57+
value={treasuryAmount}
58+
onChange={(e) => setTreasuryAmount(e.target.value)}
59+
className="col-span-3 rounded-full h-14 w-full px-4 py-3 text-base border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-300"
60+
placeholder="Amount to deposit in treasury"
61+
autoComplete="off"
62+
/>
63+
</div>
64+
8565
<div className="flex justify-center">
8666
<Button
8767
onClick={() =>
8868
createVestingAccountMutation.mutateAsync({
8969
company_name: newCompany,
90-
mint: newMintAddress
70+
mint: newMintAddress,
71+
treasuryAmount: Number(treasuryAmount)
9172
})}
92-
disabled={createVestingAccountMutation.isPending}
73+
disabled={createVestingAccountMutation.isPending || !treasuryAmount}
9374
className="bg-[#39C3EF] hover:bg-[#39C3EF]/90 text-white"
9475
style={{
95-
boxShadow:
96-
"0px -1px 0px 0px #ffffff40 inset, 0px 1px 0px 0px #ffffff40 inset",
76+
boxShadow: "0px -1px 0px 0px #ffffff40 inset, 0px 1px 0px 0px #ffffff40 inset",
9777
}}
9878
>
9979
Create New Company Vesting Account

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface JupToken {
3636
export interface CreateVestingArgs {
3737
company_name: string;
3838
mint: string;
39+
treasuryAmount: number,
3940
}
4041

4142
export interface CreateEmployeeArgs {

0 commit comments

Comments
 (0)