feat: add magic-action-shared-payer and magic-actions-on-delegation examples#79
Conversation
…xamples
magic-action-shared-payer
Extends the magic-actions example to use a protocol-owned global_signer
PDA (seeds: [b"global_signer"]) as the escrow authority instead of the
user's wallet. Uses MagicInstructionBuilder::build() + manual AccountMeta
patch + invoke_signed so the PDA can sign the MagicProgram CPI without
being initialized.
magic-actions-on-delegation
Demonstrates post-delegation actions: an increment instruction is queued
at delegation time using delegate_account_with_actions + PostDelegationActions,
and the ER validator fires it automatically when the account is first
cloned — no separate client transaction required.
Also adds:
- docs/post-delegation-actions.md: specification for PostDelegationActions
encoding (key table, compact AccountMeta, encryption variants)
- README.md: entries for both new examples and a Guides section
|
@crocodiledundalk is attempting to deploy a commit to the MagicBlock Labs Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughAdds a detailed "Post-Delegation Actions" doc and two Anchor example projects—magic-actions-on-delegation and magic-action-shared-payer—each with Anchor/Rust programs, configs, TypeScript tests, and README/docs. The root README is updated to link both examples. ChangesPost-Delegation Actions Documentation and Example
Magic Actions Shared Payer Example
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`:
- Around line 38-42: In update_leaderboard, don't deserialize
ctx.accounts.counter blindly; first validate the account is the expected PDA and
owned by the allowed program(s) before reading its bytes. Derive the expected
PDA (using the same seeds used to create the Counter account), compare it to
ctx.accounts.counter.to_account_info().key, and assert the account owner equals
the expected program id (or system program if appropriate); also check the
account data length/is_initialized flag if present. Only after these checks
succeed, call try_borrow_data and Counter::try_deserialize to mutate
high_score/leaderboard.
In `@magic-action-shared-payer/README.md`:
- Around line 41-42: The README currently instructs users to run "rm -rf
/target/deploy/*.keypair" which uses an absolute path and can delete /target on
a machine; change the instruction to use a repository-relative path instead
(e.g., "target/deploy/*.keypair" or "./target/deploy/*.keypair") in the README
so it targets the repo's target/deploy directory; update the line referencing
the command (the rm -rf /target/deploy/*.keypair entry) to the safer relative
form and consider adding a short safety note or existence check before removal.
In `@magic-action-shared-payer/tests/magic-action-shared-payer.ts`:
- Around line 292-302: Replace the fixed animated sleep helper
(sleepWithAnimation) and any hard-coded sleeps used before
delegate/commit/undelegate assertions with a polling helper that repeatedly
checks the expected on-chain/state condition until it becomes true or a
configurable timeout elapses; implement a function like
waitForCondition(checkFn, timeoutMs, intervalMs) and use it in the tests where
delegate/commit/undelegate state is asserted (replace calls to
sleepWithAnimation and direct setTimeout waits) so each test polls the relevant
getter/contract call (e.g., the delegate status, commit inclusion, or undelegate
balance) at short intervals and fails only after the timeout instead of waiting
a fixed duration.
In `@magic-action-shared-payer/tsconfig.json`:
- Around line 3-8: The tsconfig.json restricts global type resolution to
["mocha","chai"], causing Node types used by migrations/deploy.ts (which uses
module.exports) to be missing; update the "types" array in tsconfig.json to
include "node" (e.g., ["mocha","chai","node"]) so `@types/node` is picked up and
the migration entrypoint type-checks correctly.
In `@magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs`:
- Around line 163-164: The delegation_program account (field delegation_program)
is unconstrained and can be swapped by callers; add an address constraint to
ensure it is the expected MagicBlock delegation program before performing the
PDA-signed CPI. Update the struct field annotation for delegation_program to
include an address check that compares to the MagicBlock delegation program's ID
constant (for example using #[account(address =
<magic_block_program_crate>::ID)] or the appropriate crate/program ID constant),
and keep references in the CPI call sites such as delegate_account_with_actions
unchanged so the inner invocation only ever receives the verified program.
In `@magic-actions-on-delegation/README.md`:
- Around line 38-42: Update the README command that currently uses an absolute
path "/target/deploy/*.keypair" to a project-local path; replace the command
string (rm -rf /target/deploy/*.keypair) with a safe relative version such as rm
-rf target/deploy/*.keypair or rm -f ./target/deploy/*.keypair so it deletes the
local target/deploy keypairs instead of touching the filesystem root.
In `@magic-actions-on-delegation/tests/magic-actions-on-delegation.ts`:
- Around line 193-195: Replace fixed sleep() waits with a polling helper that
repeatedly calls readCounterOnER() or readCounterOnBase() until the expected
value is observed or a real timeout elapses; implement a function like
waitForCounterOnER(expected: number, timeoutMs: number, intervalMs = 500) and
similarly waitForCounterOnBase(...) that measures elapsed time, loops awaiting
the corresponding readCounter... call every interval, returns when value
matches, and throws an error on timeout, then replace all uses of sleep(seconds)
in the tests with calls to the appropriate waitForCounter... helper to avoid
flaky fixed delays.
In `@magic-actions-on-delegation/tsconfig.json`:
- Around line 3-8: The tsconfig currently restricts ambient types to
["mocha","chai"] but migrations/deploy.ts uses CommonJS module.exports, so add
Node ambient types: update the "types" array in
magic-actions-on-delegation/tsconfig.json to include "node" (e.g.
["mocha","chai","node"]) and also add `@types/node` to magic-actions-on-delegation
package.json devDependencies so TypeScript recognizes Node globals and module
typings used by module.exports in migrations/deploy.ts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 001d36c7-81f6-4dd0-9c0f-a3e1f2dd0bb1
⛔ Files ignored due to path filters (4)
magic-action-shared-payer/Cargo.lockis excluded by!**/*.lockmagic-action-shared-payer/yarn.lockis excluded by!**/yarn.lock,!**/*.lockmagic-actions-on-delegation/Cargo.lockis excluded by!**/*.lockmagic-actions-on-delegation/yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (24)
README.mddocs/post-delegation-actions.mdmagic-action-shared-payer/.gitignoremagic-action-shared-payer/.prettierignoremagic-action-shared-payer/Anchor.tomlmagic-action-shared-payer/Cargo.tomlmagic-action-shared-payer/README.mdmagic-action-shared-payer/migrations/deploy.tsmagic-action-shared-payer/package.jsonmagic-action-shared-payer/programs/magic-action-shared-payer/Cargo.tomlmagic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rsmagic-action-shared-payer/tests/magic-action-shared-payer.tsmagic-action-shared-payer/tsconfig.jsonmagic-actions-on-delegation/.gitignoremagic-actions-on-delegation/.prettierignoremagic-actions-on-delegation/Anchor.tomlmagic-actions-on-delegation/Cargo.tomlmagic-actions-on-delegation/README.mdmagic-actions-on-delegation/migrations/deploy.tsmagic-actions-on-delegation/package.jsonmagic-actions-on-delegation/programs/magic-actions-on-delegation/Cargo.tomlmagic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rsmagic-actions-on-delegation/tests/magic-actions-on-delegation.tsmagic-actions-on-delegation/tsconfig.json
| async function sleepWithAnimation(seconds: number): Promise<void> { | ||
| const totalMs = seconds * 1000; | ||
| const interval = 500; | ||
| const iterations = Math.floor(totalMs / interval); | ||
| for (let i = 0; i < iterations; i++) { | ||
| const dots = ".".repeat((i % 3) + 1); | ||
| process.stdout.write(`\rWaiting${dots} `); | ||
| await new Promise(resolve => setTimeout(resolve, interval)); | ||
| } | ||
| process.stdout.write("\r\x1b[K"); | ||
| } |
There was a problem hiding this comment.
Use state polling instead of animated fixed waits.
The delegate/commit/undelegate assertions are gated by hard-coded sleeps, so this suite will intermittently fail whenever cross-layer propagation is slower than expected. Poll the relevant state transitions with a timeout rather than sleeping for 5–10 seconds.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@magic-action-shared-payer/tests/magic-action-shared-payer.ts` around lines
292 - 302, Replace the fixed animated sleep helper (sleepWithAnimation) and any
hard-coded sleeps used before delegate/commit/undelegate assertions with a
polling helper that repeatedly checks the expected on-chain/state condition
until it becomes true or a configurable timeout elapses; implement a function
like waitForCondition(checkFn, timeoutMs, intervalMs) and use it in the tests
where delegate/commit/undelegate state is asserted (replace calls to
sleepWithAnimation and direct setTimeout waits) so each test polls the relevant
getter/contract call (e.g., the delegate status, commit inclusion, or undelegate
balance) at short intervals and fails only after the timeout instead of waiting
a fixed duration.
| function sleep(seconds: number): Promise<void> { | ||
| return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); | ||
| } |
There was a problem hiding this comment.
Replace fixed sleeps with polling-based waits.
These tests assume clone/commit propagation always finishes within 8–10 seconds. That will flake under slower validators or CI load; poll readCounterOnER() / readCounterOnBase() until the expected value or a real timeout instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@magic-actions-on-delegation/tests/magic-actions-on-delegation.ts` around
lines 193 - 195, Replace fixed sleep() waits with a polling helper that
repeatedly calls readCounterOnER() or readCounterOnBase() until the expected
value is observed or a real timeout elapses; implement a function like
waitForCounterOnER(expected: number, timeoutMs: number, intervalMs = 500) and
similarly waitForCounterOnBase(...) that measures elapsed time, loops awaiting
the corresponding readCounter... call every interval, returns when value
matches, and throws an error on timeout, then replace all uses of sleep(seconds)
in the tests with calls to the appropriate waitForCounter... helper to avoid
flaky fixed delays.
- README: fix absolute path /target/deploy -> target/deploy (both examples) - UpdateLeaderboard.counter: add seeds constraint to lock PDA address regardless of owner (varies between delegated/undelegated state) - DelegateCounter.delegation_program: add address constraint using ephemeral_rollups_sdk::id() to prevent caller substitution - tsconfig.json: add "node" to types array so migrations/deploy.ts module.exports typechecks correctly (both examples) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs (1)
38-42:⚠️ Potential issue | 🟠 Major | ⚡ Quick winStill validate the counter owner before deserializing its bytes.
The new seeds constraint fixes the PDA address, but
update_leaderboardstill trusts raw bytes from anUncheckedAccountwithout checking that the owner is one of the expected programs for the delegated/undelegated states. A wrong owner or malformed account at that PDA can still make this action read invalid state or fail unexpectedly.Also applies to: 145-146
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs` around lines 38 - 42, The code in update_leaderboard calls Counter::try_deserialize on an UncheckedAccount (counter) without verifying the account owner; before deserializing, assert that counter.to_account_info().owner matches one of the allowed program IDs for the delegated/undelegated counter states (e.g., compare to the expected program pubkeys/constants) and return an appropriate error if not, then proceed to call Counter::try_deserialize; apply the same owner check before any other Counter::try_deserialize usage (e.g., the other occurrence around lines 145-146) so you never deserialize bytes from an account with an unexpected owner.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`:
- Around line 31-35: The increment function uses unchecked addition on
counter.count which will wrap at u64::MAX; change the logic in pub fn
increment(ctx: Context<Increment>) -> Result<()> to use
counter.count.checked_add(1) and if it returns None return an explicit error
(e.g., a custom OverflowError or ProgramError::Custom) instead of performing the
addition; update any associated error enum (or define a new variant) so callers
can see the overflow failure and keep the msg! log only after a successful
checked add.
- Around line 22-26: The initialize function currently mutates shared PDA
accounts (counter and leaderboard) unguarded, allowing any caller to rerun and
zero them; change the account constraints used where you see init_if_needed for
these PDAs to init so they can only be created once, or alternatively add an
explicit authority/initialized guard in the initialize function that checks a
designated authority signer or an "initialized" flag before writing (and return
an error if already initialized). Apply the same change to the other
initialization block in this file that sets count/high_score to 0 (the other
function/section using the same counter/leaderboard pattern).
In `@magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs`:
- Around line 26-27: The increment function (pub fn increment in lib.rs)
currently does unchecked u64 addition; change it to use checked_add(1) and
return a proper error if None is returned to prevent overflow. Add the suggested
ErrorCode enum with a CounterOverflow variant (#[error_code] pub enum ErrorCode
{ #[msg("counter overflow")] CounterOverflow, }) and update increment to set
ctx.accounts.counter.count =
ctx.accounts.counter.count.checked_add(1).ok_or(ErrorCode::CounterOverflow)?; so
the handler returns an error instead of silently wrapping on overflow.
- Around line 20-21: The initialize handler currently allows reinitialization
and wiping of the global PDA counter by using init_if_needed and then
unconditionally setting ctx.accounts.counter.count = 0; update the account
constraint so the PDA is created only once by replacing init_if_needed with init
on the Counter account (the PDA derived from COUNTER_SEED) and remove or guard
the unconditional reset in the initialize function (Context<Initialize> /
initialize) so the counter is not overwritten on subsequent calls; ensure the
account is created with the same seeds and bump used elsewhere and that access
control prevents repeat creation or modification by unauthorized signers.
---
Duplicate comments:
In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`:
- Around line 38-42: The code in update_leaderboard calls
Counter::try_deserialize on an UncheckedAccount (counter) without verifying the
account owner; before deserializing, assert that counter.to_account_info().owner
matches one of the allowed program IDs for the delegated/undelegated counter
states (e.g., compare to the expected program pubkeys/constants) and return an
appropriate error if not, then proceed to call Counter::try_deserialize; apply
the same owner check before any other Counter::try_deserialize usage (e.g., the
other occurrence around lines 145-146) so you never deserialize bytes from an
account with an unexpected owner.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3b20ec78-f526-4f80-b656-9fe03413bb68
📒 Files selected for processing (6)
magic-action-shared-payer/README.mdmagic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rsmagic-action-shared-payer/tsconfig.jsonmagic-actions-on-delegation/README.mdmagic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rsmagic-actions-on-delegation/tsconfig.json
| pub fn initialize(ctx: Context<Initialize>) -> Result<()> { | ||
| let counter = &mut ctx.accounts.counter; | ||
| counter.count = 0; | ||
| let leaderboard = &mut ctx.accounts.leaderboard; | ||
| leaderboard.high_score = 0; |
There was a problem hiding this comment.
initialize is still a public reset for the shared PDAs.
Because Lines 23-26 always write zeroes, these init_if_needed constraints let any caller rerun initialize after setup and wipe both the counter and leaderboard. If this state is meant to be one-time initialization, make the accounts init instead, or add an explicit authority/initialized guard.
Suggested fix
- #[account(init_if_needed, payer = user, space = 8 + 8, seeds = [COUNTER_SEED], bump)]
+ #[account(init, payer = user, space = 8 + 8, seeds = [COUNTER_SEED], bump)]
pub counter: Account<'info, Counter>,
- #[account(init_if_needed, payer = user, space = 8 + 8, seeds = [LEADERBOARD_SEED], bump)]
+ #[account(init, payer = user, space = 8 + 8, seeds = [LEADERBOARD_SEED], bump)]
pub leaderboard: Account<'info, Leaderboard>,Also applies to: 124-127
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`
around lines 22 - 26, The initialize function currently mutates shared PDA
accounts (counter and leaderboard) unguarded, allowing any caller to rerun and
zero them; change the account constraints used where you see init_if_needed for
these PDAs to init so they can only be created once, or alternatively add an
explicit authority/initialized guard in the initialize function that checks a
designated authority signer or an "initialized" flag before writing (and return
an error if already initialized). Apply the same change to the other
initialization block in this file that sets count/high_score to 0 (the other
function/section using the same counter/leaderboard pattern).
| pub fn increment(ctx: Context<Increment>) -> Result<()> { | ||
| let counter = &mut ctx.accounts.counter; | ||
| counter.count += 1; | ||
| msg!("PDA {} count: {}", counter.key(), counter.count); | ||
| Ok(()) |
There was a problem hiding this comment.
Use checked arithmetic for count.
Line 33 can wrap on-chain once count reaches u64::MAX, which silently resets the counter and can corrupt the observed high score. Please switch this to checked_add and return an explicit error on overflow.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@magic-action-shared-payer/programs/magic-action-shared-payer/src/lib.rs`
around lines 31 - 35, The increment function uses unchecked addition on
counter.count which will wrap at u64::MAX; change the logic in pub fn
increment(ctx: Context<Increment>) -> Result<()> to use
counter.count.checked_add(1) and if it returns None return an explicit error
(e.g., a custom OverflowError or ProgramError::Custom) instead of performing the
addition; update any associated error enum (or define a new variant) so callers
can see the overflow failure and keep the msg! log only after a successful
checked add.
| pub fn initialize(ctx: Context<Initialize>) -> Result<()> { | ||
| ctx.accounts.counter.count = 0; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && wc -l lib.rsRepository: magicblock-labs/magicblock-engine-examples
Length of output: 97
🏁 Script executed:
cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && cat -n lib.rs | head -40Repository: magicblock-labs/magicblock-engine-examples
Length of output: 1825
🏁 Script executed:
cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && cat -n lib.rs | sed -n '100,130p'Repository: magicblock-labs/magicblock-engine-examples
Length of output: 1084
🏁 Script executed:
cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && grep -n "init_if_needed\|init," lib.rsRepository: magicblock-labs/magicblock-engine-examples
Length of output: 114
🏁 Script executed:
cd magic-actions-on-delegation/programs/magic-actions-on-delegation/src && cat -n lib.rs | sed -n '1,100p' | grep -A 15 "pub struct Initialize"Repository: magicblock-labs/magicblock-engine-examples
Length of output: 68
initialize can reset the shared PDA counter after deployment.
init_if_needed allows the handler to execute on an existing account. Since Line 21 unconditionally writes 0 to the counter, any signer can re-call initialize and wipe the counter state. This PDA is global (derived from COUNTER_SEED only), and there is no access control preventing repeated calls. Change init_if_needed to init to prevent reinitialization.
Suggested fix
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
- init_if_needed,
+ init,
payer = user,
space = 8 + 8,
seeds = [COUNTER_SEED],
bump,
)]
pub counter: Account<'info, Counter>,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs`
around lines 20 - 21, The initialize handler currently allows reinitialization
and wiping of the global PDA counter by using init_if_needed and then
unconditionally setting ctx.accounts.counter.count = 0; update the account
constraint so the PDA is created only once by replacing init_if_needed with init
on the Counter account (the PDA derived from COUNTER_SEED) and remove or guard
the unconditional reset in the initialize function (Context<Initialize> /
initialize) so the counter is not overwritten on subsequent calls; ensure the
account is created with the same seeds and bump used elsewhere and that access
control prevents repeat creation or modification by unauthorized signers.
| pub fn increment(ctx: Context<Increment>) -> Result<()> { | ||
| ctx.accounts.counter.count += 1; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "lib.rs" | head -20Repository: magicblock-labs/magicblock-engine-examples
Length of output: 1193
🏁 Script executed:
cat -n magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rsRepository: magicblock-labs/magicblock-engine-examples
Length of output: 7810
Guard the counter increment against overflow.
Line 27 uses unchecked u64 arithmetic. In BPF (release) builds, overflow wraps silently, corrupting state. Use checked_add(1) with proper error handling.
Suggested fix
pub fn increment(ctx: Context<Increment>) -> Result<()> {
- ctx.accounts.counter.count += 1;
+ ctx.accounts.counter.count = ctx
+ .accounts
+ .counter
+ .count
+ .checked_add(1)
+ .ok_or(ErrorCode::CounterOverflow)?;
msg!(
"PDA {} count: {} (signer: {})",
ctx.accounts.counter.key(),
ctx.accounts.counter.count,
ctx.accounts.user.key(),Add an error variant alongside the instruction code:
#[error_code]
pub enum ErrorCode {
#[msg("counter overflow")]
CounterOverflow,
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@magic-actions-on-delegation/programs/magic-actions-on-delegation/src/lib.rs`
around lines 26 - 27, The increment function (pub fn increment in lib.rs)
currently does unchecked u64 addition; change it to use checked_add(1) and
return a proper error if None is returned to prevent overflow. Add the suggested
ErrorCode enum with a CounterOverflow variant (#[error_code] pub enum ErrorCode
{ #[msg("counter overflow")] CounterOverflow, }) and update increment to set
ctx.accounts.counter.count =
ctx.accounts.counter.count.checked_add(1).ok_or(ErrorCode::CounterOverflow)?; so
the handler returns an error instead of silently wrapping on overflow.
Summary
Two new Magic Actions examples that extend the existing
magic-actionsdemo.magic-action-shared-payerShows how to use a protocol-owned PDA (
global_signer, seeds:[b"global_signer"]) as the escrow authority for Magic Action callbacks instead of the individual user's wallet. UsesMagicInstructionBuilder::build()+ manualAccountMetapatching +invoke_signedto authorize the PDA over the MagicProgram CPI.magic-actions-on-delegationDemonstrates post-delegation actions: an
incrementinstruction is bundled into thedelegatecall viadelegate_account_with_actions+PostDelegationActions. The ER validator fires it automatically when the account is first cloned — no separate client transaction needed.Also adds:
docs/post-delegation-actions.md— specification for thePostDelegationActionskey-table encoding (compact AccountMeta, encryption variants, signer constraints)README.mdupdated to list both new examples and link to the new guideTest plan
cd magic-action-shared-payer && yarn && anchor testpassescd magic-actions-on-delegation && yarn && anchor testpassesSummary by CodeRabbit
Documentation
New Features
Chores