typescript - ¿Cómo puedo resolver "TypeError: Cannot read properties of undefined (reading 'size')" mientras uso un ancla?
Estoy intentando escribir un contrato inteligente básico en Solana donde cualquier persona pueda iniciar una campaña de crowdfunding. En este momento estoy trabajando con el front-end. He logrado verificar una billetera Phantom, conectar la billetera, y ahora estoy atascado en la creación de la campaña de crowdfunding con el TypeError mencionado arriba. Este es el error completo cuando hago clic en el :
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)
Usé Anchor como base y seguí desde allí. El código de lib.rs está a continuación:
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
}
Este es el archivo App.js para el 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;
Intenté revisar los archivos account.ts de Anchor, pero todavía no encuentro una forma de definir el tamaño. En mi mente ya ha sido definido. ¿Qué me estoy perdiendo?
Respuesta #1
Pude resolverlo modificando la inicialización de:
const program = new Program(idl, programID, provider); //you will find this line inside
PARA :
const program = new Program(idl, provider);
PARA MÁS RESPUESTAS SOBRE POR QUÉ: El error ocurre porque el constructor del Programa en Anchor espera solo dos argumentos al usar la clase Programa de @project-serum/anchor. La firma correcta de la función es:
new Program(idl: Idl, provider: Provider, programId?: PublicKey)
Entonces, al llamar:
const program = new Program(idl, programID, provider);
Trata erróneamente programID como el proveedor, lo que lleva al error
Solución: Cambie la inicialización a:
const program = new Program(idl, provider);
