Reputation: 21
I am trying to write a basic smart contract on Solana where anyone can start a crowdfunding campaign. I am working with the front end at the moment. I have managed to check for a Phantom wallet, connect the wallet, and am now stuck at creating the crowdfunding campaign with the TypeError above. This is the full error when I click the :
App.js:84 Error creating campaign account: TypeError: Cannot read properties of undefined (reading 'size')
at new AccountClient (account.ts:121:1)
at account.ts:28:1
at Array.reduce (<anonymous>)
at AccountFactory.build (account.ts:27:1)
at NamespaceFactory.build (index.ts:55:1)
at new Program (index.ts:300:1)
at createCampaign (App.js:64:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
at invokeGuardedCallback (react-dom.development.js:4277:1)
I used Anchor as a basis and kept going from there. The lib.rs code is below:
use anchor_lang::prelude::*;
use anchor_lang::solana_program::entrypoint::ProgramResult;
declare_id!("***Contract address was posted here***");
#[program]
pub mod crowdfunding {
use super::*;
pub fn create(ctx: Context<Create>, name:String, description: String) -> ProgramResult {
let campaign = &mut ctx.accounts.campaign;
campaign.name = name;
campaign.description = description;
campaign.amount_donated = 0;
campaign.admin = *ctx.accounts.user.key;
Ok(())
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> ProgramResult {
let campaign = &mut ctx.accounts.campaign;
let user = &mut ctx.accounts.user;
if campaign.admin != *user.key {
return Err(ProgramError::IncorrectProgramId);
}
let rent_balance = Rent::get()?.minimum_balance(campaign.to_account_info().data_len());
if **campaign.to_account_info().lamports.borrow() - rent_balance < amount {
return Err(ProgramError::InsufficientFunds);
}
**campaign.to_account_info().try_borrow_mut_lamports()? -= amount;
**user.to_account_info().try_borrow_mut_lamports()? += amount;
Ok(())
}
pub fn donate(ctx: Context<Donate>, amount: u64) -> ProgramResult {
let ix = anchor_lang::solana_program::system_instruction::transfer(
&ctx.accounts.user.key(),
&ctx.accounts.campaign.key(),
amount
);
anchor_lang::solana_program::program::invoke(
&ix,
&[
ctx.accounts.user.to_account_info(),
ctx.accounts.campaign.to_account_info()
]
);
(&mut ctx.accounts.campaign).amount_donated += amount;
Ok(())
}
}
#[derive(Accounts)]
pub struct Create<'info> {
#[account(init, payer=user, space=9000, seeds=[b"CAMPAIGN_DEMO".as_ref(), user.key().as_ref()], bump)]
pub campaign: Account<'info, Campaign>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut)]
pub campaign: Account<'info, Campaign>,
#[account(mut)]
pub user: Signer<'info>
}
#[derive(Accounts)]
pub struct Donate<'info> {
#[account(mut)]
pub campaign: Account<'info, Campaign>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>
}
#[account]
pub struct Campaign {
pub admin: Pubkey,
pub name: String,
pub description: String,
pub amount_donated: u64
}
This is the App.js file for the front end:
import './App.css';
import idl from "./idl.json";
import { Connection, PublicKey, clusterApiUrl} from "@solana/web3.js";
import { Program, AnchorProvider, web3, utils, BN} from '@coral-xyz/anchor';
import { useEffect, useState } from "react";
import { Buffer} from "buffer"
window.Buffer = Buffer
const programID = new PublicKey(idl.address);
const network = clusterApiUrl("devnet");
const opts = {
preflightCommitment: "processed",
};
const {SystemProgram} = web3;
const App = () => {
const[walletAddress, setWalletAddress] = useState(null);
const getProvider = () => {
const connection = new Connection(network, opts.preflightCommitment);
const provider = new AnchorProvider(
connection,
window.solana,
opts.preflightCommitment
);
return provider
}
const checkIfWalletIsConnected = async() => {
try {
const {solana} = window;
if (solana) {
if (solana.isPhantom) {
console.log("Phantom wallet found!");
const response = await solana.connect({
onlyIfTrusted: true,
});
console.log(
"Connected with public key:",
response.publicKey.toString()
);
setWalletAddress(response.publicKey.toString());
}
} else {
alert("Solana object not found! Get a Phantom wallet.");
}
} catch(error) {
console.error(error);
}
};
const connectWallet = async () => {
const {solana} = window;
if (solana) {
const response = await solana.connect();
console.log(
'Connected with public key:',
response.publicKey.toString()
);
setWalletAddress(response.publicKey.toString());
}
};
const createCampaign = async() => {
try {
const provider = getProvider()
const program = new Program(idl, programID, provider)
const [campaign] = PublicKey.findProgramAddressSync(
[
utils.bytes.utf8.encode("CAMPAIGN_DEMO"),
provider.wallet.publicKey.toBuffer(),
],
program.programId
);
program.methods.create('campaign name', 'campaign description',{
accounts: {
campaign,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
});
console.log(
'Created a new campaign w/ address:',
campaign.toString()
);
} catch(error) {
console.error("Error creating campaign account:", error)
}
}
const renderNotConnectedContainer = () => (
<button onClick={connectWallet}>Connect to Wallet</button>
);
const renderConnectedContainer = () => (
<button onClick={createCampaign}>Create a campaign...</button>
);
useEffect(() => {
const onLoad = async() => {
await checkIfWalletIsConnected();
};
window.addEventListener('load', onLoad);
return () => window.removeEventListener('load', onLoad);
}, []);
return (
<div className="App">
{!walletAddress && renderNotConnectedContainer()}
{walletAddress && renderConnectedContainer()}
</div>
);
};
export default App;
I tried going through the account.ts files from anchor, but am still not finding a way to define the size. In my mind it has already been defined. What am I missing?
Upvotes: 2
Views: 382
Reputation: 808
I was able to resolve it by modifying the initialization from:
const program = new Program(idl, programID, provider); //you will find this line inside
TO:
const program = new Program(idl, provider);
FOR MORE ANSWERS AS TO WHY: The error occurs because the Program constructor in Anchor expects only two arguments when using the Program class from @project-serum/anchor. The correct function signature is:
new Program(idl: Idl, provider: Provider, programId?: PublicKey)
So, when calling:
const program = new Program(idl, programID, provider);
It mistakenly treats programID as the provider, which leads to the error
Solution: Change the initialization to:
const program = new Program(idl, provider);
Upvotes: 0