Account Constraints
We have seen an example of how to define account constraints in previous sections while we're creating PDAs, e.g. #[account(seeds = [b"auth"], bump)]
. In this section, we will discuss the constraints in more detail.
Anchor provides a way to define constraints on accounts that are passed to the program by using the #[account(..)]
attribute. These constraints are used to ensure that the account passed to the program is the correct account. This is done by checking the account's address and the account's data.
Here are some commonly used constraints if you want to define them in TypeScript:
export default class EscrowProgram {
static PROGRAM_ID = new Pubkey("11111111111111111111111111111111");
make(
escrow: EscrowState,
makerMint: Mint,
auth: UncheckedAccount,
vault: TokenAccount
) {
// `init` constraint: create a new account
vault.derive(["vault", escrow.key], makerMint, auth.key).init();
}
refund(maker: Signer, escrow: EscrowState) {
escrow
.derive(["escrow", maker.key, escrow.seed.toBytes()])
.has([maker]) // `has_one` constraint: check if the data stored inside the `escrow.maker` is the same as the `maker` account
.close(maker); // `close` constraint: close the account after the instruction is executed, transfer the remaining SOL to the `maker` account
}
take(
taker: Signer,
maker: SystemAccount,
takerAta: AssociatedTokenAccount,
makerMint: Mint,
takerMint: Mint,
escrow: EscrowState
) {
takerAta
.derive(makerMint, taker.key) // SPL constraints: check if the `taker` account has the same mint as the `makerMint` account and the authority is the `taker` account
.initIfNeeded(); // `init_if_needed` constraint: initialize the account if it doesn't exist
escrow
.derive(["escrow", maker.key, escrow.seed.toBytes()])
.has([maker, makerMint, takerMint]) // `has_one` constraint: can specify multiple accounts to check
.close(maker);
}
}
export interface EscrowState extends Account {
maker: Pubkey;
makerMint: Pubkey;
takerMint: Pubkey;
amount: u64;
seed: u64;
authBump: u8;
escrowBump: u8;
vaultBump: u8;
}
You can simply define the constraints by chaining the constraints methods after the account you want to check and make sure the .derive()
method is called before the other constraints methods.
Normal Constraints
init
(and space
)
.init()
method is used to create a new account. It is used to create a new account with the given data. Poseidon will automatically calculate the space required for the account based on the how you define the account in the state interface and specify the space in Rust with space
constraint.
initIfNeeded
Exact same functionality as the init constraint but only runs if the account does not exist yet1.
If you're using .initIfNeeded()
method, you should add additional feature flags inside your Cargo.toml
file under your program's directory:
[features]
anchor-lang = { version = "xxx", features = ["init-if-needed"]}
seed
(and bump
)
This is the constraint we use to define PDAs.
The seed
constraint is used to derive a new address from the base address. The bump
value is a number between 0 to 255 that is used to derive a new address.
Use the .derive([seed])
method to derive a new address and use the .getBump()
method to get the bump value for the account.
Use the .deriveWithBump([seed], bump)
method to derive a new address with a bump value if you're creating a PDA with a bump.
The seed
and bump
constraints are required to use together to derive a new address.
close
.close(rentReceiver)
method is used to close the account after the instruction is executed. It will transfer the remaining SOL to the account(rentReceiver
) passed to the method.
has
(or has_one
in Anchor)
.has([])
in TypeScript (or has_one
constraint in Anchor) is used to check if the data stored inside the account is the same as the data passed to the method. Like in the refund
method, we're checking if the maker
account's Pubkey is the same as the one stored inside escrow.maker
.
And has
constraint allows you to check multiple accounts at once. Like in the take
method, you can check if the maker
, makerMint
, and takerMint
accounts are the same as the ones stored inside the escrow
account.
SPL Constraints
mint
and authority
If the account is a TokenAccount
, .derive([seed], mint, authority)
method is used to check if the account has the same mint as the mint
account and the authority is the authority
account.
You can use it with the init
constraint to derive and initialize a new TokenAccount
, like vault
account in the make
method.
vault.derive(["vault", escrow.key], makerMint, auth.key).init();
For accounts of type AssociatedTokenAccount
, .derive(mint, authority)
is used instead.
vault.derive(makerMint, auth.key).initIfNeeded();
Check the Anchor documentation for more information on constraints.