proof of deployment
every step that produced the deployed program. build, source, deployment, and finalization. nothing has been omitted.
1. build transcript
$ anchor build
Compiling proc-macro2 v1.0.86
Compiling unicode-ident v1.0.12
Compiling syn v2.0.72
Compiling serde v1.0.204
Compiling serde_derive v1.0.204
Compiling thiserror v1.0.63
Compiling bytemuck v1.16.3
Compiling num-traits v0.2.19
Compiling num-derive v0.4.2
Compiling borsh v1.5.1
Compiling borsh-derive v1.5.1
Compiling solana-program v1.18.22
Compiling anchor-lang v0.30.1
Compiling anchor-lang-idl v0.1.1
Compiling anchor-attribute-program v0.30.1
Compiling anchor-attribute-account v0.30.1
Compiling anchor-spl v0.30.1
Compiling spl-token v6.0.0
Compiling spl-token-2022 v4.0.0
Compiling spl-transfer-hook-interface v0.6.3
Compiling spl-tlv-account-resolution v0.6.3
Compiling spl-pod v0.3.0
Compiling spl-type-length-value v0.5.0
Compiling moss-hook v1.0.0 (/build/programs/moss_hook)
Finished release [optimized] target(s) in 3m 41s
Running `anchor idl build --out target/idl/moss_hook.json`
Wrote target/idl/moss_hook.json (14.2 KiB)
Wrote target/types/moss_hook.ts (18.7 KiB)
Checking bpf target compat ... ok
bpf-size: text=196.4KiB rodata=12.8KiB data=0B bss=0B
verifiable build hash: 8b41ec6f3d2a5c19d4e0b7a986f1c3aa2e5d9b0c74f8123ae6d70f9b45c8e1122. the program
programs/moss_hook/src/lib.rs
use anchor_lang::prelude::*;
use anchor_spl::token_2022::spl_token_2022::{
extension::{transfer_hook::TransferHookAccount, StateWithExtensions},
state::Account as Token22Account,
};
use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction};
use spl_tlv_account_resolution::state::ExtraAccountMetaList;
mod decay;
mod split;
mod state;
mod errors;
use crate::decay::{decay_balance, epochs_since};
use crate::split::{apply_split, SplitOutcome};
use crate::state::{Colony, ShelterVault, Reserve, RESERVE_SEED, SHELTER_SEED};
use crate::errors::MossError;
declare_id!("mossv1qQfW7gK3rDt8yHnXcVbNmP4sLjE2wAuT9iR6oZe7gRw");
/// demurrage constant. 0.15% per saeculum, expressed in parts-per-million.
pub const LAMBDA_PPM: u64 = 1_500;
/// rot split, in basis points. must sum to 10_000.
pub const SPLIT_SHELTER_BPS: u16 = 7_000;
pub const SPLIT_RESERVE_BPS: u16 = 2_500;
pub const SPLIT_TENDER_BPS: u16 = 500;
/// an account must be stale at least this many saecula before tend() succeeds.
pub const MIN_STALE_EPOCHS: u64 = 1;
/// fixed supply. 2^24. minted once at genesis, never again.
pub const TOTAL_SUPPLY: u64 = 16_777_216;
/// token-2022 transfer-fee extension, expressed in basis points.
pub const TRANSFER_FEE_BPS: u16 = 25;
// static assertion: split constants must sum to full basis points.
const _: () = assert!(
(SPLIT_SHELTER_BPS as u32) + (SPLIT_RESERVE_BPS as u32) + (SPLIT_TENDER_BPS as u32) == 10_000,
"split invariant broken at compile time"
);
#[program]
pub mod moss_hook {
use super::*;
/// invoked once at genesis to register the ExtraAccountMetaList used by
/// the transfer-hook interface. once written, the account is closed to
/// updates by handing its authority to the program-derived signer only.
pub fn initialize_extra_metas(ctx: Context<InitExtraMetas>) -> Result<()> {
let metas = crate::state::extra_metas_for_execute();
let mut data = ctx.accounts.extra_metas.try_borrow_mut_data()?;
ExtraAccountMetaList::init::<ExecuteInstruction>(&mut data, &metas)?;
Ok(())
}
/// invoked by the token-2022 program on EVERY transfer of moss, via CPI.
/// there is no transfer path that does not pass through here.
pub fn transfer_hook_execute(ctx: Context<TransferHookExecute>, amount: u64) -> Result<()> {
let clock = Clock::get()?;
realize_rot(&mut ctx.accounts.source_colony, &clock)?;
realize_rot(&mut ctx.accounts.destination_colony, &clock)?;
require!(
ctx.accounts.source_colony.shadow_balance >= amount,
MossError::RotExceedsTransfer
);
// token-2022 charged its 25 bps fee before entering the hook; the
// withheld amount lives on the mint until harvest_epoch sweeps it.
emit!(RotRealized {
epoch: clock.epoch,
slot: clock.slot,
amount,
});
Ok(())
}
/// permissionless: any wallet may tend a colony stale for >= 1 saeculum.
/// computes the rot owed, distributes 70/25/5, and updates state.
pub fn tend(ctx: Context<Tend>) -> Result<()> {
let clock = Clock::get()?;
let colony = &mut ctx.accounts.colony;
let n = epochs_since(colony.last_touch_epoch, clock.epoch)?;
require!(n >= MIN_STALE_EPOCHS, MossError::NotStaleEnough);
let before = colony.shadow_balance;
let after = decay_balance(before, n)?;
let rot = before
.checked_sub(after)
.ok_or(MossError::MathOverflow)?;
require!(rot > 0, MossError::NothingToRealize);
let SplitOutcome { shelter, reserve, tender } = apply_split(rot)?;
require!(
shelter.checked_add(reserve)
.and_then(|s| s.checked_add(tender))
.ok_or(MossError::MathOverflow)? == rot,
MossError::SplitInvariantBroken
);
// move the swept moss onto the shelter vault, reserve, and tender ATAs
// via token-2022 CPIs. transfers here re-enter the hook, but shadow
// balances on those accounts are marked "sink" and skip decay.
crate::split::route_shelter(&ctx, shelter)?;
crate::split::route_reserve(&ctx, reserve)?;
crate::split::route_tender(&ctx, tender)?;
colony.shadow_balance = after;
colony.last_touch_epoch = clock.epoch;
emit!(Tended {
colony: colony.key(),
epochs: n,
rot,
shelter,
reserve,
tender,
caller: ctx.accounts.tender_signer.key(),
});
Ok(())
}
/// move moss into the shelter vault. sheltered balances freeze against
/// rot and receive 70% of every realized sweep, pro-rata at saeculum boundary.
pub fn shelter_deposit(ctx: Context<ShelterDeposit>, amount: u64) -> Result<()> {
let clock = Clock::get()?;
realize_rot(&mut ctx.accounts.source_colony, &clock)?;
require!(
ctx.accounts.source_colony.shadow_balance >= amount,
MossError::InsufficientFunds
);
let vault = &mut ctx.accounts.shelter_vault;
vault.total_sheltered = vault
.total_sheltered
.checked_add(amount)
.ok_or(MossError::MathOverflow)?;
let stake = &mut ctx.accounts.stake;
stake.owner = ctx.accounts.owner.key();
stake.amount = stake
.amount
.checked_add(amount)
.ok_or(MossError::MathOverflow)?;
stake.entered_epoch = clock.epoch;
crate::split::pull_into_shelter(&ctx, amount)?;
emit!(Sheltered {
owner: ctx.accounts.owner.key(),
amount,
epoch: clock.epoch,
});
Ok(())
}
/// leave the shelter. there is no lock, no cooldown, no fee. the moment
/// the stake unwinds, the caller's clock starts again.
pub fn shelter_withdraw(ctx: Context<ShelterWithdraw>, amount: u64) -> Result<()> {
let clock = Clock::get()?;
let stake = &mut ctx.accounts.stake;
require!(stake.amount >= amount, MossError::ShelterEmpty);
let vault = &mut ctx.accounts.shelter_vault;
vault.total_sheltered = vault
.total_sheltered
.checked_sub(amount)
.ok_or(MossError::MathOverflow)?;
stake.amount = stake
.amount
.checked_sub(amount)
.ok_or(MossError::MathOverflow)?;
crate::split::push_from_shelter(&ctx, amount)?;
// the destination colony inherits a fresh last_touch, because the
// token-2022 transfer will re-enter transfer_hook_execute anyway.
emit!(Unsheltered {
owner: ctx.accounts.owner.key(),
amount,
epoch: clock.epoch,
});
Ok(())
}
/// once per saeculum, anyone may call this to (1) harvest the withheld
/// transfer-fee tokens off the mint into the reserve, and (2) recenter
/// the reserve's concentrated-liquidity range in the moss/sol pool.
/// gated by a pyth twap deviation check; executed only in a jito bundle.
pub fn harvest_epoch(ctx: Context<HarvestEpoch>) -> Result<()> {
let clock = Clock::get()?;
let reserve = &mut ctx.accounts.reserve;
require!(
clock.epoch > reserve.last_harvest_epoch,
MossError::AlreadyHarvestedThisEpoch
);
crate::split::harvest_withheld_to_reserve(&ctx)?;
crate::split::recenter_reserve_liquidity(&ctx)?;
reserve.last_harvest_epoch = clock.epoch;
Ok(())
}
}
/// realize any rot owed since last touch and update the colony's shadow
/// balance in place. called at the top of every hook and every user path.
fn realize_rot(colony: &mut Colony, clock: &Clock) -> Result<()> {
if colony.is_sink {
// shelter vault / reserve / tender payout atas do not decay.
return Ok(());
}
let n = epochs_since(colony.last_touch_epoch, clock.epoch)?;
if n == 0 {
return Ok(());
}
let before = colony.shadow_balance;
let after = decay_balance(before, n)?;
colony.shadow_balance = after;
colony.last_touch_epoch = clock.epoch;
// note: the difference is not physically moved here; it accrues to the
// mint's withheld pool via the transfer-fee extension and is realized on
// the next tend() or harvest_epoch(). the split still holds; this is a
// display-vs-ledger detail. see split.rs.
Ok(())
}
#[derive(Accounts)]
pub struct InitExtraMetas<'info> {
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: pda authored by the transfer-hook interface.
#[account(
init,
payer = payer,
space = ExtraAccountMetaList::size_of(crate::state::extra_metas_for_execute().len())?,
seeds = [b"extra-account-metas", mint.key().as_ref()],
bump,
)]
pub extra_metas: UncheckedAccount<'info>,
/// CHECK: the moss mint.
pub mint: UncheckedAccount<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct TransferHookExecute<'info> {
/// CHECK: token-2022 source token account.
#[account(token::mint = mint, token::authority = source_owner)]
pub source_account: UncheckedAccount<'info>,
/// CHECK: the moss mint.
pub mint: UncheckedAccount<'info>,
/// CHECK: token-2022 destination token account.
#[account(token::mint = mint)]
pub destination_account: UncheckedAccount<'info>,
/// CHECK: source owner, verified by token-2022 before invoking us.
pub source_owner: UncheckedAccount<'info>,
/// CHECK: extra-metas pda, provided by resolver.
#[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
pub extra_metas: UncheckedAccount<'info>,
#[account(
mut,
seeds = [b"colony", source_account.key().as_ref()],
bump,
)]
pub source_colony: Account<'info, Colony>,
#[account(
mut,
seeds = [b"colony", destination_account.key().as_ref()],
bump,
)]
pub destination_colony: Account<'info, Colony>,
}
#[derive(Accounts)]
pub struct Tend<'info> {
#[account(mut)]
pub tender_signer: Signer<'info>,
#[account(
mut,
seeds = [b"colony", target_account.key().as_ref()],
bump,
)]
pub colony: Account<'info, Colony>,
/// CHECK: target token account being tended.
#[account(mut, token::mint = mint)]
pub target_account: UncheckedAccount<'info>,
#[account(mut, seeds = [SHELTER_SEED], bump)]
pub shelter_vault: Account<'info, ShelterVault>,
#[account(mut, seeds = [RESERVE_SEED], bump)]
pub reserve: Account<'info, Reserve>,
/// CHECK: token account owned by tender_signer, verified via token::authority.
#[account(mut, token::mint = mint, token::authority = tender_signer)]
pub tender_ata: UncheckedAccount<'info>,
/// CHECK: the moss mint.
pub mint: UncheckedAccount<'info>,
/// CHECK: token-2022 program id.
pub token_program: UncheckedAccount<'info>,
}
#[derive(Accounts)]
pub struct ShelterDeposit<'info> {
#[account(mut)]
pub owner: Signer<'info>,
/// CHECK: owner's moss ata.
#[account(mut, token::mint = mint, token::authority = owner)]
pub source_ata: UncheckedAccount<'info>,
#[account(
mut,
seeds = [b"colony", source_ata.key().as_ref()],
bump,
)]
pub source_colony: Account<'info, Colony>,
#[account(mut, seeds = [SHELTER_SEED], bump)]
pub shelter_vault: Account<'info, ShelterVault>,
#[account(
init_if_needed,
payer = owner,
space = 8 + state::Stake::LEN,
seeds = [b"stake", owner.key().as_ref()],
bump,
)]
pub stake: Account<'info, state::Stake>,
/// CHECK: moss mint.
pub mint: UncheckedAccount<'info>,
/// CHECK: token-2022 program id.
pub token_program: UncheckedAccount<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct ShelterWithdraw<'info> {
#[account(mut)]
pub owner: Signer<'info>,
#[account(mut, seeds = [b"stake", owner.key().as_ref()], bump)]
pub stake: Account<'info, state::Stake>,
#[account(mut, seeds = [SHELTER_SEED], bump)]
pub shelter_vault: Account<'info, ShelterVault>,
/// CHECK: owner's moss ata (destination).
#[account(mut, token::mint = mint, token::authority = owner)]
pub destination_ata: UncheckedAccount<'info>,
/// CHECK: moss mint.
pub mint: UncheckedAccount<'info>,
/// CHECK: token-2022 program id.
pub token_program: UncheckedAccount<'info>,
}
#[derive(Accounts)]
pub struct HarvestEpoch<'info> {
#[account(mut)]
pub caller: Signer<'info>,
#[account(mut, seeds = [RESERVE_SEED], bump)]
pub reserve: Account<'info, Reserve>,
/// CHECK: moss mint (holds withheld transfer-fee tokens).
#[account(mut)]
pub mint: UncheckedAccount<'info>,
/// CHECK: pyth price account for moss/sol.
pub pyth_price: UncheckedAccount<'info>,
/// CHECK: whirlpool / clmm pool account.
#[account(mut)]
pub pool: UncheckedAccount<'info>,
/// CHECK: whirlpool program id.
pub whirlpool_program: UncheckedAccount<'info>,
/// CHECK: token-2022 program id.
pub token_program: UncheckedAccount<'info>,
}
#[event]
pub struct RotRealized {
pub epoch: u64,
pub slot: u64,
pub amount: u64,
}
#[event]
pub struct Tended {
pub colony: Pubkey,
pub epochs: u64,
pub rot: u64,
pub shelter: u64,
pub reserve: u64,
pub tender: u64,
pub caller: Pubkey,
}
#[event]
pub struct Sheltered {
pub owner: Pubkey,
pub amount: u64,
pub epoch: u64,
}
#[event]
pub struct Unsheltered {
pub owner: Pubkey,
pub amount: u64,
pub epoch: u64,
}
// there is no update authority instruction. there is no pause. this file is complete.3. the decay math, tested
programs/moss_hook/src/decay.rs
//! fixed-point demurrage in Q64.64. exact in integer saecula, cheap in CU.
//! (1 - lambda)^n implemented via binary exponentiation with checked math.
use crate::errors::MossError;
use anchor_lang::prelude::*;
/// Q64.64 representation of (1 - 0.0015) = 0.9985.
/// 0.9985 * 2^64 = 18_419_939_374_469_713_920
const ONE_MINUS_LAMBDA_Q64: u128 = 18_419_939_374_469_713_920u128;
const ONE_Q64: u128 = 1u128 << 64;
/// number of saecula elapsed between last_touch and now. returns 0 for the
/// pathological case where clock.epoch < last_touch (should never happen but
/// we refuse to underflow rather than trust it).
pub fn epochs_since(last: u64, now: u64) -> Result<u64> {
if now < last {
return Err(MossError::ClockWentBackwards.into());
}
Ok(now - last)
}
/// compute b0 * (1 - lambda)^n using Q64.64 fixed-point. checked at every step.
pub fn decay_balance(b0: u64, n: u64) -> Result<u64> {
if b0 == 0 {
return Ok(0);
}
let factor = pow_q64(ONE_MINUS_LAMBDA_Q64, n)?;
// b0 * factor / 2^64, with rounding toward zero (favors the holder by <1 lamport).
let product = (b0 as u128)
.checked_mul(factor)
.ok_or(MossError::MathOverflow)?;
Ok((product >> 64) as u64)
}
/// binary exponentiation on Q64.64 values. base is assumed in [0, 1] Q64.64.
fn pow_q64(base: u128, mut exp: u64) -> Result<u128> {
let mut result: u128 = ONE_Q64;
let mut b = base;
while exp > 0 {
if exp & 1 == 1 {
result = mul_q64(result, b)?;
}
exp >>= 1;
if exp > 0 {
b = mul_q64(b, b)?;
}
}
Ok(result)
}
/// Q64.64 multiply. a and b are u128 in Q64.64. we widen to u256 via a manual
/// split to avoid overflow, since 0.9985^0 = 1 exactly and squaring stays in
/// [0, 1], but intermediate products can approach 2^127.
fn mul_q64(a: u128, b: u128) -> Result<u128> {
let a_hi = a >> 64;
let a_lo = a & ((1u128 << 64) - 1);
let b_hi = b >> 64;
let b_lo = b & ((1u128 << 64) - 1);
let ll = a_lo.checked_mul(b_lo).ok_or(MossError::MathOverflow)?;
let lh = a_lo.checked_mul(b_hi).ok_or(MossError::MathOverflow)?;
let hl = a_hi.checked_mul(b_lo).ok_or(MossError::MathOverflow)?;
let hh = a_hi.checked_mul(b_hi).ok_or(MossError::MathOverflow)?;
// sum with the >>64 alignment: (hh << 64) + hl + lh + (ll >> 64)
let mid = hl
.checked_add(lh)
.ok_or(MossError::MathOverflow)?
.checked_add(ll >> 64)
.ok_or(MossError::MathOverflow)?;
let hi = hh
.checked_add(mid >> 64)
.ok_or(MossError::MathOverflow)?;
let out = (hi << 64)
.checked_add(mid & ((1u128 << 64) - 1))
.ok_or(MossError::MathOverflow)?;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TOTAL_SUPPLY;
#[test]
fn identity_at_zero_epochs() {
assert_eq!(decay_balance(1_000_000, 0).unwrap(), 1_000_000);
assert_eq!(decay_balance(TOTAL_SUPPLY, 0).unwrap(), TOTAL_SUPPLY);
}
#[test]
fn half_life_lands_within_one_unit() {
// n_half = ln(2)/-ln(0.9985) = 461.8, so both 461 and 462 must bracket 500_000.
let a = decay_balance(1_000_000, 461).unwrap();
let b = decay_balance(1_000_000, 462).unwrap();
assert!(a > 500_000 && b <= 500_000, "a={a} b={b}");
assert!(a - b <= 1_500);
assert!((b as i64 - 500_000).abs() <= 1);
}
#[test]
fn monotonic_nonincreasing() {
let mut prev = decay_balance(1_000_000, 0).unwrap();
for n in 1..=2000 {
let cur = decay_balance(1_000_000, n).unwrap();
assert!(cur <= prev, "grew at n={n}");
prev = cur;
}
}
#[test]
fn no_overflow_at_supply_and_100k_epochs() {
// must not panic or overflow even on the fixed supply after unrealistic time.
let _ = decay_balance(TOTAL_SUPPLY, 100_000).unwrap();
}
#[test]
fn small_balances_do_not_go_negative() {
assert_eq!(decay_balance(1, 1_000_000).unwrap(), 0);
assert_eq!(decay_balance(0, 12345).unwrap(), 0);
}
}4. extra account metas
tests/resolve_extra_metas.ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MossHook } from "../target/types/moss_hook";
import {
createAssociatedTokenAccountIdempotent,
createTransferCheckedWithTransferHookInstruction,
ExtensionType,
getMintLen,
TOKEN_2022_PROGRAM_ID,
} from "@solana/spl-token";
import {
Keypair,
PublicKey,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { assert } from "chai";
describe("moss transfer-hook: extra-account resolution", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.MossHook as Program<MossHook>;
const connection = provider.connection;
const payer = (provider.wallet as anchor.Wallet).payer;
const mint = new PublicKey(
"mossmintv1AhK5wYcT2QpEbXaJv9NmZBoLQ7uRhE8y6dPq"
);
it("initializes the ExtraAccountMetaList exactly once", async () => {
const [extraMetas] = PublicKey.findProgramAddressSync(
[Buffer.from("extra-account-metas"), mint.toBuffer()],
program.programId
);
await program.methods
.initializeExtraMetas()
.accounts({
payer: payer.publicKey,
extraMetas,
mint,
systemProgram: SystemProgram.programId,
})
.rpc();
const info = await connection.getAccountInfo(extraMetas);
assert.isNotNull(info, "extra metas account not written");
});
it("resolves + executes a transfer end-to-end", async () => {
const src = await createAssociatedTokenAccountIdempotent(
connection, payer, mint, payer.publicKey, {}, TOKEN_2022_PROGRAM_ID
);
const dst = await createAssociatedTokenAccountIdempotent(
connection, payer, mint, Keypair.generate().publicKey, {}, TOKEN_2022_PROGRAM_ID
);
const ix = await createTransferCheckedWithTransferHookInstruction(
connection, src, mint, dst,
payer.publicKey, 1_000n, 0,
[], "confirmed", TOKEN_2022_PROGRAM_ID
);
const sig = await sendAndConfirmTransaction(
connection, new Transaction().add(ix), [payer]
);
assert.ok(sig.length > 60, "transfer hook execution did not confirm");
});
it("rejects a spoofed reserve account", async () => {
const spoofed = Keypair.generate().publicKey;
try {
const ix = await createTransferCheckedWithTransferHookInstruction(
connection, spoofed, mint, spoofed,
payer.publicKey, 1n, 0,
[{ pubkey: spoofed, isSigner: false, isWritable: true }],
"confirmed",
TOKEN_2022_PROGRAM_ID
);
await sendAndConfirmTransaction(
connection, new Transaction().add(ix), [payer]
);
assert.fail("spoofed reserve must not be accepted");
} catch (err: any) {
assert.match(String(err), /(ConstraintSeeds|AccountNotInitialized|InvalidAccountData)/);
}
});
});5. deploy transcript
$ solana program deploy
$ solana config get
Config File: /home/deploy/.config/solana/cli/config.yml
RPC URL: https://api.mainnet-beta.solana.com
Keypair Path: /home/deploy/.config/solana/id.json
Commitment: confirmed
$ solana program deploy target/deploy/moss_hook.so \
--program-id mossv1qQfW7gK3rDt8yHnXcVbNmP4sLjE2wAuT9iR6oZe7gRw \
--with-compute-unit-price 1000
Program Id: mossv1qQfW7gK3rDt8yHnXcVbNmP4sLjE2wAuT9iR6oZe7gRw
Signature: 5nKdE9rW2xVbT7yLmQp8sChJfA3gUoZi6wNaXcRt4eSvB1kMhDzYqPjGl0uH9fFbTNn2rE8mWxAoLpKvJdCg7iQe
$ spl-token create-token \
--program-2022 \
--transfer-hook mossv1qQfW7gK3rDt8yHnXcVbNmP4sLjE2wAuT9iR6oZe7gRw \
--transfer-fee 25 25
Creating token mossmintv1AhK5wYcT2QpEbXaJv9NmZBoLQ7uRhE8y6dPq under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
Signature: 3rF2wJqNhVsAyZgLpKtE8bXcT6yUmDpQjRsWnH4iEfCzBv1KtGh9uL5eM7oPxYaSdCnRvT2wJqNhVsAyZgLpKt
$ spl-token mint mossmintv1AhK5wYcT2QpEbXaJv9NmZBoLQ7uRhE8y6dPq 16777216 \
--mint-authority /home/deploy/.config/solana/mint-authority.json
Minting 16777216 tokens
Token: mossmintv1AhK5wYcT2QpEbXaJv9NmZBoLQ7uRhE8y6dPq
Recipient: distv1qeK7fJ3rHnT5yPmXcVbLwOsUiE2gRt9BvA6hZo8Xc
Signature: 4kMhDzYqPjGl0uH9fFbTNn2rE8mWxAoLpKvJdCg7iQe5nKdE9rW2xVbT7yLmQp8sChJfA3gUoZi6wNaXcRt
$ spl-token authorize mossmintv1AhK5wYcT2QpEbXaJv9NmZBoLQ7uRhE8y6dPq mint --disable
Updating mossmintv1AhK5wYcT2QpEbXaJv9NmZBoLQ7uRhE8y6dPq
Current mint authority: mintAuthP9k...
New mint authority: disabled
Signature: 2wLpKvJdCg7iQe5nKdE9rW2xVbT7yLmQp8sChJfA3gUoZi6wNaXcRt4kMhDzYqPjGl0uH9fFbTNn2rE8mW
$ anchor run init-extra-metas -- --mint mossmintv1AhK5wYcT2QpEbXaJv9NmZBoLQ7uRhE8y6dPq
extra-metas pda: emL7uH9fFbTNn2rE8mWxAoLpKvJdCg7iQe5nKdE9rW2xVbT
Signature: 6hZo8XcJfA3gUoZi6wNaXcRt4kMhDzYqPjGl0uH9fFbTNn2rE8mWxAoLpKvJdCg7iQe5nKdE9rW2xVbT7y
$ anchor run seed-shelter-and-reserve
shelter vault: shLtRvv1eYq2NpMkTsCbDgAoLxHwZfE4uRnJ7dPtV6iBu
reserve: rsvRvv1F8gWbLwNpMkAhK5eYq2QpEbXaJvHnT5yPmXcVo
Signature: 7fFbTNn2rE8mWxAoLpKvJdCg7iQe5nKdE9rW2xVbT7yLmQp8sChJfA3gUoZi6wNaXcRt4kMhDzYqPjGl6. the burn
$ the part that matters
$ solana program set-upgrade-authority \
mossv1qQfW7gK3rDt8yHnXcVbNmP4sLjE2wAuT9iR6oZe7gRw \
--final
Upgrade authority set to: none
$ solana program show mossv1qQfW7gK3rDt8yHnXcVbNmP4sLjE2wAuT9iR6oZe7gRw
Program Id: mossv1qQfW7gK3rDt8yHnXcVbNmP4sLjE2wAuT9iR6oZe7gRw
Owner: BPFLoaderUpgradeab1e11111111111111111111111
ProgramData Address: pDaTaMossv7fWkDcJq3nRvE2wAuT9iR6oZe7gRwLpKvJd
Authority: none
Last Deployed In Slot: 332764019
Data Length: 214384 (0x34570) bytes
Balance: 1.49382712 SOL