├── .gitignore ├── .gitmodules ├── metaplex ├── metaplex.zig └── token_metadata.zig ├── spl ├── spl.zig ├── associated_token.zig └── token.zig ├── SlotHashes.zig ├── bpf.zig ├── clock.zig ├── instruction.zig ├── README.md ├── rent.zig ├── sol.zig ├── allocator.zig ├── account.zig ├── context.zig ├── base58 └── base58.zig ├── public_key.zig └── SystemProgram.zig /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .direnv/ 3 | .envrc 4 | 5 | zig-cache/ 6 | zig-out/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "borsh"] 2 | path = borsh 3 | url = https://github.com/lithdew/borsh-zig.git 4 | [submodule "bincode"] 5 | path = bincode 6 | url = https://github.com/lithdew/bincode-zig.git 7 | -------------------------------------------------------------------------------- /metaplex/metaplex.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol"); 3 | 4 | pub const token_metadata = @import("token_metadata.zig"); 5 | 6 | pub const token_metadata_program_id = sol.PublicKey.comptimeFromBase58("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"); 7 | 8 | test { 9 | std.testing.refAllDecls(@This()); 10 | } 11 | -------------------------------------------------------------------------------- /spl/spl.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol"); 3 | 4 | pub const token = @import("token.zig"); 5 | pub const associated_token = @import("associated_token.zig"); 6 | 7 | pub const token_program_id = sol.PublicKey.comptimeFromBase58("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); 8 | pub const associated_token_program_id = sol.PublicKey.comptimeFromBase58("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); 9 | 10 | test { 11 | std.testing.refAllDecls(@This()); 12 | } 13 | -------------------------------------------------------------------------------- /SlotHashes.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol.zig"); 3 | 4 | const SlotHashes = @This(); 5 | 6 | pub const id = sol.slot_hashes_id; 7 | 8 | /// About 2.5 minutes to get your vote in. 9 | pub const max_entries = 512; 10 | 11 | pub const SlotHash = struct { 12 | slot: u64, 13 | hash: [32]u8, 14 | }; 15 | 16 | ptr: [*]SlotHash, 17 | len: u64, 18 | 19 | pub fn from(data: []const u8) []const SlotHash { 20 | const len = std.mem.readIntSliceLittle(u64, data[0..@sizeOf(u64)]); 21 | return @ptrCast([*]const SlotHash, @alignCast(@alignOf(SlotHash), data.ptr + @sizeOf(u64)))[0..len]; 22 | } 23 | -------------------------------------------------------------------------------- /bpf.zig: -------------------------------------------------------------------------------- 1 | const sol = @import("sol.zig"); 2 | 3 | const bpf = @This(); 4 | 5 | pub const UpgradeableLoaderState = union(enum(u32)) { 6 | pub const ProgramData = struct { 7 | slot: u64, 8 | upgrade_authority_id: ?sol.PublicKey, 9 | }; 10 | 11 | uninitialized: void, 12 | buffer: struct { 13 | authority_id: ?sol.PublicKey, 14 | }, 15 | program: struct { 16 | program_data_id: sol.PublicKey, 17 | }, 18 | program_data: ProgramData, 19 | }; 20 | 21 | pub fn getUpgradeableLoaderProgramDataId(program_id: sol.PublicKey) !sol.PublicKey { 22 | const pda = try sol.PublicKey.findProgramAddress(.{program_id}, sol.bpf_upgradeable_loader_program_id); 23 | return pda.address; 24 | } 25 | -------------------------------------------------------------------------------- /clock.zig: -------------------------------------------------------------------------------- 1 | const sol = @import("sol.zig"); 2 | 3 | pub const Clock = extern struct { 4 | pub const id = sol.clock_id; 5 | 6 | /// The current network/bank slot 7 | slot: u64, 8 | /// The timestamp of the first slot in this Epoch 9 | epoch_start_timestamp: i64, 10 | /// The bank epoch 11 | epoch: u64, 12 | /// The future epoch for which the leader schedule has 13 | /// most recently been calculated 14 | leader_schedule_epoch: u64, 15 | /// Originally computed from genesis creation time and network time 16 | /// in slots (drifty); corrected using validator timestamp oracle as of 17 | /// timestamp_correction and timestamp_bounding features 18 | /// An approximate measure of real-world time, expressed as Unix time 19 | /// (i.e. seconds since the Unix epoch) 20 | unix_timestamp: i64, 21 | 22 | pub fn get() !Clock { 23 | var clock: Clock = undefined; 24 | if (sol.is_bpf_program) { 25 | const Syscall = struct { 26 | extern fn sol_get_clock_sysvar(ptr: *Clock) callconv(.C) u64; 27 | }; 28 | const result = Syscall.sol_get_clock_sysvar(&clock); 29 | if (result != 0) { 30 | sol.print("failed to get clock sysvar: error code {}", .{result}); 31 | return error.Unexpected; 32 | } 33 | } 34 | return clock; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /instruction.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol.zig"); 3 | 4 | pub const Instruction = extern struct { 5 | program_id: *const sol.PublicKey, 6 | accounts: [*]const sol.Account.Param, 7 | accounts_len: usize, 8 | data: [*]const u8, 9 | data_len: usize, 10 | 11 | extern fn sol_invoke_signed_c( 12 | instruction: *const Instruction, 13 | account_infos: ?[*]const sol.Account.Info, 14 | account_infos_len: usize, 15 | signer_seeds: ?[*]const []const []const u8, 16 | signer_seeds_len: usize, 17 | ) callconv(.C) u64; 18 | 19 | pub fn from(params: struct { 20 | program_id: *const sol.PublicKey, 21 | accounts: []const sol.Account.Param, 22 | data: []const u8, 23 | }) Instruction { 24 | return .{ 25 | .program_id = params.program_id, 26 | .accounts = params.accounts.ptr, 27 | .accounts_len = params.accounts.len, 28 | .data = params.data.ptr, 29 | .data_len = params.data.len, 30 | }; 31 | } 32 | 33 | pub fn invoke(self: *const Instruction, accounts: []const sol.Account.Info) !void { 34 | if (sol.is_bpf_program) { 35 | return switch (sol_invoke_signed_c(self, accounts.ptr, accounts.len, null, 0)) { 36 | 0 => {}, 37 | else => error.CrossProgramInvocationFailed, 38 | }; 39 | } 40 | return error.CrossProgramInvocationFailed; 41 | } 42 | 43 | pub fn invokeSigned(self: *const Instruction, accounts: []const sol.Account.Info, signer_seeds: []const []const []const u8) !void { 44 | if (sol.is_bpf_program) { 45 | return switch (sol_invoke_signed_c(self, accounts.ptr, accounts.len, signer_seeds.ptr, signer_seeds.len)) { 46 | 0 => {}, 47 | else => error.CrossProgramInvocationFailed, 48 | }; 49 | } 50 | return error.CrossProgramInvocationFailed; 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solana-zig 2 | 3 | ## Setup 4 | 5 | 1. Add this repository as a submodule to your project: 6 | 7 | ```console 8 | git submodule init 9 | git submodule add https://github.com/lithdew/solana-zig.git sol 10 | git submodule update --init --recursive 11 | ``` 12 | 13 | 2. In build.zig: 14 | 15 | ```zig 16 | const std = @import("std"); 17 | const sol = @import("sol/build.zig"); 18 | 19 | // Assume 'step' is a *std.build.LibExeObjStep, and 'sol/' is the directory in 20 | // which this repository is located within your project. 21 | 22 | const sol_pkgs = sol.Packages("sol/"); 23 | 24 | inline for (@typeInfo(sol_pkgs).Struct.decls) |field| { 25 | step.addPackage(@field(sol_pkgs, field.name)); 26 | } 27 | ``` 28 | 29 | ## Example 30 | 31 | 1. Setup build.zig: 32 | 33 | ```zig 34 | const std = @import("std"); 35 | const sol = @import("sol/build.zig"); 36 | 37 | const sol_pkgs = sol.Packages("sol/"); 38 | 39 | pub fn build(b: *std.build.Builder) !void { 40 | const program = b.addSharedLibrary("helloworld", "main.zig", .unversioned); 41 | inline for (@typeInfo(sol_pkgs).Struct.decls) |field| { 42 | program.addPackage(@field(sol_pkgs, field.name)); 43 | } 44 | program.install(); 45 | 46 | try sol.linkSolanaProgram(b, program); 47 | try sol.generateProgramKeypair(b, program); 48 | } 49 | ``` 50 | 51 | 2. Setup main.zig: 52 | 53 | ```zig 54 | const sol = @import("sol"); 55 | 56 | export fn entrypoint(_: [*]u8) callconv(.C) u64 { 57 | sol.print("Hello world!", .{}); 58 | return 0; 59 | } 60 | ``` 61 | 62 | 3. Build and deploy your program on Solana devnet: 63 | 64 | ```console 65 | $ zig build 66 | Program ID: FHGeakPPYgDWomQT6Embr4mVW5DSoygX6TaxQXdgwDYU 67 | 68 | $ solana airdrop -ud 1 69 | Requesting airdrop of 1 SOL 70 | 71 | Signature: 52rgcLosCjRySoQq5MQLpoKg4JacCdidPNXPWbJhTE1LJR2uzFgp93Q7Dq1hQrcyc6nwrNrieoN54GpyNe8H4j3T 72 | 73 | 882.4039166 SOL 74 | 75 | $ solana program deploy -ud zig-out/lib/libhelloworld.so 76 | Program Id: FHGeakPPYgDWomQT6Embr4mVW5DSoygX6TaxQXdgwDYU 77 | ``` -------------------------------------------------------------------------------- /rent.zig: -------------------------------------------------------------------------------- 1 | const sol = @import("sol.zig"); 2 | 3 | pub const Rent = struct { 4 | pub const id = sol.rent_id; 5 | 6 | /// Default rental rate in lamports/byte-year based on: 7 | /// - 10^9 lamports per SOL 8 | /// - $1 per SOL 9 | /// - $0.01 per megabyte day 10 | /// - $3.65 per megabyte year 11 | pub const default_lamports_per_byte_year: u64 = 1_000_000_000 / 100 * 365 / (1024 * 1024); 12 | 13 | /// Default amount of time (in years) the balance has to include rent for. 14 | pub const default_exemption_threshold: f64 = 2.0; 15 | 16 | /// Default percentage of rent to burn (valid values are 0 to 100). 17 | pub const default_burn_percent: u8 = 50; 18 | 19 | /// Account storage overhead for calculation of base rent. 20 | pub const account_storage_overhead: u64 = 128; 21 | 22 | pub const Data = extern struct { 23 | lamports_per_byte_year: u64 = Rent.default_lamports_per_byte_year, 24 | exemption_threshold: f64 = Rent.default_exemption_threshold, 25 | burn_percent: u8 = Rent.default_burn_percent, 26 | 27 | pub fn getAmountBurned(self: Rent.Data, rent_collected: u64) struct { burned: u64, remaining: u64 } { 28 | const burned = (rent_collected * @as(u64, self.burn_percent)) / 100; 29 | return .{ .burned = burned, .remaining = rent_collected - burned }; 30 | } 31 | 32 | pub fn getAmountDue(self: Rent.Data, balance: u64, data_len: usize, years_elapsed: f64) ?u64 { 33 | if (self.isExempt(balance, data_len)) return null; 34 | const total_data_len: u64 = Rent.account_storage_overhead + data_len; 35 | return @floatToInt(u64, @intToFloat(f64, total_data_len * self.lamports_per_byte_year) * years_elapsed); 36 | } 37 | 38 | pub fn isExempt(self: Rent.Data, balance: u64, data_len: usize) bool { 39 | return balance >= self.getMinimumBalance(data_len); 40 | } 41 | 42 | pub fn getMinimumBalance(self: Rent.Data, data_len: usize) u64 { 43 | const total_data_len: u64 = Rent.account_storage_overhead + data_len; 44 | return @floatToInt(u64, @intToFloat(f64, total_data_len * self.lamports_per_byte_year) * self.exemption_threshold); 45 | } 46 | }; 47 | 48 | pub fn get() !Rent.Data { 49 | var rent: Rent.Data = undefined; 50 | if (sol.is_bpf_program) { 51 | const Syscall = struct { 52 | extern fn sol_get_rent_sysvar(ptr: *Rent.Data) callconv(.C) u64; 53 | }; 54 | const result = Syscall.sol_get_rent_sysvar(&rent); 55 | if (result != 0) { 56 | sol.print("failed to get rent sysvar: error code {}", .{result}); 57 | return error.Unexpected; 58 | } 59 | } 60 | return rent; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /sol.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | 4 | const sol = @This(); 5 | 6 | pub usingnamespace @import("public_key.zig"); 7 | pub usingnamespace @import("account.zig"); 8 | pub usingnamespace @import("instruction.zig"); 9 | pub usingnamespace @import("allocator.zig"); 10 | pub usingnamespace @import("context.zig"); 11 | pub usingnamespace @import("build.zig"); 12 | pub usingnamespace @import("clock.zig"); 13 | pub usingnamespace @import("rent.zig"); 14 | 15 | pub const SystemProgram = @import("SystemProgram.zig"); 16 | pub const SlotHashes = @import("SlotHashes.zig"); 17 | 18 | pub const bpf = @import("bpf.zig"); 19 | 20 | pub const is_bpf_program = !builtin.is_test and 21 | ((builtin.os.tag == .freestanding and 22 | builtin.cpu.arch == .bpfel and 23 | std.Target.bpf.featureSetHas(builtin.cpu.features, .solana)) or 24 | (builtin.cpu.arch == .sbf)); 25 | 26 | pub const native_loader_id = sol.PublicKey.comptimeFromBase58("NativeLoader1111111111111111111111111111111"); 27 | pub const system_program_id = sol.PublicKey.comptimeFromBase58("11111111111111111111111111111111"); 28 | pub const incinerator_id = sol.PublicKey.comptimeFromBase58("1nc1nerator11111111111111111111111111111111"); 29 | 30 | pub const rent_id = sol.PublicKey.comptimeFromBase58("SysvarRent111111111111111111111111111111111"); 31 | pub const clock_id = sol.PublicKey.comptimeFromBase58("SysvarC1ock11111111111111111111111111111111"); 32 | pub const sysvar_id = sol.PublicKey.comptimeFromBase58("Sysvar1111111111111111111111111111111111111"); 33 | pub const slot_hashes_id = sol.PublicKey.comptimeFromBase58("SysvarS1otHashes111111111111111111111111111"); 34 | pub const instructions_id = sol.PublicKey.comptimeFromBase58("Sysvar1nstructions1111111111111111111111111"); 35 | 36 | pub const ed25519_program_id = sol.PublicKey.comptimeFromBase58("Ed25519SigVerify111111111111111111111111111"); 37 | pub const secp256k1_program_id = sol.PublicKey.comptimeFromBase58("KeccakSecp256k11111111111111111111111111111"); 38 | 39 | pub const bpf_loader_deprecated_program_id = sol.PublicKey.comptimeFromBase58("BPFLoader1111111111111111111111111111111111"); 40 | pub const bpf_loader_program_id = sol.PublicKey.comptimeFromBase58("BPFLoader2111111111111111111111111111111111"); 41 | pub const bpf_upgradeable_loader_program_id = sol.PublicKey.comptimeFromBase58("BPFLoaderUpgradeab1e11111111111111111111111"); 42 | 43 | pub const lamports_per_sol = 1_000_000_000; 44 | 45 | pub inline fn log(message: []const u8) void { 46 | if (sol.is_bpf_program) { 47 | const Syscall = struct { 48 | extern fn sol_log_(ptr: [*]const u8, len: u64) callconv(.C) void; 49 | }; 50 | Syscall.sol_log_(message.ptr, message.len); 51 | } else { 52 | std.debug.print("{s}\n", .{message}); 53 | } 54 | } 55 | 56 | pub fn print(comptime format: []const u8, args: anytype) void { 57 | if (!sol.is_bpf_program) { 58 | return std.debug.print(format ++ "\n", args); 59 | } 60 | 61 | if (args.len == 0) { 62 | return log(format); 63 | } 64 | 65 | const message = std.fmt.allocPrint(sol.allocator, format, args) catch return; 66 | defer sol.allocator.free(message); 67 | 68 | return sol.log(message); 69 | } 70 | 71 | test { 72 | std.testing.refAllDecls(@This()); 73 | } 74 | -------------------------------------------------------------------------------- /allocator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const heap_start = @intToPtr([*]u8, 0x300000000); 4 | const heap_length = 32 * 1024; 5 | 6 | pub const allocator: std.mem.Allocator = .{ 7 | .ptr = @ptrCast(*Allocator, @alignCast(@alignOf(Allocator), heap_start)), 8 | .vtable = &.{ 9 | .alloc = Allocator.allocFn, 10 | .resize = Allocator.resizeFn, 11 | .free = Allocator.freeFn, 12 | }, 13 | }; 14 | 15 | const Allocator = struct { 16 | end_index: usize, 17 | 18 | fn isLastAllocation(self: Allocator, buf: []u8) bool { 19 | return buf.ptr + buf.len == heap_start + self.end_index; 20 | } 21 | 22 | fn allocFn( 23 | ctx: *anyopaque, 24 | n: usize, 25 | log2_ptr_align: u8, 26 | return_address: usize, 27 | ) ?[*]u8 { 28 | _ = return_address; 29 | 30 | const self = @ptrCast(*Allocator, @alignCast(@alignOf(Allocator), ctx)); 31 | if (self.end_index == 0) { 32 | self.end_index = comptime std.mem.alignPointerOffset(heap_start, @alignOf(Allocator)).? + @sizeOf(Allocator); 33 | } 34 | 35 | const ptr_align = @as(usize, 1) << @intCast(std.mem.Allocator.Log2Align, log2_ptr_align); 36 | const offset = std.mem.alignPointerOffset(heap_start + self.end_index, ptr_align) orelse { 37 | return null; 38 | }; 39 | 40 | const adjusted_index = self.end_index + offset; 41 | const new_end_index = adjusted_index + n; 42 | 43 | if (new_end_index > heap_length) { 44 | return null; 45 | } 46 | self.end_index = new_end_index; 47 | 48 | return heap_start + adjusted_index; 49 | } 50 | 51 | fn resizeFn( 52 | ctx: *anyopaque, 53 | buf: []u8, 54 | log2_buf_align: u8, 55 | new_size: usize, 56 | return_address: usize, 57 | ) bool { 58 | _ = log2_buf_align; 59 | _ = return_address; 60 | 61 | const self = @ptrCast(*Allocator, @alignCast(@alignOf(Allocator), ctx)); 62 | if (self.end_index == 0) { 63 | self.end_index = comptime std.mem.alignPointerOffset(heap_start, @alignOf(Allocator)).? + @sizeOf(Allocator); 64 | } 65 | 66 | if (!self.isLastAllocation(buf)) { 67 | if (new_size > buf.len) { 68 | return false; 69 | } 70 | return true; 71 | } 72 | 73 | if (new_size <= buf.len) { 74 | const sub = buf.len - new_size; 75 | self.end_index -= sub; 76 | return true; 77 | } 78 | 79 | const add = new_size - buf.len; 80 | if (add + self.end_index > heap_length) { 81 | return false; 82 | } 83 | self.end_index += add; 84 | return true; 85 | } 86 | 87 | fn freeFn( 88 | ctx: *anyopaque, 89 | buf: []u8, 90 | log2_buf_align: u8, 91 | return_address: usize, 92 | ) void { 93 | _ = log2_buf_align; 94 | _ = return_address; 95 | const self = @ptrCast(*Allocator, @alignCast(@alignOf(Allocator), ctx)); 96 | if (self.isLastAllocation(buf)) { 97 | self.end_index -= buf.len; 98 | } 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /account.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol.zig"); 3 | 4 | pub const Account = struct { 5 | /// A Solana account sliced from what is provided as inputs to the BPF virtual machine. 6 | pub const Data = extern struct { 7 | duplicate_index: u8, 8 | is_signer: bool, 9 | is_writable: bool, 10 | is_executable: bool, 11 | _: [4]u8, 12 | id: sol.PublicKey, 13 | owner_id: sol.PublicKey, 14 | lamports: u64, 15 | data_len: usize, 16 | 17 | comptime { 18 | std.debug.assert(@offsetOf(Account.Data, "duplicate_index") == 0); 19 | std.debug.assert(@offsetOf(Account.Data, "is_signer") == 0 + 1); 20 | std.debug.assert(@offsetOf(Account.Data, "is_writable") == 0 + 1 + 1); 21 | std.debug.assert(@offsetOf(Account.Data, "is_executable") == 0 + 1 + 1 + 1); 22 | std.debug.assert(@offsetOf(Account.Data, "_") == 0 + 1 + 1 + 1 + 1); 23 | std.debug.assert(@offsetOf(Account.Data, "id") == 0 + 1 + 1 + 1 + 1 + 4); 24 | std.debug.assert(@offsetOf(Account.Data, "owner_id") == 0 + 1 + 1 + 1 + 1 + 4 + 32); 25 | std.debug.assert(@offsetOf(Account.Data, "lamports") == 0 + 1 + 1 + 1 + 1 + 4 + 32 + 32); 26 | std.debug.assert(@offsetOf(Account.Data, "data_len") == 0 + 1 + 1 + 1 + 1 + 4 + 32 + 32 + 8); 27 | std.debug.assert(@sizeOf(Account.Data) == 1 + 1 + 1 + 1 + 4 + 32 + 32 + 8 + 8); 28 | } 29 | }; 30 | 31 | /// Metadata representing a Solana acconut. 32 | pub const Param = extern struct { 33 | id: *const sol.PublicKey, 34 | is_writable: bool, 35 | is_signer: bool, 36 | }; 37 | 38 | pub const Info = extern struct { 39 | id: *const sol.PublicKey, 40 | lamports: *u64, 41 | data_len: u64, 42 | data: [*]u8, 43 | owner_id: *const sol.PublicKey, 44 | rent_epoch: u64, 45 | is_signer: bool, 46 | is_writable: bool, 47 | is_executable: bool, 48 | }; 49 | 50 | ptr: *Account.Data, 51 | len: usize, 52 | 53 | pub fn id(self: Account) sol.PublicKey { 54 | return self.ptr.id; 55 | } 56 | 57 | pub fn lamports(self: Account) *u64 { 58 | return &self.ptr.lamports; 59 | } 60 | 61 | pub fn ownerId(self: Account) sol.PublicKey { 62 | return self.ptr.owner_id; 63 | } 64 | 65 | pub fn data(self: Account) []u8 { 66 | const data_ptr = @intToPtr([*]u8, @ptrToInt(self.ptr)) + @sizeOf(Account.Data); 67 | return data_ptr[0..self.ptr.data_len]; 68 | } 69 | 70 | pub fn isWritable(self: Account) bool { 71 | return self.ptr.is_writable; 72 | } 73 | 74 | pub fn isExecutable(self: Account) bool { 75 | return self.ptr.is_executable; 76 | } 77 | 78 | pub fn isSigner(self: Account) bool { 79 | return self.ptr.is_signer; 80 | } 81 | 82 | pub fn dataLen(self: Account) usize { 83 | return self.ptr.data_len; 84 | } 85 | 86 | pub fn info(self: Account) Account.Info { 87 | const data_ptr = @intToPtr([*]u8, @ptrToInt(self.ptr)) + @sizeOf(Account.Data); 88 | const rent_epoch = @intToPtr(*u64, @ptrToInt(self.ptr) + self.len - @sizeOf(u64)); 89 | 90 | return .{ 91 | .id = &self.ptr.id, 92 | .lamports = &self.ptr.lamports, 93 | .data_len = self.ptr.data_len, 94 | .data = data_ptr, 95 | .owner_id = &self.ptr.owner_id, 96 | .rent_epoch = rent_epoch.*, 97 | .is_signer = self.ptr.is_signer, 98 | .is_writable = self.ptr.is_writable, 99 | .is_executable = self.ptr.is_executable, 100 | }; 101 | } 102 | }; 103 | -------------------------------------------------------------------------------- /spl/associated_token.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol"); 3 | const bincode = @import("bincode"); 4 | 5 | const spl = @import("spl.zig"); 6 | 7 | const associated_token = @This(); 8 | 9 | pub const Instruction = union(enum(u8)) { 10 | /// Creates an associated token account for the given wallet address and token mint 11 | /// Returns an error if the account exists. 12 | /// 13 | /// 0. `[writeable,signer]` Funding account (must be a system account) 14 | /// 1. `[writeable]` Associated token account address to be created 15 | /// 2. `[]` Wallet address for the new associated token account 16 | /// 3. `[]` The token mint for the new associated token account 17 | /// 4. `[]` System program 18 | /// 5. `[]` SPL Token program 19 | create: void, 20 | /// Creates an associated token account for the given wallet address and token mint, 21 | /// if it doesn't already exist. Returns an error if the account exists, 22 | /// but with a different owner. 23 | /// 24 | /// 0. `[writeable,signer]` Funding account (must be a system account) 25 | /// 1. `[writeable]` Associated token account address to be created 26 | /// 2. `[]` Wallet address for the new associated token account 27 | /// 3. `[]` The token mint for the new associated token account 28 | /// 4. `[]` System program 29 | /// 5. `[]` SPL Token program 30 | create_idempotent: void, 31 | /// Transfers from and closes a nested associated token account: an 32 | /// associated token account owned by an associated token account. 33 | /// 34 | /// The tokens are moved from the nested associated token account to the 35 | /// wallet's associated token account, and the nested account lamports are 36 | /// moved to the wallet. 37 | /// 38 | /// Note: Nested token accounts are an anti-pattern, and almost always 39 | /// created unintentionally, so this instruction should only be used to 40 | /// recover from errors. 41 | /// 42 | /// 0. `[writeable]` Nested associated token account, must be owned by `3` 43 | /// 1. `[]` Token mint for the nested associated token account 44 | /// 2. `[writeable]` Wallet's associated token account 45 | /// 3. `[]` Owner associated token account address, must be owned by `5` 46 | /// 4. `[]` Token mint for the owner associated token account 47 | /// 5. `[writeable, signer]` Wallet address for the owner associated token account 48 | /// 6. `[]` SPL Token program 49 | recover_nested: void, 50 | }; 51 | 52 | pub fn getAccountId(mint_id: sol.PublicKey, user_id: sol.PublicKey) !sol.PublicKey { 53 | const pda = try associated_token.getAccountPDA(mint_id, user_id); 54 | return pda.address; 55 | } 56 | 57 | pub fn getAccountPDA(mint_id: sol.PublicKey, user_id: sol.PublicKey) !sol.ProgramDerivedAddress { 58 | return sol.PublicKey.findProgramAddress(.{ user_id, &spl.token_program_id.bytes, mint_id }, spl.associated_token_program_id); 59 | } 60 | 61 | pub fn createAccount(account: sol.Account.Info, params: struct { 62 | funder: sol.Account.Info, 63 | owner: sol.Account.Info, 64 | mint: sol.Account.Info, 65 | system_program: sol.Account.Info, 66 | token_program: sol.Account.Info, 67 | rent: sol.Account.Info, 68 | seeds: []const []const []const u8 = &.{}, 69 | }) !void { 70 | const data = try bincode.writeAlloc(sol.allocator, associated_token.Instruction.create, .{}); 71 | defer sol.allocator.free(data); 72 | 73 | const instruction = sol.Instruction.from(.{ 74 | .program_id = &spl.associated_token_program_id, 75 | .accounts = &[_]sol.Account.Param{ 76 | .{ .id = params.funder.id, .is_writable = true, .is_signer = true }, 77 | .{ .id = account.id, .is_writable = true, .is_signer = false }, 78 | .{ .id = params.owner.id, .is_writable = false, .is_signer = false }, 79 | .{ .id = params.mint.id, .is_writable = false, .is_signer = false }, 80 | .{ .id = params.system_program.id, .is_writable = false, .is_signer = false }, 81 | .{ .id = params.token_program.id, .is_writable = false, .is_signer = false }, 82 | .{ .id = params.rent.id, .is_writable = false, .is_signer = false }, 83 | }, 84 | .data = data, 85 | }); 86 | 87 | try instruction.invokeSigned(&.{ 88 | params.funder, 89 | account, 90 | params.owner, 91 | params.mint, 92 | params.system_program, 93 | params.token_program, 94 | params.rent, 95 | }, params.seeds); 96 | } 97 | 98 | pub fn createIdempotentAccount(account: sol.Account.Info, params: struct { 99 | funder: sol.Account.Info, 100 | owner: sol.Account.Info, 101 | mint: sol.Account.Info, 102 | system_program: sol.Account.Info, 103 | token_program: sol.Account.Info, 104 | associated_token_program: sol.Account.Info, 105 | seeds: []const []const []const u8 = &.{}, 106 | }) !void { 107 | const data = try bincode.writeAlloc(sol.allocator, associated_token.Instruction.create_idempotent, .{}); 108 | defer sol.allocator.free(data); 109 | 110 | const instruction = sol.Instruction.from(.{ 111 | .program_id = &spl.associated_token_program_id, 112 | .accounts = &[_]sol.Account.Param{ 113 | .{ .id = params.funder.id, .is_writable = true, .is_signer = true }, 114 | .{ .id = account.id, .is_writable = true, .is_signer = false }, 115 | .{ .id = params.owner.id, .is_writable = false, .is_signer = false }, 116 | .{ .id = params.mint.id, .is_writable = false, .is_signer = false }, 117 | .{ .id = params.system_program.id, .is_writable = false, .is_signer = false }, 118 | .{ .id = params.token_program.id, .is_writable = false, .is_signer = false }, 119 | }, 120 | .data = data, 121 | }); 122 | 123 | try instruction.invokeSigned(&.{ 124 | params.funder, 125 | account, 126 | params.owner, 127 | params.mint, 128 | params.system_program, 129 | params.token_program, 130 | params.associated_token_program, 131 | }, params.seeds); 132 | } 133 | -------------------------------------------------------------------------------- /context.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol.zig"); 3 | 4 | pub const Context = struct { 5 | num_accounts: usize, 6 | accounts: [*]u8, 7 | data: []const u8, 8 | program_id: *sol.PublicKey, 9 | 10 | pub fn load(input: [*]u8) !Context { 11 | var ptr: [*]u8 = input; 12 | 13 | const num_accounts = std.mem.bytesToValue(usize, ptr[0..@sizeOf(usize)]); 14 | ptr += @sizeOf(usize); 15 | 16 | const accounts: [*]u8 = ptr; 17 | 18 | var i: usize = 0; 19 | while (i < num_accounts) : (i += 1) { 20 | const account: *align(1) sol.Account.Data = @ptrCast(*align(1) sol.Account.Data, ptr); 21 | if (account.duplicate_index != std.math.maxInt(u8)) { 22 | ptr += @sizeOf(usize); 23 | continue; 24 | } 25 | ptr += @sizeOf(sol.Account.Data); 26 | ptr = @intToPtr([*]u8, std.mem.alignForward(@ptrToInt(ptr + account.data_len + 10 * 1024), @alignOf(usize))); 27 | ptr += @sizeOf(u64); 28 | } 29 | 30 | const data_len = std.math.cast(usize, std.mem.bytesToValue(u64, ptr[0..@sizeOf(u64)])) orelse return error.DataTooLarge; 31 | ptr += @sizeOf(u64); 32 | 33 | const data = ptr[0..data_len]; 34 | ptr += data_len; 35 | 36 | const program_id = @ptrCast(*sol.PublicKey, ptr); 37 | ptr += @sizeOf(sol.PublicKey); 38 | 39 | return Context{ 40 | .num_accounts = num_accounts, 41 | .accounts = accounts, 42 | .data = data, 43 | .program_id = program_id, 44 | }; 45 | } 46 | 47 | pub fn loadAccountsAlloc(self: Context, comptime Accounts: type, gpa: std.mem.Allocator) !*Accounts { 48 | const accounts = try gpa.create(Accounts); 49 | errdefer gpa.destroy(accounts); 50 | 51 | try self.populateAccounts(Accounts, accounts); 52 | 53 | return accounts; 54 | } 55 | 56 | pub fn loadAccounts(self: Context, comptime Accounts: type) !Accounts { 57 | var accounts: Accounts = undefined; 58 | try self.populateAccounts(Accounts, &accounts); 59 | return accounts; 60 | } 61 | 62 | fn populateAccounts(self: Context, comptime Accounts: type, accounts: *Accounts) !void { 63 | comptime var min_accounts = 0; 64 | comptime var last_field_is_slice = false; 65 | 66 | comptime { 67 | inline for (@typeInfo(Accounts).Struct.fields) |field, i| { 68 | switch (field.type) { 69 | sol.Account => min_accounts += 1, 70 | []sol.Account => { 71 | if (i != @typeInfo(Accounts).Struct.fields.len - 1) { 72 | @compileError("Only the last field of an 'Accounts' struct may be a slice of accounts."); 73 | } 74 | last_field_is_slice = true; 75 | }, 76 | else => @compileError(""), 77 | } 78 | } 79 | } 80 | 81 | if (self.num_accounts < min_accounts) { 82 | return error.NotEnoughAccounts; 83 | } 84 | 85 | var ptr: [*]u8 = self.accounts; 86 | 87 | @setEvalBranchQuota(100_000); 88 | 89 | inline for (@typeInfo(Accounts).Struct.fields) |field| { 90 | switch (field.type) { 91 | sol.Account => { 92 | const account: *align(1) sol.Account.Data = @ptrCast(*align(1) sol.Account.Data, ptr); 93 | if (account.duplicate_index != std.math.maxInt(u8)) { 94 | inline for (@typeInfo(Accounts).Struct.fields) |cloned_field, cloned_index| { 95 | if (cloned_field.type == sol.Account) { 96 | if (account.duplicate_index == cloned_index) { 97 | @field(accounts, field.name) = @field(accounts, cloned_field.name); 98 | } 99 | } 100 | } 101 | ptr += @sizeOf(usize); 102 | } else { 103 | const start = @ptrToInt(ptr); 104 | ptr += @sizeOf(sol.Account.Data); 105 | ptr = @intToPtr([*]u8, std.mem.alignForward(@ptrToInt(ptr + account.data_len + 10 * 1024), @alignOf(usize))); 106 | ptr += @sizeOf(u64); 107 | const end = @ptrToInt(ptr); 108 | 109 | @field(accounts, field.name) = .{ .ptr = @alignCast(@alignOf(sol.Account.Data), account), .len = end - start }; 110 | } 111 | }, 112 | []sol.Account => { 113 | const remaining_accounts = try sol.allocator.alloc(sol.Account, self.num_accounts + 1 - @typeInfo(Accounts).Struct.fields.len); 114 | errdefer sol.allocator.free(remaining_accounts); 115 | 116 | for (remaining_accounts) |*remaining_account| { 117 | const account: *align(1) sol.Account.Data = @ptrCast(*align(1) sol.Account.Data, ptr); 118 | if (account.duplicate_index != std.math.maxInt(u8)) { 119 | inline for (@typeInfo(Accounts).Struct.fields) |cloned_field, cloned_index| { 120 | if (cloned_field.type == sol.Account) { 121 | if (account.duplicate_index == cloned_index) { 122 | remaining_account.* = @field(accounts, cloned_field.name); 123 | } 124 | } 125 | } 126 | ptr += @sizeOf(usize); 127 | } else { 128 | const start = @ptrToInt(ptr); 129 | ptr += @sizeOf(sol.Account.Data); 130 | ptr = @intToPtr([*]u8, std.mem.alignForward(@ptrToInt(ptr + account.data_len + 10 * 1024), @alignOf(usize))); 131 | ptr += @sizeOf(u64); 132 | const end = @ptrToInt(ptr); 133 | 134 | remaining_account.* = .{ .ptr = @alignCast(@alignOf(sol.Account.Data), account), .len = end - start }; 135 | } 136 | } 137 | 138 | @field(accounts, field.name) = remaining_accounts; 139 | }, 140 | else => @compileError(""), 141 | } 142 | } 143 | } 144 | }; 145 | -------------------------------------------------------------------------------- /base58/base58.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const base58 = @This(); 4 | 5 | pub const bitcoin = base58.Alphabet.init("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"); 6 | pub const ipfs = base58.Alphabet.init("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"); 7 | pub const flickr = base58.Alphabet.init("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"); 8 | pub const ripple = base58.Alphabet.init("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"); 9 | 10 | pub const Alphabet = struct { 11 | digits_map: [128]u8, 12 | character_map: [58]u8, 13 | 14 | pub fn init(comptime characters: *const [58]u8) base58.Alphabet { 15 | var alphabet: base58.Alphabet = .{ .character_map = characters.*, .digits_map = [_]u8{255} ** 128 }; 16 | 17 | var distinct: usize = 0; 18 | for (alphabet.character_map) |b, i| { 19 | if (alphabet.digits_map[b] == 255) { 20 | distinct += 1; 21 | } 22 | alphabet.digits_map[b] = @as(i8, i); 23 | } 24 | if (distinct != 58) { 25 | @compileError("base58 alphabet does not consist of 58 distinct characters"); 26 | } 27 | return alphabet; 28 | } 29 | 30 | pub fn comptimeEncode(comptime self: base58.Alphabet, comptime decoded: []const u8) [self.comptimeGetEncodedLength(decoded)]u8 { 31 | comptime { 32 | @setEvalBranchQuota(100_000); 33 | var buffer: [self.getEncodedLengthUpperBound(decoded.len)]u8 = undefined; 34 | const encoded = self.encode(&buffer, decoded); 35 | return encoded[0..encoded.len].*; 36 | } 37 | } 38 | 39 | pub fn comptimeDecode(comptime self: base58.Alphabet, comptime encoded: []const u8) [self.comptimeGetDecodedLength(encoded)]u8 { 40 | comptime { 41 | @setEvalBranchQuota(100_000); 42 | var buffer: [self.getDecodedLengthUpperBound(encoded.len)]u8 = undefined; 43 | const decoded = self.decode(&buffer, encoded) catch |err| { 44 | @compileError("failed to decode base58 string: '" ++ @errorName(err) ++ "'"); 45 | }; 46 | return decoded[0..decoded.len].*; 47 | } 48 | } 49 | 50 | pub fn comptimeGetDecodedLength(comptime self: base58.Alphabet, comptime encoded: []const u8) usize { 51 | comptime { 52 | @setEvalBranchQuota(100_000); 53 | 54 | var decoded = std.mem.zeroes([self.getDecodedLengthUpperBound(encoded.len)]u8); 55 | 56 | var len: usize = 0; 57 | for (encoded) |r| { 58 | var val: usize = self.digits_map[r]; 59 | if (val == 255) { 60 | @compileError("failed to compute base58 string length: unknown character '" ++ [_]u8{r} ++ "'"); 61 | } 62 | for (decoded[0..len]) |b, i| { 63 | val += @as(u32, b) * 58; 64 | decoded[i] = @truncate(u8, val); 65 | val >>= 8; 66 | } 67 | while (val > 0) : (val >>= 8) { 68 | decoded[len] = @truncate(u8, val); 69 | len += 1; 70 | } 71 | } 72 | 73 | for (encoded) |r| { 74 | if (r != self.character_map[0]) { 75 | break; 76 | } 77 | len += 1; 78 | } 79 | 80 | return len; 81 | } 82 | } 83 | 84 | pub fn comptimeGetEncodedLength(comptime self: base58.Alphabet, comptime decoded: []const u8) usize { 85 | comptime { 86 | @setEvalBranchQuota(100_000); 87 | 88 | var encoded = std.mem.zeroes([self.getEncodedLengthUpperBound(decoded.len)]u8); 89 | 90 | var len: usize = 0; 91 | for (decoded) |r| { 92 | var val: u32 = r; 93 | for (encoded[0..len]) |b, i| { 94 | val += @as(u32, b) << 8; 95 | encoded[i] = @intCast(u8, val % 58); 96 | val /= 58; 97 | } 98 | while (val > 0) : (val /= 58) { 99 | encoded[len] = @intCast(u8, val % 58); 100 | len += 1; 101 | } 102 | } 103 | 104 | for (decoded) |r| { 105 | if (r != 0) { 106 | break; 107 | } 108 | len += 1; 109 | } 110 | 111 | return len; 112 | } 113 | } 114 | 115 | pub fn encode(comptime self: base58.Alphabet, encoded: []u8, decoded: []const u8) []const u8 { 116 | std.mem.set(u8, encoded, 0); 117 | 118 | var len: usize = 0; 119 | for (decoded) |r| { 120 | var val: u32 = r; 121 | for (encoded[0..len]) |b, i| { 122 | val += @as(u32, b) << 8; 123 | encoded[i] = @intCast(u8, val % 58); 124 | val /= 58; 125 | } 126 | while (val > 0) : (val /= 58) { 127 | encoded[len] = @intCast(u8, val % 58); 128 | len += 1; 129 | } 130 | } 131 | 132 | for (encoded[0..len]) |b, i| { 133 | encoded[i] = self.character_map[b]; 134 | } 135 | 136 | for (decoded) |r| { 137 | if (r != 0) { 138 | break; 139 | } 140 | encoded[len] = self.character_map[0]; 141 | len += 1; 142 | } 143 | 144 | std.mem.reverse(u8, encoded[0..len]); 145 | 146 | return encoded[0..len]; 147 | } 148 | 149 | pub fn decode(comptime self: base58.Alphabet, decoded: []u8, encoded: []const u8) ![]const u8 { 150 | if (encoded.len == 0) { 151 | return error.ZeroLengthString; 152 | } 153 | 154 | std.mem.set(u8, decoded, 0); 155 | 156 | var len: usize = 0; 157 | for (encoded) |r| { 158 | var val: u32 = self.digits_map[r]; 159 | if (val == 255) { 160 | return error.InvalidBase58Digit; 161 | } 162 | for (decoded[0..len]) |b, i| { 163 | val += @as(u32, b) * 58; 164 | decoded[i] = @truncate(u8, val); 165 | val >>= 8; 166 | } 167 | while (val > 0) : (val >>= 8) { 168 | decoded[len] = @truncate(u8, val); 169 | len += 1; 170 | } 171 | } 172 | 173 | for (encoded) |r| { 174 | if (r != self.character_map[0]) { 175 | break; 176 | } 177 | decoded[len] = 0; 178 | len += 1; 179 | } 180 | 181 | std.mem.reverse(u8, decoded[0..len]); 182 | 183 | return decoded[0..len]; 184 | } 185 | 186 | /// The max possible number of outputted bytes per input byte is log_58(256) 187 | /// ~= 1.37. Thus, the max total output size is ceil(decoded_len * 137/100). 188 | /// Rather than worrying about accurately computing ceil(), add one to the 189 | /// upper bound even if it isn't necessary. 190 | pub fn getEncodedLengthUpperBound(comptime self: base58.Alphabet, decoded_len: usize) usize { 191 | _ = self; 192 | return decoded_len * 137 / 100 + 1; 193 | } 194 | 195 | /// A base58 string filled with nothing but the first base58 alphabet 196 | /// character's decoded length is the length of the itself. 197 | pub fn getDecodedLengthUpperBound(comptime self: base58.Alphabet, encoded_len: usize) usize { 198 | _ = self; 199 | return encoded_len; 200 | } 201 | }; 202 | 203 | test "base58: test vectors" { 204 | inline for (.{ 205 | .{ "Hello World!", "2NEpo7TZRRrLZSi2U" }, 206 | .{ "The quick brown fox jumps over the lazy dog.", "USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z" }, 207 | .{ &[_]u8{ 0x00, 0x00, 0x28, 0x7f, 0xb4, 0xcd }, "11233QC4" }, 208 | }) |test_case| { 209 | try std.testing.expectEqualSlices(u8, test_case[1], &base58.bitcoin.comptimeEncode(test_case[0])); 210 | try std.testing.expectEqualSlices(u8, test_case[0], &base58.bitcoin.comptimeDecode(test_case[1])); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /public_key.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const base58 = @import("base58"); 3 | const builtin = @import("builtin"); 4 | 5 | const sol = @import("sol.zig"); 6 | 7 | const mem = std.mem; 8 | const testing = std.testing; 9 | 10 | pub const ProgramDerivedAddress = struct { 11 | address: PublicKey, 12 | bump_seed: [1]u8, 13 | }; 14 | 15 | pub const PublicKey = extern struct { 16 | pub const length: usize = 32; 17 | pub const base58_length: usize = 44; 18 | 19 | pub const max_num_seeds: usize = 16; 20 | pub const max_seed_length: usize = 32; 21 | 22 | bytes: [PublicKey.length]u8, 23 | 24 | pub fn from(bytes: [PublicKey.length]u8) PublicKey { 25 | return .{ .bytes = bytes }; 26 | } 27 | 28 | pub fn comptimeFromBase58(comptime encoded: []const u8) PublicKey { 29 | return PublicKey.from(base58.bitcoin.comptimeDecode(encoded)); 30 | } 31 | 32 | pub fn comptimeCreateProgramAddress(comptime seeds: anytype, comptime program_id: PublicKey) PublicKey { 33 | comptime { 34 | return PublicKey.createProgramAddress(seeds, program_id) catch |err| { 35 | @compileError("Failed to create program address: " ++ @errorName(err)); 36 | }; 37 | } 38 | } 39 | 40 | pub fn comptimeFindProgramAddress(comptime seeds: anytype, comptime program_id: PublicKey) ProgramDerivedAddress { 41 | comptime { 42 | return PublicKey.findProgramAddress(seeds, program_id) catch |err| { 43 | @compileError("Failed to find program address: " ++ @errorName(err)); 44 | }; 45 | } 46 | } 47 | 48 | pub fn equals(self: PublicKey, other: PublicKey) bool { 49 | return mem.eql(u8, &self.bytes, &other.bytes); 50 | } 51 | 52 | pub fn isPointOnCurve(self: PublicKey) bool { 53 | const Y = std.crypto.ecc.Curve25519.Fe.fromBytes(self.bytes); 54 | const Z = std.crypto.ecc.Curve25519.Fe.one; 55 | const YY = Y.sq(); 56 | const u = YY.sub(Z); 57 | const v = YY.mul(std.crypto.ecc.Curve25519.Fe.edwards25519d).add(Z); 58 | if (sqrtRatioM1(u, v) != 1) { 59 | return false; 60 | } 61 | return true; 62 | } 63 | 64 | fn sqrtRatioM1(u: std.crypto.ecc.Curve25519.Fe, v: std.crypto.ecc.Curve25519.Fe) u32 { 65 | const v3 = v.sq().mul(v); // v^3 66 | const x = v3.sq().mul(u).mul(v).pow2523().mul(v3).mul(u); // uv^3(uv^7)^((q-5)/8) 67 | const vxx = x.sq().mul(v); // vx^2 68 | const m_root_check = vxx.sub(u); // vx^2-u 69 | const p_root_check = vxx.add(u); // vx^2+u 70 | const has_m_root = m_root_check.isZero(); 71 | const has_p_root = p_root_check.isZero(); 72 | return @boolToInt(has_m_root) | @boolToInt(has_p_root); 73 | } 74 | 75 | pub fn createProgramAddress(seeds: anytype, program_id: PublicKey) !PublicKey { 76 | if (seeds.len > PublicKey.max_num_seeds) { 77 | return error.MaxSeedLengthExceeded; 78 | } 79 | 80 | comptime var seeds_index = 0; 81 | inline while (seeds_index < seeds.len) : (seeds_index += 1) { 82 | if (@as([]const u8, seeds[seeds_index]).len > PublicKey.max_seed_length) { 83 | return error.MaxSeedLengthExceeded; 84 | } 85 | } 86 | 87 | var address: PublicKey = undefined; 88 | 89 | if (sol.is_bpf_program) { 90 | const Syscall = struct { 91 | extern fn sol_create_program_address( 92 | seeds_ptr: [*]const []const u8, 93 | seeds_len: u64, 94 | program_id_ptr: *const PublicKey, 95 | address_ptr: *PublicKey, 96 | ) callconv(.C) u64; 97 | }; 98 | 99 | var seeds_array: [seeds.len][]const u8 = undefined; 100 | inline for (seeds) |seed, i| seeds_array[i] = seed; 101 | 102 | const result = Syscall.sol_create_program_address( 103 | &seeds_array, 104 | seeds.len, 105 | &program_id, 106 | &address, 107 | ); 108 | if (result != 0) { 109 | sol.print("failed to create program address with seeds {any} and program id {}: error code {}", .{ 110 | seeds, 111 | program_id, 112 | result, 113 | }); 114 | return error.Unexpected; 115 | } 116 | 117 | return address; 118 | } 119 | 120 | @setEvalBranchQuota(100_000_000); 121 | 122 | var hasher = std.crypto.hash.sha2.Sha256.init(.{}); 123 | comptime var i = 0; 124 | inline while (i < seeds.len) : (i += 1) { 125 | hasher.update(seeds[i]); 126 | } 127 | hasher.update(&program_id.bytes); 128 | hasher.update("ProgramDerivedAddress"); 129 | hasher.final(&address.bytes); 130 | 131 | if (address.isPointOnCurve()) { 132 | return error.InvalidSeeds; 133 | } 134 | 135 | return address; 136 | } 137 | 138 | pub fn findProgramAddress(seeds: anytype, program_id: PublicKey) !ProgramDerivedAddress { 139 | var pda: ProgramDerivedAddress = undefined; 140 | 141 | if (comptime sol.is_bpf_program) { 142 | const Syscall = struct { 143 | extern fn sol_try_find_program_address( 144 | seeds_ptr: [*]const []const u8, 145 | seeds_len: u64, 146 | program_id_ptr: *const PublicKey, 147 | address_ptr: *PublicKey, 148 | bump_seed_ptr: *u8, 149 | ) callconv(.C) u64; 150 | }; 151 | 152 | var seeds_array: [seeds.len][]const u8 = undefined; 153 | 154 | comptime var seeds_index = 0; 155 | inline while (seeds_index < seeds.len) : (seeds_index += 1) { 156 | const Seed = @TypeOf(seeds[seeds_index]); 157 | if (comptime std.meta.trait.isZigString(Seed)) { 158 | seeds_array[seeds_index] = seeds[seeds_index]; 159 | } else if (comptime Seed == PublicKey) { 160 | seeds_array[seeds_index] = &seeds[seeds_index].bytes; 161 | } else { 162 | @compileError("Unknown seed type '" ++ @typeName(Seed) ++ "'"); 163 | } 164 | } 165 | 166 | const result = Syscall.sol_try_find_program_address( 167 | &seeds_array, 168 | seeds.len, 169 | &program_id, 170 | &pda.address, 171 | &pda.bump_seed[0], 172 | ); 173 | if (result != 0) { 174 | sol.print("failed to find program address given seeds {any} and program id {}: error code {}", .{ 175 | seeds, 176 | program_id, 177 | result, 178 | }); 179 | return error.Unexpected; 180 | } 181 | 182 | return pda; 183 | } 184 | 185 | var seeds_with_bump: [seeds.len + 1][]const u8 = undefined; 186 | 187 | comptime var seeds_index = 0; 188 | inline while (seeds_index < seeds.len) : (seeds_index += 1) { 189 | const Seed = @TypeOf(seeds[seeds_index]); 190 | if (comptime std.meta.trait.isZigString(Seed)) { 191 | seeds_with_bump[seeds_index] = seeds[seeds_index]; 192 | } else if (comptime Seed == PublicKey) { 193 | seeds_with_bump[seeds_index] = &seeds[seeds_index].bytes; 194 | } else { 195 | @compileError("Unknown seed type '" ++ @typeName(Seed) ++ "'"); 196 | } 197 | } 198 | 199 | pda.bump_seed[0] = 255; 200 | seeds_with_bump[seeds.len] = &pda.bump_seed; 201 | 202 | while (pda.bump_seed[0] >= 0) : (pda.bump_seed[0] -= 1) { 203 | pda = ProgramDerivedAddress{ 204 | .address = PublicKey.createProgramAddress(&seeds_with_bump, program_id) catch { 205 | if (pda.bump_seed[0] == 0) { 206 | return error.NoViableBumpSeed; 207 | } 208 | continue; 209 | }, 210 | .bump_seed = pda.bump_seed, 211 | }; 212 | 213 | break; 214 | } 215 | 216 | return pda; 217 | } 218 | 219 | pub fn jsonStringify(self: PublicKey, options: anytype, writer: anytype) !void { 220 | _ = options; 221 | try writer.print("\"{}\"", .{self}); 222 | } 223 | 224 | pub fn format(self: PublicKey, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 225 | _ = fmt; 226 | _ = options; 227 | var buffer: [base58.bitcoin.getEncodedLengthUpperBound(PublicKey.length)]u8 = undefined; 228 | try writer.print("{s}", .{base58.bitcoin.encode(&buffer, &self.bytes)}); 229 | } 230 | }; 231 | 232 | // TODO(kenta): fix tests 233 | // test "public_key: comptime create program address" { 234 | // const address = PublicKey.comptimeCreateProgramAddress(.{ "hello", &.{255} }, sol.system_program_id); 235 | // try testing.expectFmt("2PjSSVURwJV4o9wz1BDVwwddvcUCuF1NKFpcQBF9emYJ", "{}", .{address}); 236 | // } 237 | 238 | // test "public_key: comptime find program address" { 239 | // const pda = PublicKey.comptimeFindProgramAddress(.{"hello"}, sol.system_program_id); 240 | // try testing.expectFmt("2PjSSVURwJV4o9wz1BDVwwddvcUCuF1NKFpcQBF9emYJ", "{}", .{pda.address}); 241 | // try testing.expectEqual(@as(u8, 255), pda.bump_seed[0]); 242 | // } 243 | -------------------------------------------------------------------------------- /SystemProgram.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const bincode = @import("bincode"); 3 | 4 | const sol = @import("sol.zig"); 5 | 6 | const SystemProgram = @This(); 7 | 8 | pub fn createAccount(account: sol.Account.Info, params: struct { 9 | payer: sol.Account.Info, 10 | lamports: u64, 11 | space: u64, 12 | owner_id: sol.PublicKey, 13 | seeds: []const []const []const u8 = &.{}, 14 | }) !void { 15 | const data = try bincode.writeAlloc(sol.allocator, SystemProgram.Instruction{ 16 | .create_account = .{ 17 | .lamports = params.lamports, 18 | .space = params.space, 19 | .owner_id = params.owner_id, 20 | }, 21 | }, .{}); 22 | defer sol.allocator.free(data); 23 | 24 | const instruction = sol.Instruction.from(.{ 25 | .program_id = &sol.system_program_id, 26 | .accounts = &[_]sol.Account.Param{ 27 | .{ .id = params.payer.id, .is_writable = true, .is_signer = true }, 28 | .{ .id = account.id, .is_writable = true, .is_signer = true }, 29 | }, 30 | .data = data, 31 | }); 32 | 33 | try instruction.invokeSigned(&.{ params.payer, account }, params.seeds); 34 | } 35 | 36 | pub fn transfer(params: struct { 37 | from: sol.Account.Info, 38 | to: sol.Account.Info, 39 | lamports: u64, 40 | seeds: []const []const []const u8 = &.{}, 41 | }) !void { 42 | const data = try bincode.writeAlloc(sol.allocator, SystemProgram.Instruction{ 43 | .transfer = .{ .lamports = params.lamports }, 44 | }, .{}); 45 | defer sol.allocator.free(data); 46 | 47 | const instruction = sol.Instruction.from(.{ 48 | .program_id = &sol.system_program_id, 49 | .accounts = &[_]sol.Account.Param{ 50 | .{ .id = params.from.id, .is_writable = true, .is_signer = true }, 51 | .{ .id = params.to.id, .is_writable = true, .is_signer = false }, 52 | }, 53 | .data = data, 54 | }); 55 | 56 | try instruction.invokeSigned(&.{ params.from, params.to }, params.seeds); 57 | } 58 | 59 | pub fn allocate(account: sol.Account.Info, space: u64, params: struct { 60 | seeds: []const []const []const u8 = &.{}, 61 | }) !void { 62 | const data = try bincode.writeAlloc(sol.allocator, SystemProgram.Instruction{ 63 | .allocate = .{ .space = space }, 64 | }, .{}); 65 | defer sol.allocator.free(data); 66 | 67 | const instruction = sol.Instruction.from(.{ 68 | .program_id = &sol.system_program_id, 69 | .accounts = &[_]sol.Account.Param{ 70 | .{ .id = account.id, .is_writable = true, .is_signer = true }, 71 | }, 72 | .data = data, 73 | }); 74 | 75 | try instruction.invokeSigned(&.{account}, params.seeds); 76 | } 77 | 78 | pub fn assign(account: sol.Account.Info, owner_id: sol.PublicKey, params: struct { 79 | seeds: []const []const []const u8 = &.{}, 80 | }) !void { 81 | const data = try bincode.writeAlloc(sol.allocator, SystemProgram.Instruction{ 82 | .assign = .{ .owner_id = owner_id }, 83 | }, .{}); 84 | defer sol.allocator.free(data); 85 | 86 | const instruction = sol.Instruction.from(.{ 87 | .program_id = &sol.system_program_id, 88 | .accounts = &[_]sol.Account.Param{ 89 | .{ .id = account.id, .is_writable = true, .is_signer = true }, 90 | }, 91 | .data = data, 92 | }); 93 | 94 | try instruction.invokeSigned(&.{account}, params.seeds); 95 | } 96 | 97 | pub const Instruction = union(enum(u32)) { 98 | /// Create a new account 99 | /// 100 | /// # Account references 101 | /// 0. `[WRITE, SIGNER]` Funding account 102 | /// 1. `[WRITE, SIGNER]` New account 103 | create_account: struct { 104 | /// Number of lamports to transfer to the new account 105 | lamports: u64, 106 | /// Number of bytes of memory to allocate 107 | space: u64, 108 | /// Address of program that will own the new account 109 | owner_id: sol.PublicKey, 110 | }, 111 | /// Assign account to a program 112 | /// 113 | /// # Account references 114 | /// 0. `[WRITE, SIGNER]` Assigned account public key 115 | assign: struct { 116 | /// Owner program account 117 | owner_id: sol.PublicKey, 118 | }, 119 | /// Transfer lamports 120 | /// 121 | /// # Account references 122 | /// 0. `[WRITE, SIGNER]` Funding account 123 | /// 1. `[WRITE]` Recipient account 124 | transfer: struct { 125 | lamports: u64, 126 | }, 127 | /// Create a new account at an address derived from a base public key and a seed 128 | /// 129 | /// # Account references 130 | /// 0. `[WRITE, SIGNER]` Funding account 131 | /// 1. `[WRITE]` Created account 132 | /// 2. `[SIGNER]` (optional) Base account; the account matching the base sol.PublicKey below must be 133 | /// provided as a signer, but may be the same as the funding account 134 | /// and provided as account 0 135 | create_account_with_seed: struct { 136 | /// Base public key 137 | base: sol.PublicKey, 138 | /// String of ASCII chars, no longer than `sol.PublicKey.max_seed_length` 139 | seed: []const u8, 140 | /// Number of lamports to transfer to the new account 141 | lamports: u64, 142 | /// Number of bytes of memory to allocate 143 | space: u64, 144 | /// Owner program account address 145 | owner_id: sol.PublicKey, 146 | }, 147 | /// Consumes a stored nonce, replacing it with a successor 148 | /// 149 | /// # Account references 150 | /// 0. `[WRITE]` Nonce account 151 | /// 1. `[]` RecentBlockhashes sysvar 152 | /// 2. `[SIGNER]` Nonce authority 153 | advance_nonce_account: void, 154 | /// Withdraw funds from a nonce account 155 | /// 156 | /// # Account references 157 | /// 0. `[WRITE]` Nonce account 158 | /// 1. `[WRITE]` Recipient account 159 | /// 2. `[]` RecentBlockhashes sysvar 160 | /// 3. `[]` Rent sysvar 161 | /// 4. `[SIGNER]` Nonce authority 162 | /// 163 | /// The `u64` parameter is the lamports to withdraw, which must leave the 164 | /// account balance above the rent exempt reserve or at zero. 165 | withdraw_nonce_account: u64, 166 | /// Drive state of Uninitialized nonce account to Initialized, setting the nonce value 167 | /// 168 | /// # Account references 169 | /// 0. `[WRITE]` Nonce account 170 | /// 1. `[]` RecentBlockhashes sysvar 171 | /// 2. `[]` Rent sysvar 172 | /// 173 | /// The `sol.PublicKey` parameter specifies the entity authorized to execute nonce 174 | /// instruction on the account 175 | /// 176 | /// No signatures are required to execute this instruction, enabling derived 177 | /// nonce account addresses 178 | initialize_nonce_account: sol.PublicKey, 179 | /// Change the entity authorized to execute nonce instructions on the account 180 | /// 181 | /// # Account references 182 | /// 0. `[WRITE]` Nonce account 183 | /// 1. `[SIGNER]` Nonce authority 184 | /// 185 | /// The `sol.PublicKey` parameter identifies the entity to authorize 186 | authorize_nonce_account: sol.PublicKey, 187 | /// Allocate space in a (possibly new) account without funding 188 | /// 189 | /// # Account references 190 | /// 0. `[WRITE, SIGNER]` New account 191 | allocate: struct { 192 | /// Number of bytes of memory to allocate 193 | space: u64, 194 | }, 195 | /// Allocate space for and assign an account at an address 196 | /// derived from a base public key and a seed 197 | /// 198 | /// # Account references 199 | /// 0. `[WRITE]` Allocated account 200 | /// 1. `[SIGNER]` Base account 201 | allocate_with_seed: struct { 202 | /// Base public key 203 | base: sol.PublicKey, 204 | /// String of ASCII chars, no longer than `sol.PublicKey.max_seed_len` 205 | seed: []const u8, 206 | /// Number of bytes of memory to allocate 207 | space: u64, 208 | /// Owner program account 209 | owner_id: sol.PublicKey, 210 | }, 211 | /// Assign account to a program based on a seed 212 | /// 213 | /// # Account references 214 | /// 0. `[WRITE]` Assigned account 215 | /// 1. `[SIGNER]` Base account 216 | assign_with_seed: struct { 217 | /// Base public key 218 | base: sol.PublicKey, 219 | /// String of ASCII chars, no longer than `sol.PublicKey.max_Seed_len` 220 | seed: []const u8, 221 | /// Owner program account 222 | owner_id: sol.PublicKey, 223 | }, 224 | /// Transfer lamports from a derived address 225 | /// 226 | /// # Account references 227 | /// 0. `[WRITE]` Funding account 228 | /// 1. `[SIGNER]` Base for funding account 229 | /// 2. `[WRITE]` Recipient account 230 | transfer_with_seed: struct { 231 | /// Amount to transfer 232 | lamports: u64, 233 | /// Seed to use to derive the funding accout address 234 | from_seed: []const u8, 235 | /// Owner to use to derive the funding account address 236 | from_owner: sol.PublicKey, 237 | }, 238 | }; 239 | 240 | test "SystemProgram.Instruction: serialize and deserialize" { 241 | var buffer = std.ArrayList(u8).init(std.testing.allocator); 242 | defer buffer.deinit(); 243 | 244 | inline for (.{ .{}, bincode.Params.legacy, bincode.Params.standard }) |params| { 245 | inline for (.{ 246 | SystemProgram.Instruction{ 247 | .create_account = .{ 248 | .lamports = 1586880, 249 | .space = 100, 250 | .owner_id = sol.system_program_id, 251 | }, 252 | }, 253 | }) |payload| { 254 | try bincode.write(buffer.writer(), payload, params); 255 | var stream = std.io.fixedBufferStream(buffer.items); 256 | try std.testing.expectEqual(payload, try bincode.read(std.testing.allocator, @TypeOf(payload), stream.reader(), params)); 257 | buffer.clearRetainingCapacity(); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /spl/token.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol"); 3 | const bincode = @import("bincode"); 4 | 5 | const spl = @import("spl.zig"); 6 | 7 | const token = @This(); 8 | 9 | pub const Error = error{ 10 | NotRentExempt, 11 | InsufficientFunds, 12 | InvalidMint, 13 | MintMismatch, 14 | OwnerMismatch, 15 | FixedSupply, 16 | AlreadyInUse, 17 | InvalidNumberOfProvidedSigners, 18 | InvalidNumberOfRequiredSigners, 19 | UninitializedState, 20 | NativeNotSupported, 21 | NonNativeHasBalance, 22 | InvalidInstruction, 23 | InvalidState, 24 | Overflow, 25 | AuthorityTypeNotSupported, 26 | MintCannotFreeze, 27 | AccountFrozen, 28 | MintDecimalsMismatch, 29 | NonNativeNotSupported, 30 | }; 31 | 32 | pub fn getErrorFromCode(code: u32) (Error || error{Unknown})!void { 33 | inline for (@typeInfo(Error).ErrorSet.?) |err, i| { 34 | if (i == code) { 35 | return @field(token.Error, err.name); 36 | } 37 | } 38 | return error.Unknown; 39 | } 40 | 41 | pub const AuthorityType = enum(u8) { 42 | /// Authority to mint new tokens 43 | mint_tokens, 44 | /// Authority to freeze any account associated with the Mint 45 | freeze_account, 46 | /// Owner of a given token account 47 | account_owner, 48 | /// Authority to close a token account 49 | close_account, 50 | }; 51 | 52 | pub const Instruction = union(enum(u8)) { 53 | /// Initializes a new mint and optionally deposits all the newly minted 54 | /// tokens in an account. 55 | /// 56 | /// The `InitializeMint` instruction requires no signers and MUST be 57 | /// included within the same Transaction as the system program's 58 | /// `CreateAccount` instruction that creates the account being initialized. 59 | /// Otherwise another party can acquire ownership of the uninitialized 60 | /// account. 61 | /// 62 | /// Accounts expected by this instruction: 63 | /// 64 | /// 0. `[writable]` The mint to initialize. 65 | /// 1. `[]` Rent sysvar 66 | /// 67 | initialize_mint: struct { 68 | /// Number of base 10 digits to the right of the decimal place. 69 | decimals: u8, 70 | /// The authority/multisignature to mint tokens. 71 | mint_authority_id: sol.PublicKey, 72 | /// The freeze authority/multisignature of the mint. 73 | freeze_authority_id: ?sol.PublicKey, 74 | }, 75 | /// Initializes a new account to hold tokens. If this account is associated 76 | /// with the native mint then the token balance of the initialized account 77 | /// will be equal to the amount of SOL in the account. If this account is 78 | /// associated with another mint, that mint must be initialized before this 79 | /// command can succeed. 80 | /// 81 | /// The `InitializeAccount` instruction requires no signers and MUST be 82 | /// included within the same Transaction as the system program's 83 | /// `CreateAccount` instruction that creates the account being initialized. 84 | /// Otherwise another party can acquire ownership of the uninitialized 85 | /// account. 86 | /// 87 | /// Accounts expected by this instruction: 88 | /// 89 | /// 0. `[writable]` The account to initialize. 90 | /// 1. `[]` The mint this account will be associated with. 91 | /// 2. `[]` The new account's owner/multisignature. 92 | /// 3. `[]` Rent sysvar 93 | initialize_account: void, 94 | /// Initializes a multisignature account with N provided signers. 95 | /// 96 | /// Multisignature accounts can used in place of any single owner/delegate 97 | /// accounts in any token instruction that require an owner/delegate to be 98 | /// present. The variant field represents the number of signers (M) 99 | /// required to validate this multisignature account. 100 | /// 101 | /// The `InitializeMultisig` instruction requires no signers and MUST be 102 | /// included within the same Transaction as the system program's 103 | /// `CreateAccount` instruction that creates the account being initialized. 104 | /// Otherwise another party can acquire ownership of the uninitialized 105 | /// account. 106 | /// 107 | /// Accounts expected by this instruction: 108 | /// 109 | /// 0. `[writable]` The multisignature account to initialize. 110 | /// 1. `[]` Rent sysvar 111 | /// 2. ..2+N. `[]` The signer accounts, must equal to N where 1 <= N <= 112 | /// 11. 113 | initialize_multisig: struct { 114 | /// The number of signers (M) required to validate this multisignature 115 | /// account. 116 | m: u8, 117 | }, 118 | /// Transfers tokens from one account to another either directly or via a 119 | /// delegate. If this account is associated with the native mint then equal 120 | /// amounts of SOL and Tokens will be transferred to the destination 121 | /// account. 122 | /// 123 | /// Accounts expected by this instruction: 124 | /// 125 | /// * Single owner/delegate 126 | /// 0. `[writable]` The source account. 127 | /// 1. `[writable]` The destination account. 128 | /// 2. `[signer]` The source account's owner/delegate. 129 | /// 130 | /// * Multisignature owner/delegate 131 | /// 0. `[writable]` The source account. 132 | /// 1. `[writable]` The destination account. 133 | /// 2. `[]` The source account's multisignature owner/delegate. 134 | /// 3. ..3+M `[signer]` M signer accounts. 135 | transfer: struct { 136 | /// The amount of tokens to transfer. 137 | amount: u64, 138 | }, 139 | /// Approves a delegate. A delegate is given the authority over tokens on 140 | /// behalf of the source account's owner. 141 | /// 142 | /// Accounts expected by this instruction: 143 | /// 144 | /// * Single owner 145 | /// 0. `[writable]` The source account. 146 | /// 1. `[]` The delegate. 147 | /// 2. `[signer]` The source account owner. 148 | /// 149 | /// * Multisignature owner 150 | /// 0. `[writable]` The source account. 151 | /// 1. `[]` The delegate. 152 | /// 2. `[]` The source account's multisignature owner. 153 | /// 3. ..3+M `[signer]` M signer accounts 154 | approve: struct { 155 | /// The amount of tokens the delegate is approved for. 156 | amount: u64, 157 | }, 158 | /// Revokes the delegate's authority. 159 | /// 160 | /// Accounts expected by this instruction: 161 | /// 162 | /// * Single owner 163 | /// 0. `[writable]` The source account. 164 | /// 1. `[signer]` The source account owner. 165 | /// 166 | /// * Multisignature owner 167 | /// 0. `[writable]` The source account. 168 | /// 1. `[]` The source account's multisignature owner. 169 | /// 2. ..2+M `[signer]` M signer accounts 170 | revoke: void, 171 | /// Sets a new authority of a mint or account. 172 | /// 173 | /// Accounts expected by this instruction: 174 | /// 175 | /// * Single authority 176 | /// 0. `[writable]` The mint or account to change the authority of. 177 | /// 1. `[signer]` The current authority of the mint or account. 178 | /// 179 | /// * Multisignature authority 180 | /// 0. `[writable]` The mint or account to change the authority of. 181 | /// 1. `[]` The mint's or account's current multisignature authority. 182 | /// 2. ..2+M `[signer]` M signer accounts 183 | set_authority: struct { 184 | /// The type of authority to update. 185 | authority_type: AuthorityType, 186 | /// The new authority. 187 | new_authority_id: ?sol.PublicKey, 188 | }, 189 | /// Mints new tokens to an account. The native mint does not support 190 | /// minting. 191 | /// 192 | /// Accounts expected by this instruction: 193 | /// 194 | /// * Single authority 195 | /// 0. `[writable]` The mint. 196 | /// 1. `[writable]` The account to mint tokens to. 197 | /// 2. `[signer]` The mint's minting authority. 198 | /// 199 | /// * Multisignature authority 200 | /// 0. `[writable]` The mint. 201 | /// 1. `[writable]` The account to mint tokens to. 202 | /// 2. `[]` The mint's multisignature mint-tokens authority. 203 | /// 3. ..3+M `[signer]` M signer accounts. 204 | mint_to: struct { 205 | /// The amount of new tokens to mint. 206 | amount: u64, 207 | }, 208 | /// Burns tokens by removing them from an account. `Burn` does not support 209 | /// accounts associated with the native mint, use `CloseAccount` instead. 210 | /// 211 | /// Accounts expected by this instruction: 212 | /// 213 | /// * Single owner/delegate 214 | /// 0. `[writable]` The account to burn from. 215 | /// 1. `[writable]` The token mint. 216 | /// 2. `[signer]` The account's owner/delegate. 217 | /// 218 | /// * Multisignature owner/delegate 219 | /// 0. `[writable]` The account to burn from. 220 | /// 1. `[writable]` The token mint. 221 | /// 2. `[]` The account's multisignature owner/delegate. 222 | /// 3. ..3+M `[signer]` M signer accounts. 223 | burn: struct { 224 | /// The amount of tokens to burn. 225 | amount: u64, 226 | }, 227 | /// Close an account by transferring all its SOL to the destination account. 228 | /// Non-native accounts may only be closed if its token amount is zero. 229 | /// 230 | /// Accounts expected by this instruction: 231 | /// 232 | /// * Single owner 233 | /// 0. `[writable]` The account to close. 234 | /// 1. `[writable]` The destination account. 235 | /// 2. `[signer]` The account's owner. 236 | /// 237 | /// * Multisignature owner 238 | /// 0. `[writable]` The account to close. 239 | /// 1. `[writable]` The destination account. 240 | /// 2. `[]` The account's multisignature owner. 241 | /// 3. ..3+M `[signer]` M signer accounts. 242 | close_account: void, 243 | /// Freeze an Initialized account using the Mint's freeze_authority (if 244 | /// set). 245 | /// 246 | /// Accounts expected by this instruction: 247 | /// 248 | /// * Single owner 249 | /// 0. `[writable]` The account to freeze. 250 | /// 1. `[]` The token mint. 251 | /// 2. `[signer]` The mint freeze authority. 252 | /// 253 | /// * Multisignature owner 254 | /// 0. `[writable]` The account to freeze. 255 | /// 1. `[]` The token mint. 256 | /// 2. `[]` The mint's multisignature freeze authority. 257 | /// 3. ..3+M `[signer]` M signer accounts. 258 | freeze_account: void, 259 | /// Thaw a Frozen account using the Mint's freeze_authority (if set). 260 | /// 261 | /// Accounts expected by this instruction: 262 | /// 263 | /// * Single owner 264 | /// 0. `[writable]` The account to freeze. 265 | /// 1. `[]` The token mint. 266 | /// 2. `[signer]` The mint freeze authority. 267 | /// 268 | /// * Multisignature owner 269 | /// 0. `[writable]` The account to freeze. 270 | /// 1. `[]` The token mint. 271 | /// 2. `[]` The mint's multisignature freeze authority. 272 | /// 3. ..3+M `[signer]` M signer accounts. 273 | thaw_account: void, 274 | /// Transfers tokens from one account to another either directly or via a 275 | /// delegate. If this account is associated with the native mint then equal 276 | /// amounts of SOL and Tokens will be transferred to the destination 277 | /// account. 278 | /// 279 | /// This instruction differs from Transfer in that the token mint and 280 | /// decimals value is checked by the caller. This may be useful when 281 | /// creating transactions offline or within a hardware wallet. 282 | /// 283 | /// Accounts expected by this instruction: 284 | /// 285 | /// * Single owner/delegate 286 | /// 0. `[writable]` The source account. 287 | /// 1. `[]` The token mint. 288 | /// 2. `[writable]` The destination account. 289 | /// 3. `[signer]` The source account's owner/delegate. 290 | /// 291 | /// * Multisignature owner/delegate 292 | /// 0. `[writable]` The source account. 293 | /// 1. `[]` The token mint. 294 | /// 2. `[writable]` The destination account. 295 | /// 3. `[]` The source account's multisignature owner/delegate. 296 | /// 4. ..4+M `[signer]` M signer accounts. 297 | transfer_checked: struct { 298 | /// The amount of tokens to transfer. 299 | amount: u64, 300 | /// Expected number of base 10 digits to the right of the decimal place. 301 | decimals: u8, 302 | }, 303 | /// Approves a delegate. A delegate is given the authority over tokens on 304 | /// behalf of the source account's owner. 305 | /// 306 | /// This instruction differs from Approve in that the token mint and 307 | /// decimals value is checked by the caller. This may be useful when 308 | /// creating transactions offline or within a hardware wallet. 309 | /// 310 | /// Accounts expected by this instruction: 311 | /// 312 | /// * Single owner 313 | /// 0. `[writable]` The source account. 314 | /// 1. `[]` The token mint. 315 | /// 2. `[]` The delegate. 316 | /// 3. `[signer]` The source account owner. 317 | /// 318 | /// * Multisignature owner 319 | /// 0. `[writable]` The source account. 320 | /// 1. `[]` The token mint. 321 | /// 2. `[]` The delegate. 322 | /// 3. `[]` The source account's multisignature owner. 323 | /// 4. ..4+M `[signer]` M signer accounts 324 | approve_checked: struct { 325 | /// The amount of tokens the delegate is approved for. 326 | amount: u64, 327 | /// Expected number of base 10 digits to the right of the decimal place. 328 | decimals: u8, 329 | }, 330 | /// Mints new tokens to an account. The native mint does not support 331 | /// minting. 332 | /// 333 | /// This instruction differs from MintTo in that the decimals value is 334 | /// checked by the caller. This may be useful when creating transactions 335 | /// offline or within a hardware wallet. 336 | /// 337 | /// Accounts expected by this instruction: 338 | /// 339 | /// * Single authority 340 | /// 0. `[writable]` The mint. 341 | /// 1. `[writable]` The account to mint tokens to. 342 | /// 2. `[signer]` The mint's minting authority. 343 | /// 344 | /// * Multisignature authority 345 | /// 0. `[writable]` The mint. 346 | /// 1. `[writable]` The account to mint tokens to. 347 | /// 2. `[]` The mint's multisignature mint-tokens authority. 348 | /// 3. ..3+M `[signer]` M signer accounts. 349 | mint_to_checked: struct { 350 | /// The amount of new tokens to mint. 351 | amount: u64, 352 | /// Expected number of base 10 digits to the right of the decimal place. 353 | decimals: u8, 354 | }, 355 | /// Burns tokens by removing them from an account. `BurnChecked` does not 356 | /// support accounts associated with the native mint, use `CloseAccount` 357 | /// instead. 358 | /// 359 | /// This instruction differs from Burn in that the decimals value is checked 360 | /// by the caller. This may be useful when creating transactions offline or 361 | /// within a hardware wallet. 362 | /// 363 | /// Accounts expected by this instruction: 364 | /// 365 | /// * Single owner/delegate 366 | /// 0. `[writable]` The account to burn from. 367 | /// 1. `[writable]` The token mint. 368 | /// 2. `[signer]` The account's owner/delegate. 369 | /// 370 | /// * Multisignature owner/delegate 371 | /// 0. `[writable]` The account to burn from. 372 | /// 1. `[writable]` The token mint. 373 | /// 2. `[]` The account's multisignature owner/delegate. 374 | /// 3. ..3+M `[signer]` M signer accounts. 375 | burn_checked: struct { 376 | /// The amount of tokens to burn. 377 | amount: u64, 378 | /// Expected number of base 10 digits to the right of the decimal place. 379 | decimals: u8, 380 | }, 381 | /// Like InitializeAccount, but the owner pubkey is passed via instruction data 382 | /// rather than the accounts list. This variant may be preferable when using 383 | /// Cross Program Invocation from an instruction that does not need the owner's 384 | /// `AccountInfo` otherwise. 385 | /// 386 | /// Accounts expected by this instruction: 387 | /// 388 | /// 0. `[writable]` The account to initialize. 389 | /// 1. `[]` The mint this account will be associated with. 390 | /// 3. `[]` Rent sysvar 391 | initialize_account_2: struct { 392 | /// The new account's owner/multisignature. 393 | owner_id: sol.PublicKey, 394 | }, 395 | /// Given a wrapped / native token account (a token account containing SOL) 396 | /// updates its amount field based on the account's underlying `lamports`. 397 | /// This is useful if a non-wrapped SOL account uses `system_instruction::transfer` 398 | /// to move lamports to a wrapped token account, and needs to have its token 399 | /// `amount` field updated. 400 | /// 401 | /// Accounts expected by this instruction: 402 | /// 403 | /// 0. `[writable]` The native token account to sync with its underlying lamports. 404 | sync_native: void, 405 | /// Like InitializeAccount2, but does not require the Rent sysvar to be provided 406 | /// 407 | /// Accounts expected by this instruction: 408 | /// 409 | /// 0. `[writable]` The account to initialize. 410 | /// 1. `[]` The mint this account will be associated with. 411 | initialize_account_3: struct { 412 | /// The new account's owner/multisignature. 413 | owner_id: sol.PublicKey, 414 | }, 415 | /// Like InitializeMultisig, but does not require the Rent sysvar to be provided 416 | /// 417 | /// Accounts expected by this instruction: 418 | /// 419 | /// 0. `[writable]` The multisignature account to initialize. 420 | /// 1. ..1+N. `[]` The signer accounts, must equal to N where 1 <= N <= 421 | /// 11. 422 | initialize_multisig_2: struct { 423 | /// The number of signers (M) required to validate this multisignature 424 | /// account. 425 | m: u8, 426 | }, 427 | /// Like InitializeMint, but does not require the Rent sysvar to be provided 428 | /// 429 | /// Accounts expected by this instruction: 430 | /// 431 | /// 0. `[writable]` The mint to initialize. 432 | /// 433 | initialize_mint_2: struct { 434 | /// Number of base 10 digits to the right of the decimal place. 435 | decimals: u8, 436 | /// The authority/multisignature to mint tokens. 437 | mint_authority_id: sol.PublicKey, 438 | /// The freeze authority/multisignature of the mint. 439 | freeze_authority_id: ?sol.PublicKey, 440 | }, 441 | /// Gets the required size of an account for the given mint as a little-endian 442 | /// `u64`. 443 | /// 444 | /// Return data can be fetched using `sol_get_return_data` and deserializing 445 | /// the return data as a little-endian `u64`. 446 | /// 447 | /// Accounts expected by this instruction: 448 | /// 449 | /// 0. `[]` The mint to calculate for 450 | get_account_data_size: void, 451 | /// Initialize the Immutable Owner extension for the given token account 452 | /// 453 | /// Fails if the account has already been initialized, so must be called before 454 | /// `InitializeAccount`. 455 | /// 456 | /// No-ops in this version of the program, but is included for compatibility 457 | /// with the Associated Token Account program. 458 | /// 459 | /// Accounts expected by this instruction: 460 | /// 461 | /// 0. `[writable]` The account to initialize. 462 | /// 463 | /// Data expected by this instruction: 464 | /// None 465 | initialize_immutable_owner: void, 466 | /// Convert an Amount of tokens to a UiAmount `string`, using the given mint. 467 | /// In this version of the program, the mint can only specify the number of decimals. 468 | /// 469 | /// Fails on an invalid mint. 470 | /// 471 | /// Return data can be fetched using `sol_get_return_data` and deserialized with 472 | /// `String::from_utf8`. 473 | /// 474 | /// Accounts expected by this instruction: 475 | /// 476 | /// 0. `[]` The mint to calculate for 477 | amount_to_ui_amount: struct { 478 | /// The amount of tokens to reformat. 479 | amount: u64, 480 | }, 481 | /// Convert a UiAmount of tokens to a little-endian `u64` raw Amount, using the given mint. 482 | /// In this version of the program, the mint can only specify the number of decimals. 483 | /// 484 | /// Return data can be fetched using `sol_get_return_data` and deserializing 485 | /// the return data as a little-endian `u64`. 486 | /// 487 | /// Accounts expected by this instruction: 488 | /// 489 | /// 0. `[]` The mint to calculate for 490 | ui_amount_to_amount: struct { 491 | /// The ui_amount of tokens to reformat. 492 | ui_amount: [*:0]u8, 493 | }, 494 | // Any new variants also need to be added to program-2022 `TokenInstruction`, so that the 495 | // latter remains a superset of this instruction set. New variants also need to be added to 496 | // token/js/src/instructions/types.ts to maintain @solana/spl-token compatability 497 | }; 498 | 499 | // 4 + 32 + 8 + 1 + 1 + 4 + 32 500 | 501 | pub const Mint = struct { 502 | pub const len = 82; 503 | 504 | authority: bincode.Option(sol.PublicKey), 505 | supply: u64, 506 | decimals: u8, 507 | is_initialized: bool, 508 | freeze_authority: bincode.Option(sol.PublicKey), 509 | 510 | pub fn decode(bytes: []const u8) !token.Mint { 511 | return bincode.readFromSlice(undefined, token.Mint, bytes, .{}); 512 | } 513 | 514 | pub fn writeTo(self: token.Mint, writer: anytype) !void { 515 | return bincode.write(writer, self, .{}); 516 | } 517 | }; 518 | 519 | pub const Account = struct { 520 | pub const len = 165; 521 | 522 | pub const State = enum(u8) { 523 | uninitialized, 524 | initialized, 525 | frozen, 526 | }; 527 | 528 | mint_id: sol.PublicKey, 529 | owner: sol.PublicKey, 530 | amount: u64, 531 | delegate_id: bincode.Option(sol.PublicKey), 532 | state: token.Account.State, 533 | is_native: bincode.Option(u64), 534 | delegated_amount: u64, 535 | close_authority_id: bincode.Option(sol.PublicKey), 536 | 537 | pub fn decode(bytes: []const u8) !token.Account { 538 | return bincode.readFromSlice(undefined, token.Account, bytes, .{}); 539 | } 540 | 541 | pub fn writeTo(self: token.Account, writer: anytype) !void { 542 | return bincode.write(writer, self, .{}); 543 | } 544 | }; 545 | 546 | pub const Multisig = struct { 547 | pub const len = 355; 548 | /// Minimum number of multisignature signers (min N) 549 | pub const min_signers = 1; 550 | /// Maximum number of multisignature signers (max N) 551 | pub const max_signers = 11; 552 | 553 | /// Number of signers required 554 | m: u8, 555 | /// Number of valid signers 556 | n: u8, 557 | /// Is `true` if this structure has been initialized 558 | is_initialized: bool, 559 | /// Signer public keys 560 | signers: [token.Multisig.max_signers]sol.PublicKey, 561 | }; 562 | 563 | pub fn initializeMint(mint: sol.Account.Info, params: struct { 564 | mint_authority_id: sol.PublicKey, 565 | freeze_authority_id: ?sol.PublicKey, 566 | decimals: u8, 567 | rent: sol.Account.Info, 568 | seeds: []const []const []const u8 = &.{}, 569 | }) !void { 570 | const data = try bincode.writeAlloc(sol.allocator, token.Instruction{ 571 | .initialize_mint = .{ 572 | .decimals = params.decimals, 573 | .mint_authority_id = params.mint_authority_id, 574 | .freeze_authority_id = params.freeze_authority_id, 575 | }, 576 | }, .{}); 577 | defer sol.allocator.free(data); 578 | 579 | const instruction = sol.Instruction.from(.{ 580 | .program_id = &spl.token_program_id, 581 | .accounts = &[_]sol.Account.Param{ 582 | .{ .id = mint.id, .is_writable = true, .is_signer = false }, 583 | .{ .id = params.rent.id, .is_writable = false, .is_signer = false }, 584 | }, 585 | .data = data, 586 | }); 587 | 588 | try instruction.invokeSigned(&.{ mint, params.rent }, params.seeds); 589 | } 590 | 591 | pub fn initializeAccount(account: sol.Account.Info, params: struct { 592 | mint: sol.Account.Info, 593 | owner: sol.Account.Info, 594 | rent: sol.Account.Info, 595 | seeds: []const []const []const u8 = &.{}, 596 | }) !void { 597 | const data = try bincode.writeAlloc(sol.allocator, token.Instruction.initialize_account, .{}); 598 | defer sol.allocator.free(data); 599 | 600 | const instruction = sol.Instruction.from(.{ 601 | .program_id = &spl.token_program_id, 602 | .accounts = &[_]sol.Account.Param{ 603 | .{ .id = account.id, .is_writable = true, .is_signer = false }, 604 | .{ .id = params.mint.id, .is_writable = false, .is_signer = false }, 605 | .{ .id = params.owner.id, .is_writable = false, .is_signer = false }, 606 | .{ .id = params.rent.id, .is_writable = false, .is_signer = false }, 607 | }, 608 | .data = data, 609 | }); 610 | 611 | try instruction.invokeSigned(&.{ account, params.mint, params.owner, params.rent }, params.seeds); 612 | } 613 | 614 | pub fn transfer(params: struct { 615 | from: sol.Account.Info, 616 | to: sol.Account.Info, 617 | amount: u64, 618 | authority: union(enum) { 619 | single: sol.Account.Info, 620 | multiple: []const sol.Account.Info, 621 | }, 622 | seeds: []const []const []const u8 = &.{}, 623 | }) !void { 624 | const data = try bincode.writeAlloc(sol.allocator, token.Instruction{ 625 | .transfer = .{ .amount = params.amount }, 626 | }, .{}); 627 | defer sol.allocator.free(data); 628 | 629 | const instruction = sol.Instruction.from(.{ 630 | .program_id = &spl.token_program_id, 631 | .accounts = &[_]sol.Account.Param{ 632 | .{ .id = params.from.id, .is_writable = true, .is_signer = false }, 633 | .{ .id = params.to.id, .is_writable = true, .is_signer = false }, 634 | .{ .id = params.authority.single.id, .is_writable = false, .is_signer = true }, 635 | }, 636 | .data = data, 637 | }); 638 | 639 | try instruction.invokeSigned(&.{ params.from, params.to, params.authority.single }, params.seeds); 640 | } 641 | 642 | pub fn setAuthority(mint_or_account: sol.Account.Info, params: struct { 643 | authority: union(enum) { 644 | single: sol.Account.Info, 645 | multiple: []const sol.Account.Info, 646 | }, 647 | authority_type: AuthorityType, 648 | new_authority_id: ?sol.PublicKey, 649 | seeds: []const []const []const u8 = &.{}, 650 | }) !void { 651 | const data = try bincode.writeAlloc(sol.allocator, token.Instruction{ 652 | .set_authority = .{ 653 | .authority_type = params.authority_type, 654 | .new_authority_id = params.new_authority_id, 655 | }, 656 | }, .{}); 657 | defer sol.allocator.free(data); 658 | 659 | const instruction = sol.Instruction.from(.{ 660 | .program_id = &spl.token_program_id, 661 | .accounts = &[_]sol.Account.Param{ 662 | .{ .id = mint_or_account.id, .is_writable = true, .is_signer = false }, 663 | .{ .id = params.authority.single.id, .is_writable = false, .is_signer = true }, 664 | }, 665 | .data = data, 666 | }); 667 | 668 | try instruction.invokeSigned(&.{ mint_or_account, params.authority.single }, params.seeds); 669 | } 670 | 671 | pub fn burn(params: struct { 672 | account: sol.Account.Info, 673 | mint: sol.Account.Info, 674 | authority: union(enum) { 675 | single: sol.Account.Info, 676 | multiple: []const sol.Account.Info, 677 | }, 678 | amount: u64, 679 | seeds: []const []const []const u8 = &.{}, 680 | }) !void { 681 | const data = try bincode.writeAlloc(sol.allocator, token.Instruction{ 682 | .burn = .{ 683 | .amount = params.amount, 684 | }, 685 | }, .{}); 686 | defer sol.allocator.free(data); 687 | 688 | const instruction = sol.Instruction.from(.{ 689 | .program_id = &spl.token_program_id, 690 | .accounts = &[_]sol.Account.Param{ 691 | .{ .id = params.account.id, .is_writable = true, .is_signer = false }, 692 | .{ .id = params.mint.id, .is_writable = true, .is_signer = false }, 693 | .{ .id = params.authority.single.id, .is_writable = false, .is_signer = true }, 694 | }, 695 | .data = data, 696 | }); 697 | 698 | try instruction.invokeSigned(&.{ params.account, params.mint, params.authority.single }, params.seeds); 699 | } 700 | 701 | pub fn mintTo(params: struct { 702 | mint: sol.Account.Info, 703 | account: sol.Account.Info, 704 | amount: u64, 705 | mint_authority: union(enum) { 706 | single: sol.Account.Info, 707 | multiple: []const sol.Account.Info, 708 | }, 709 | seeds: []const []const []const u8 = &.{}, 710 | }) !void { 711 | const data = try bincode.writeAlloc(sol.allocator, token.Instruction{ 712 | .mint_to = .{ 713 | .amount = params.amount, 714 | }, 715 | }, .{}); 716 | defer sol.allocator.free(data); 717 | 718 | const instruction = sol.Instruction.from(.{ 719 | .program_id = &spl.token_program_id, 720 | .accounts = &[_]sol.Account.Param{ 721 | .{ .id = params.mint.id, .is_writable = true, .is_signer = false }, 722 | .{ .id = params.account.id, .is_writable = true, .is_signer = false }, 723 | .{ .id = params.mint_authority.single.id, .is_writable = false, .is_signer = true }, 724 | }, 725 | .data = data, 726 | }); 727 | 728 | try instruction.invokeSigned(&.{ params.mint, params.account, params.mint_authority.single }, params.seeds); 729 | } 730 | 731 | pub fn closeAccount(account: sol.Account.Info, params: struct { 732 | account_to_receive_remaining_tokens: sol.Account.Info, 733 | owner: sol.Account.Info, 734 | seeds: []const []const []const u8 = &.{}, 735 | }) !void { 736 | const data = try bincode.writeAlloc(sol.allocator, token.Instruction.close_account, .{}); 737 | defer sol.allocator.free(data); 738 | 739 | const instruction = sol.Instruction.from(.{ 740 | .program_id = &spl.token_program_id, 741 | .accounts = &[_]sol.Account.Param{ 742 | .{ .id = account.id, .is_writable = true, .is_signer = false }, 743 | .{ .id = params.account_to_receive_remaining_tokens.id, .is_writable = true, .is_signer = false }, 744 | .{ .id = params.owner.id, .is_writable = false, .is_signer = true }, 745 | }, 746 | .data = data, 747 | }); 748 | 749 | try instruction.invokeSigned(&.{ account, params.account_to_receive_remaining_tokens, params.owner }, params.seeds); 750 | } 751 | 752 | pub fn freezeAccount(account: sol.Account.Info, params: struct { 753 | mint: sol.Account.Info, 754 | freeze_authority: union(enum) { 755 | single: sol.Account.Info, 756 | multiple: []const sol.Account.Info, 757 | }, 758 | seeds: []const []const []const u8 = &.{}, 759 | }) !void { 760 | const data = try bincode.writeAlloc(sol.allocator, token.Instruction.freeze_account, .{}); 761 | defer sol.allocator.free(data); 762 | 763 | const instruction = sol.Instruction.from(.{ 764 | .program_id = &spl.token_program_id, 765 | .accounts = &[_]sol.Account.Param{ 766 | .{ .id = account.id, .is_writable = true, .is_signer = false }, 767 | .{ .id = params.mint.id, .is_writable = false, .is_signer = false }, 768 | .{ .id = params.freeze_authority.single.id, .is_writable = false, .is_signer = true }, 769 | }, 770 | .data = data, 771 | }); 772 | 773 | try instruction.invokeSigned(&.{ account, params.mint, params.freeze_authority.single }, params.seeds); 774 | } 775 | 776 | test { 777 | std.testing.refAllDecls(@This()); 778 | } 779 | 780 | test "token.Mint: decode" { 781 | const mint = try token.Mint.decode(&.{ 1, 0, 0, 0, 152, 20, 133, 228, 144, 2, 178, 195, 30, 8, 250, 169, 148, 235, 43, 40, 87, 221, 245, 227, 234, 210, 0, 59, 91, 201, 36, 22, 115, 249, 197, 34, 128, 92, 74, 46, 88, 91, 240, 6, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); 782 | sol.print("mint: {}", .{mint}); 783 | } 784 | 785 | test "token.Account: decode" { 786 | const account = try token.Account.decode(&.{ 38, 136, 199, 122, 42, 156, 154, 209, 115, 24, 105, 157, 203, 133, 179, 217, 162, 55, 98, 198, 231, 21, 107, 199, 248, 59, 48, 82, 149, 50, 147, 242, 137, 3, 79, 28, 13, 227, 40, 160, 41, 42, 170, 226, 227, 192, 156, 176, 67, 103, 166, 39, 95, 238, 193, 178, 126, 0, 146, 162, 23, 67, 18, 159, 232, 115, 60, 12, 252, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); 787 | sol.print("account: {}", .{account}); 788 | } 789 | -------------------------------------------------------------------------------- /metaplex/token_metadata.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sol = @import("sol"); 3 | const borsh = @import("borsh"); 4 | 5 | const metaplex = @import("metaplex.zig"); 6 | 7 | const token_metadata = @This(); 8 | 9 | pub const max_name_len = 32; 10 | pub const max_symbol_len = 10; 11 | pub const max_uri_len = 200; 12 | 13 | pub const max_metadata_len = 14 | 1 + // key 15 | 32 + // update authority id 16 | 32 + // mint id 17 | max_data_len + 18 | 1 + // primary sale 19 | 1 + // mutable 20 | 9 + // nonce 21 | 34 + // collection 22 | 18 + // uses 23 | 2 + // token standard 24 | 118 // padding 25 | ; 26 | 27 | pub const max_data_len = 4 + 28 | max_name_len + 29 | 4 + 30 | max_symbol_len + 31 | 4 + 32 | max_uri_len + 33 | 2 + 34 | 1 + 35 | 4 + 36 | max_creator_limit * max_creator_len; 37 | 38 | pub const max_edition_len = 1 + 32 + 8 + 200; 39 | 40 | pub const max_master_edition_len = 1 + 9 + 8 + 264; 41 | pub const max_creator_limit = 5; 42 | pub const max_creator_len = 32 + 1 + 1; 43 | pub const max_reservations = 200; 44 | pub const max_reservation_list_v1_len = 1 + 32 + 8 + 8 + max_reservations * 34 + 100; 45 | pub const max_reservation_list_len = 1 + 32 + 8 + 8 + max_reservations * 48 + 8 + 8 + 84; 46 | pub const max_edition_marker_len = 32; 47 | pub const edition_marker_bit_len = 248; 48 | pub const use_authority_record_len = 18; 49 | pub const collection_authority_record_len = 11; 50 | 51 | pub fn getMetadataId(mint_id: sol.PublicKey) !sol.PublicKey { 52 | const pda = try sol.PublicKey.findProgramAddress(.{ "metadata", &metaplex.token_metadata_program_id.bytes, mint_id }, metaplex.token_metadata_program_id); 53 | return pda.address; 54 | } 55 | 56 | pub fn getMasterEditionId(mint_id: sol.PublicKey) !sol.PublicKey { 57 | const pda = try sol.PublicKey.findProgramAddress(.{ "metadata", &metaplex.token_metadata_program_id.bytes, mint_id, "edition" }, metaplex.token_metadata_program_id); 58 | return pda.address; 59 | } 60 | 61 | pub fn getEditionId(mint_id: sol.PublicKey, edition_number: u8) !sol.PublicKey { 62 | const pda = try sol.PublicKey.findProgramAddress(.{ "metadata", &metaplex.token_metadata_program_id.bytes, mint_id, "edition", edition_number / edition_marker_bit_len }, metaplex.token_metadata_program_id); 63 | return pda.address; 64 | } 65 | 66 | pub fn createMetadataAccountV2(metadata: sol.Account.Info, params: struct { 67 | data: DataV2, 68 | is_mutable: bool, 69 | mint: sol.Account.Info, 70 | mint_authority: sol.Account.Info, 71 | payer: sol.Account.Info, 72 | update_authority: sol.Account.Info, 73 | system_program: sol.Account.Info, 74 | rent: sol.Account.Info, 75 | seeds: []const []const []const u8 = &.{}, 76 | }) !void { 77 | const data = try borsh.writeAlloc(sol.allocator, token_metadata.Instruction{ 78 | .create_metadata_account_v2 = .{ 79 | .data = params.data, 80 | .is_mutable = params.is_mutable, 81 | }, 82 | }); 83 | defer sol.allocator.free(data); 84 | 85 | const instruction = sol.Instruction.from(.{ 86 | .program_id = &metaplex.token_metadata_program_id, 87 | .accounts = &[_]sol.Account.Param{ 88 | .{ .id = metadata.id, .is_writable = true, .is_signer = false }, 89 | .{ .id = params.mint.id, .is_writable = false, .is_signer = false }, 90 | .{ .id = params.mint_authority.id, .is_writable = false, .is_signer = true }, 91 | .{ .id = params.payer.id, .is_writable = true, .is_signer = true }, 92 | .{ .id = params.update_authority.id, .is_writable = false, .is_signer = false }, 93 | .{ .id = params.system_program.id, .is_writable = false, .is_signer = false }, 94 | .{ .id = params.rent.id, .is_writable = false, .is_signer = false }, 95 | }, 96 | .data = data, 97 | }); 98 | 99 | try instruction.invokeSigned(&.{ 100 | metadata, 101 | params.mint, 102 | params.mint_authority, 103 | params.payer, 104 | params.update_authority, 105 | params.system_program, 106 | params.rent, 107 | }, params.seeds); 108 | } 109 | 110 | pub fn updateMetadataAccountV2(metadata: sol.Account.Info, params: struct { 111 | new_data: ?DataV2, 112 | new_update_authority_id: ?sol.PublicKey, 113 | primary_sale_happened: ?bool, 114 | is_mutable: ?bool, 115 | update_authority: sol.Account.Info, 116 | seeds: []const []const []const u8 = &.{}, 117 | }) !void { 118 | const data = try borsh.writeAlloc(sol.allocator, token_metadata.Instruction{ 119 | .update_metadata_account_v2 = .{ 120 | .data = params.new_data, 121 | .update_authority_id = params.new_update_authority_id, 122 | .primary_sale_happened = params.primary_sale_happened, 123 | .is_mutable = params.is_mutable, 124 | }, 125 | }); 126 | defer sol.allocator.free(data); 127 | 128 | const instruction = sol.Instruction.from(.{ 129 | .program_id = &metaplex.token_metadata_program_id, 130 | .accounts = &[_]sol.Account.Param{ 131 | .{ .id = metadata.id, .is_writable = true, .is_signer = false }, 132 | .{ .id = params.update_authority.id, .is_writable = false, .is_signer = true }, 133 | }, 134 | .data = data, 135 | }); 136 | 137 | try instruction.invokeSigned(&.{ metadata, params.update_authority }, params.seeds); 138 | } 139 | 140 | pub fn createMetadataAccount(metadata: sol.Account.Info, params: struct { 141 | data: Data, 142 | is_mutable: bool, 143 | mint: sol.Account.Info, 144 | mint_authority: sol.Account.Info, 145 | payer: sol.Account.Info, 146 | update_authority: sol.Account.Info, 147 | system_program: sol.Account.Info, 148 | rent: sol.Account.Info, 149 | seeds: []const []const []const u8 = &.{}, 150 | }) !void { 151 | const data = try borsh.writeAlloc(sol.allocator, token_metadata.Instruction{ 152 | .create_metadata_account = .{ 153 | .data = params.data, 154 | .is_mutable = params.is_mutable, 155 | }, 156 | }); 157 | defer sol.allocator.free(data); 158 | 159 | const instruction = sol.Instruction.from(.{ 160 | .program_id = &metaplex.token_metadata_program_id, 161 | .accounts = &[_]sol.Account.Param{ 162 | .{ .id = metadata.id, .is_writable = true, .is_signer = false }, 163 | .{ .id = params.mint.id, .is_writable = false, .is_signer = false }, 164 | .{ .id = params.mint_authority.id, .is_writable = false, .is_signer = true }, 165 | .{ .id = params.payer.id, .is_writable = true, .is_signer = true }, 166 | .{ .id = params.update_authority.id, .is_writable = false, .is_signer = false }, 167 | .{ .id = params.system_program.id, .is_writable = false, .is_signer = false }, 168 | .{ .id = params.rent.id, .is_writable = false, .is_signer = false }, 169 | }, 170 | .data = data, 171 | }); 172 | 173 | try instruction.invokeSigned(&.{ 174 | metadata, 175 | params.mint, 176 | params.mint_authority, 177 | params.payer, 178 | params.update_authority, 179 | params.system_program, 180 | params.rent, 181 | }, params.seeds); 182 | } 183 | 184 | pub fn updateMetadataAccount(metadata: sol.Account.Info, params: struct { 185 | new_data: ?Data, 186 | new_update_authority_id: ?sol.PublicKey, 187 | primary_sale_happened: ?bool, 188 | update_authority: sol.Account.Info, 189 | seeds: []const []const []const u8 = &.{}, 190 | }) !void { 191 | const data = try borsh.writeAlloc(sol.allocator, token_metadata.Instruction{ 192 | .update_metadata_account = .{ 193 | .data = params.new_data, 194 | .update_authority_id = params.new_update_authority_id, 195 | .primary_sale_happened = params.primary_sale_happened, 196 | }, 197 | }); 198 | defer sol.allocator.free(data); 199 | 200 | const instruction = sol.Instruction.from(.{ 201 | .program_id = &metaplex.token_metadata_program_id, 202 | .accounts = &[_]sol.Account.Param{ 203 | .{ .id = metadata.id, .is_writable = true, .is_signer = false }, 204 | .{ .id = params.update_authority.id, .is_writable = false, .is_signer = true }, 205 | }, 206 | .data = data, 207 | }); 208 | 209 | try instruction.invokeSigned(&.{ metadata, params.update_authority }, params.seeds); 210 | } 211 | 212 | pub fn createMasterEditionV3(edition: sol.Account.Info, params: struct { 213 | max_supply: ?u64, 214 | mint: sol.Account.Info, 215 | update_authority: sol.Account.Info, 216 | mint_authority: sol.Account.Info, 217 | payer: sol.Account.Info, 218 | metadata: sol.Account.Info, 219 | token_program: sol.Account.Info, 220 | system_program: sol.Account.Info, 221 | rent: sol.Account.Info, 222 | seeds: []const []const []const u8 = &.{}, 223 | }) !void { 224 | const data = try borsh.writeAlloc(sol.allocator, token_metadata.Instruction{ 225 | .create_master_edition_v3 = .{ 226 | .max_supply = params.max_supply, 227 | }, 228 | }); 229 | defer sol.allocator.free(data); 230 | 231 | const instruction = sol.Instruction.from(.{ 232 | .program_id = &metaplex.token_metadata_program_id, 233 | .accounts = &[_]sol.Account.Param{ 234 | .{ .id = edition.id, .is_writable = true, .is_signer = false }, 235 | .{ .id = params.mint.id, .is_writable = true, .is_signer = false }, 236 | .{ .id = params.update_authority.id, .is_writable = false, .is_signer = true }, 237 | .{ .id = params.mint_authority.id, .is_writable = false, .is_signer = true }, 238 | .{ .id = params.payer.id, .is_writable = true, .is_signer = true }, 239 | .{ .id = params.metadata.id, .is_writable = true, .is_signer = false }, 240 | .{ .id = params.token_program.id, .is_writable = false, .is_signer = false }, 241 | .{ .id = params.system_program.id, .is_writable = false, .is_signer = false }, 242 | .{ .id = params.rent.id, .is_writable = false, .is_signer = false }, 243 | }, 244 | .data = data, 245 | }); 246 | 247 | try instruction.invokeSigned(&.{ 248 | edition, 249 | params.mint, 250 | params.update_authority, 251 | params.mint_authority, 252 | params.payer, 253 | params.metadata, 254 | params.token_program, 255 | params.system_program, 256 | params.rent, 257 | }, params.seeds); 258 | } 259 | 260 | pub fn setAndVerifyCollection(params: struct { 261 | metadata: sol.Account.Info, 262 | collection_authority: sol.Account.Info, 263 | payer: sol.Account.Info, 264 | update_authority: sol.Account.Info, 265 | collection_mint: sol.Account.Info, 266 | collection_metadata: sol.Account.Info, 267 | collection_master_edition: sol.Account.Info, 268 | collection_authority_record: ?sol.Account.Info, 269 | seeds: []const []const []const u8 = &.{}, 270 | }) !void { 271 | const data = try borsh.writeAlloc(sol.allocator, token_metadata.Instruction.set_and_verify_collection); 272 | defer sol.allocator.free(data); 273 | 274 | var accounts = [8]sol.Account.Param{ 275 | .{ .id = params.metadata.id, .is_writable = true, .is_signer = false }, 276 | .{ .id = params.collection_authority.id, .is_writable = false, .is_signer = true }, 277 | .{ .id = params.payer.id, .is_writable = true, .is_signer = true }, 278 | .{ .id = params.update_authority.id, .is_writable = false, .is_signer = true }, 279 | .{ .id = params.collection_mint.id, .is_writable = false, .is_signer = false }, 280 | .{ .id = params.collection_metadata.id, .is_writable = false, .is_signer = false }, 281 | .{ .id = params.collection_master_edition.id, .is_writable = false, .is_signer = false }, 282 | undefined, 283 | }; 284 | 285 | if (params.collection_authority_record) |collection_authority_record| { 286 | accounts[accounts.len - 1] = .{ .id = collection_authority_record.id, .is_writable = false, .is_signer = false }; 287 | } 288 | 289 | const instruction = sol.Instruction.from(.{ 290 | .program_id = &metaplex.token_metadata_program_id, 291 | .accounts = if (params.collection_authority_record != null) accounts[0..] else accounts[0 .. accounts.len - 1], 292 | .data = data, 293 | }); 294 | 295 | if (params.collection_authority_record) |collection_authority_record| { 296 | try instruction.invokeSigned(&.{ 297 | params.metadata, 298 | params.collection_authority, 299 | params.payer, 300 | params.update_authority, 301 | params.collection_mint, 302 | params.collection_metadata, 303 | params.collection_master_edition, 304 | collection_authority_record, 305 | }, params.seeds); 306 | } else { 307 | try instruction.invokeSigned(&.{ 308 | params.metadata, 309 | params.collection_authority, 310 | params.payer, 311 | params.update_authority, 312 | params.collection_mint, 313 | params.collection_metadata, 314 | params.collection_master_edition, 315 | }, params.seeds); 316 | } 317 | } 318 | 319 | pub fn burnNft(params: struct { 320 | metadata: sol.Account.Info, 321 | owner: sol.Account.Info, 322 | mint: sol.Account.Info, 323 | token_account: sol.Account.Info, 324 | master_edition_account: sol.Account.Info, 325 | token_program: sol.Account.Info, 326 | collection_metadata: ?sol.Account.Info, 327 | seeds: []const []const []const u8 = &.{}, 328 | }) !void { 329 | const data = try borsh.writeAlloc(sol.allocator, token_metadata.Instruction.burn_nft); 330 | defer sol.allocator.free(data); 331 | 332 | var accounts: [7]sol.Account.Param = undefined; 333 | accounts[0] = .{ .id = params.metadata.id, .is_writable = true, .is_signer = false }; 334 | accounts[1] = .{ .id = params.owner.id, .is_writable = true, .is_signer = true }; 335 | accounts[2] = .{ .id = params.mint.id, .is_writable = true, .is_signer = false }; 336 | accounts[3] = .{ .id = params.token_account.id, .is_writable = true, .is_signer = false }; 337 | accounts[4] = .{ .id = params.master_edition_account.id, .is_writable = true, .is_signer = false }; 338 | accounts[5] = .{ .id = params.token_program.id, .is_writable = false, .is_signer = false }; 339 | 340 | var num_accounts: usize = 6; 341 | if (params.collection_metadata) |collection_metadata| { 342 | accounts[6] = .{ .id = collection_metadata.id, .is_writable = true, .is_signer = false }; 343 | num_accounts += 1; 344 | } 345 | 346 | const instruction = sol.Instruction.from(.{ 347 | .program_id = &metaplex.token_metadata_program_id, 348 | .accounts = accounts[0..num_accounts], 349 | .data = data, 350 | }); 351 | 352 | if (params.collection_metadata) |collection_metadata| { 353 | try instruction.invokeSigned(&.{ 354 | params.metadata, 355 | params.owner, 356 | params.mint, 357 | params.token_account, 358 | params.master_edition_account, 359 | params.token_program, 360 | collection_metadata, 361 | }, params.seeds); 362 | } else { 363 | try instruction.invokeSigned(&.{ 364 | params.metadata, 365 | params.owner, 366 | params.mint, 367 | params.token_account, 368 | params.master_edition_account, 369 | params.token_program, 370 | }, params.seeds); 371 | } 372 | } 373 | 374 | pub const Instruction = union(enum) { 375 | /// Create Metadata object. 376 | /// 377 | /// #[account(0, writable, name="metadata", desc="Metadata key (pda of ['metadata', program id, mint id])")] 378 | /// #[account(1, name="mint", desc="Mint of token asset")] 379 | /// #[account(2, signer, name="mint_authority", desc="Mint authority")] 380 | /// #[account(3, signer, name="payer", desc="payer")] 381 | /// #[account(4, name="update_authority", desc="update authority info")] 382 | /// #[account(5, name="system_program", desc="System program")] 383 | /// #[account(6, name="rent", desc="Rent info")] 384 | create_metadata_account: struct { 385 | /// Note that unique metadatas are disabled for now. 386 | data: Data, 387 | /// Whether you want your metadata to be updateable in the future. 388 | is_mutable: bool, 389 | }, 390 | /// Update a Metadata 391 | /// 392 | /// #[account(0, writable, name="metadata", desc="Metadata account")] 393 | /// #[account(1, signer, name="update_authority", desc="Update authority key")] 394 | update_metadata_account: struct { 395 | data: ?Data, 396 | update_authority_id: ?sol.PublicKey, 397 | primary_sale_happened: ?bool, 398 | }, 399 | /// Register a Metadata as a Master Edition V1, which means Editions can be minted. 400 | /// Henceforth, no further tokens will be mintable from this primary mint. Will throw an error if more than one 401 | /// token exists, and will throw an error if less than one token exists in this primary mint. 402 | /// 403 | /// #[account(0, writable, name="edition", desc="Unallocated edition V1 account with address as pda of ['metadata', program id, mint, 'edition']")] 404 | /// #[account(1, writable, name="mint", desc="Metadata mint")] 405 | /// #[account(2, writable, name="printing_mint", desc="Printing mint - A mint you control that can mint tokens that can be exchanged for limited editions of your master edition via the MintNewEditionFromMasterEditionViaToken endpoint")] 406 | /// #[account(3, writable, name="one_time_printing_authorization_mint", desc="One time authorization printing mint - A mint you control that prints tokens that gives the bearer permission to mint any number of tokens from the printing mint one time via an endpoint with the token-metadata program for your metadata. Also burns the token.")] 407 | /// #[account(4, signer, name="update_authority", desc="Current Update authority key")] 408 | /// #[account(5, signer, name="printing_mint_authority", desc="Printing mint authority - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY.")] 409 | /// #[account(6, signer, name="mint_authority", desc="Mint authority on the metadata's mint - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY")] 410 | /// #[account(7, name="metadata", desc="Metadata account")] 411 | /// #[account(8, signer, name="payer", desc="payer")] 412 | /// #[account(9, name="token_program", desc="Token program")] 413 | /// #[account(10, name="system_program", desc="System program")] 414 | /// #[account(11, name="rent", desc="Rent info")] 415 | /// #[account(12, signer, name="one_time_printing_authorization_mint_authority", desc="One time authorization printing mint authority - must be provided if using max supply. THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY.")] 416 | deprecated_create_master_edition: struct { 417 | max_supply: ?u64, 418 | }, 419 | /// Given an authority token minted by the Printing mint of a master edition, and a brand new non-metadata-ed mint with one token 420 | /// make a new Metadata + Edition that is a child of the master edition denoted by this authority token. 421 | /// 422 | /// #[account(0, writable, name="metadata", desc="New Metadata key (pda of ['metadata', program id, mint id])")] 423 | /// #[account(1, writable, name="edition", desc="New Edition V1 (pda of ['metadata', program id, mint id, 'edition'])")] 424 | /// #[account(2, writable, name="master_edition", desc="Master Record Edition V1 (pda of ['metadata', program id, master metadata mint id, 'edition'])")] 425 | /// #[account(3, writable, name="mint", desc="Mint of new token - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY")] 426 | /// #[account(4, signer, name="mint_authority", desc="Mint authority of new mint")] 427 | /// #[account(5, writable, name="printing_mint", desc="Printing Mint of master record edition")] 428 | /// #[account(6, writable, name="master_token_account", desc="Token account containing Printing mint token to be transferred")] 429 | /// #[account(7, writable, name="edition_marker", desc="Edition pda to mark creation - will be checked for pre-existence. (pda of ['metadata', program id, master mint id, edition_number])")] 430 | /// #[account(8, signer, name="burn_authority", desc="Burn authority for this token")] 431 | /// #[account(9, signer, name="payer", desc="payer")] 432 | /// #[account(10, name="master_update_authority", desc="update authority info for new metadata account")] 433 | /// #[account(11, name="master_metadata", desc="Master record metadata account")] 434 | /// #[account(12, name="token_program", desc="Token program")] 435 | /// #[account(13, name="system_program", desc="System program")] 436 | /// #[account(14, name="rent", desc="Rent info")] 437 | /// #[account(15, optional, writable, name="reservation_list", desc="Reservation List - If present, and you are on this list, you can get an edition number given by your position on the list.")] 438 | deprecated_mint_new_edition_from_master_edition_via_printing_token: struct { 439 | edition: u64, 440 | }, 441 | /// Allows updating the primary sale boolean on Metadata solely through owning an account 442 | /// containing a token from the metadata's mint and being a signer on this transaction. 443 | /// A sort of limited authority for limited update capability that is required for things like 444 | /// Metaplex to work without needing full authority passing. 445 | /// 446 | /// #[account(0, writable, name="metadata", desc="Metadata key (pda of ['metadata', program id, mint id])")] 447 | /// #[account(1, signer, name="owner", desc="Owner on the token account")] 448 | /// #[account(2, name="token", desc="Account containing tokens from the metadata's mint")] 449 | update_primary_sale_happened_via_token: void, 450 | /// Reserve up to 200 editions in sequence for up to 200 addresses in an existing reservation PDA, which can then be used later by 451 | /// redeemers who have printing tokens as a reservation to get a specific edition number 452 | /// as opposed to whatever one is currently listed on the master edition. Used by Auction Manager 453 | /// to guarantee printing order on bid redemption. AM will call whenever the first person redeems a 454 | /// printing bid to reserve the whole block 455 | /// of winners in order and then each winner when they get their token submits their mint and account 456 | /// with the pda that was created by that first bidder - the token metadata can then cross reference 457 | /// these people with the list and see that bidder A gets edition #2, so on and so forth. 458 | /// 459 | /// NOTE: If you have more than 20 addresses in a reservation list, this may be called multiple times to build up the list, 460 | /// otherwise, it simply wont fit in one transaction. Only provide a total_reservation argument on the first call, which will 461 | /// allocate the edition space, and in follow up calls this will specifically be unnecessary (and indeed will error.) 462 | /// 463 | /// #[account(0, writable, name="master_edition", desc="Master Edition V1 key (pda of ['metadata', program id, mint id, 'edition'])")] 464 | /// #[account(1, writable, name="reservation_list", desc="PDA for ReservationList of ['metadata', program id, master edition key, 'reservation', resource-key]")] 465 | /// #[account(2, signer, name="resource", desc="The resource you tied the reservation list too")] 466 | deprecated_set_reservation_list: struct { 467 | /// If set, means that no more than this number of editions can ever be minted. This is immutable. 468 | reservations: []const Reservation, 469 | /// Should only be present on the very first call to set reservation list. 470 | total_reservation_spots: ?u64, 471 | /// Where in the reservation list you want to insert this slice of reservations. 472 | offset: u64, 473 | /// What the total spot offset is in the reservation list from the beginning to your slice of reservations. 474 | /// So if is going to be 4 total editions eventually reserved between your slice and the beginning of the array, 475 | /// split between 2 reservation entries, the offset variable above would be "2" since you start at entry 2 in 0 indexed array 476 | /// (first 2 taking 0 and 1) and because they each have 2 spots taken, this variable would be 4. 477 | total_spot_offset: u64, 478 | }, 479 | /// Create an empty reservation list for a resource who can come back later as a signer and fill the reservation list 480 | /// with reservations to ensure that people who come to get editions get the number they expect. See SetReservationList for more. 481 | /// 482 | /// #[account(0, writable, name="reservation_list", desc="PDA for ReservationList of ['metadata', program id, master edition key, 'reservation', resource-key]")] 483 | /// #[account(1, signer, name="payer", desc="Payer")] 484 | /// #[account(2, signer, name="update_authority", desc="Update authority")] 485 | /// #[account(3, name="master_edition", desc=" Master Edition V1 key (pda of ['metadata', program id, mint id, 'edition'])")] 486 | /// #[account(4, name="resource", desc="A resource you wish to tie the reservation list to. This is so your later visitors who come to redeem can derive your reservation list PDA with something they can easily get at. You choose what this should be.")] 487 | /// #[account(5, name="metadata", desc="Metadata key (pda of ['metadata', program id, mint id])")] 488 | /// #[account(6, name="system_program", desc="System program")] 489 | /// #[account(7, name="rent", desc="Rent info")] 490 | deprecated_create_reservation_list: void, 491 | /// Sign a piece of metadata that has you as an unverified creator so that it is now verified. 492 | /// 493 | /// #[account(0, writable, name="metadata", desc="Metadata (pda of ['metadata', program id, mint id])")] 494 | /// #[account(1, signer, name="creator", desc="Creator")] 495 | sign_metadata: void, 496 | /// Using a one time authorization token from a master edition v1, print any number of printing tokens from the printing_mint 497 | /// one time, burning the one time authorization token. 498 | /// 499 | /// [account(0, writable, name="destination", desc="Destination account")] 500 | /// [account(1, writable, name="token", desc="Token account containing one time authorization token")] 501 | /// [account(2, writable, name="one_time_printing_authorization_mint", desc="One time authorization mint")] 502 | /// [account(3, writable, name="printing_mint", desc="Printing mint")] 503 | /// [account(4, signer, name="burn_authority", desc="Burn authority")] 504 | /// [account(5, name="metadata", desc="Metadata key (pda of ['metadata', program id, mint id])")] 505 | /// [account(6, name="master_edition", desc="Master Edition V1 key (pda of ['metadata', program id, mint id, 'edition'])")] 506 | /// [account(7, name="token_program", desc="Token program")] 507 | /// [account(8, name="rent", desc="Rent")] 508 | deprecated_mint_printing_tokens_via_token: struct { 509 | supply: u64, 510 | }, 511 | /// Using your update authority, mint printing tokens for your master edition. 512 | /// 513 | /// #[account(0, writable, name="destination", desc="Destination account")] 514 | /// #[account(1, writable, name="printing_mint", desc="Printing mint")] 515 | /// #[account(2, signer, name="update_authority", desc="Update authority")] 516 | /// #[account(3, name="metadata", desc="Metadata key (pda of ['metadata', program id, mint id])")] 517 | /// #[account(4, name="master_edition", desc="Master Edition V1 key (pda of ['metadata', program id, mint id, 'edition'])")] 518 | /// #[account(5, name="token_program", desc="Token program")] 519 | /// #[account(6, name="rent", desc="Rent")] 520 | deprecated_mint_printing_token: struct { 521 | supply: u64, 522 | }, 523 | /// Register a Metadata as a Master Edition V2, which means Edition V2s can be minted. 524 | /// Henceforth, no further tokens will be mintable from this primary mint. Will throw an error if more than one 525 | /// token exists, and will throw an error if less than one token exists in this primary mint. 526 | /// 527 | /// #[account(0, writable, name="edition", desc="Unallocated edition V2 account with address as pda of ['metadata', program id, mint, 'edition']")] 528 | /// #[account(1, writable, name="mint", desc="Metadata mint")] 529 | /// #[account(2, signer, name="update_authority", desc="Update authority")] 530 | /// #[account(3, signer, name="mint_authority", desc="Mint authority on the metadata's mint - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY")] 531 | /// #[account(4, signer, name="payer", desc="payer")] 532 | /// #[account(5, name="metadata", desc="Metadata account")] 533 | /// #[account(6, name="token_program", desc="Token program")] 534 | /// #[account(7, name="system_program", desc="System program")] 535 | /// #[account(8, name="rent", desc="Rent info")] 536 | create_master_edition: struct { 537 | /// If set, means that no more than this number of editions can ever be minted. This is immutable. 538 | max_supply: ?u64, 539 | }, 540 | /// Given a token account containing the master edition token to prove authority, and a brand new non-metadata-ed mint with one token 541 | /// make a new Metadata + Edition that is a child of the master edition denoted by this authority token. 542 | /// 543 | /// #[account(0, writable, name="new_metadata", desc="New Metadata key (pda of ['metadata', program id, mint id])")] 544 | /// #[account(1, writable, name="new_edition", desc="New Edition (pda of ['metadata', program id, mint id, 'edition'])")] 545 | /// #[account(2, writable, name="master_edition", desc="Master Record Edition V2 (pda of ['metadata', program id, master metadata mint id, 'edition'])")] 546 | /// #[account(3, writable, name="new_mint", desc="Mint of new token - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY")] 547 | /// #[account(4, writable, name="edition_mark_pda", desc="Edition pda to mark creation - will be checked for pre-existence. (pda of ['metadata', program id, master metadata mint id, 'edition', edition_number]) where edition_number is NOT the edition number you pass in args but actually edition_number = floor(edition/EDITION_MARKER_BIT_SIZE).")] 548 | /// #[account(5, signer, name="new_mint_authority", desc="Mint authority of new mint")] 549 | /// #[account(6, signer, name="payer", desc="payer")] 550 | /// #[account(7, signer, name="token_account_owner", desc="owner of token account containing master token (#8)")] 551 | /// #[account(8, name="token_account", desc="token account containing token from master metadata mint")] 552 | /// #[account(9, name="new_metadata_update_authority", desc="Update authority info for new metadata")] 553 | /// #[account(10, name="metadata", desc="Master record metadata account")] 554 | /// #[account(11, name="token_program", desc="Token program")] 555 | /// #[account(12, name="system_program", desc="System program")] 556 | /// #[account(13, name="rent", desc="Rent info")] 557 | mint_new_edition_from_master_edition_via_token: struct { 558 | edition: u64, 559 | }, 560 | /// Converts the Master Edition V1 to a Master Edition V2, draining lamports from the two printing mints 561 | /// to the owner of the token account holding the master edition token. Permissionless. 562 | /// Can only be called if there are currenly no printing tokens or one time authorization tokens in circulation. 563 | /// 564 | /// #[account(0, writable, name="master_edition", desc="Master Record Edition V1 (pda of ['metadata', program id, master metadata mint id, 'edition'])")] 565 | /// #[account(1, writable, name="one_time_auth", desc="One time authorization mint")] 566 | /// #[account(2, writable, name="printing_mint", desc="Printing mint")] 567 | convert_master_edition_v1_to_v2: void, 568 | /// Proxy Call to Mint Edition using a Store Token Account as a Vault Authority. 569 | /// 570 | /// #[account(0, writable, name="new_metadata", desc="New Metadata key (pda of ['metadata', program id, mint id])")] 571 | /// #[account(1, writable, name="new_edition", desc="New Edition (pda of ['metadata', program id, mint id, 'edition'])")] 572 | /// #[account(2, writable, name="master_edition", desc="Master Record Edition V2 (pda of ['metadata', program id, master metadata mint id, 'edition']")] 573 | /// #[account(3, writable, name="new_mint", desc="Mint of new token - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY")] 574 | /// #[account(4, writable, name="edition_mark_pda", desc="Edition pda to mark creation - will be checked for pre-existence. (pda of ['metadata', program id, master metadata mint id, 'edition', edition_number]) where edition_number is NOT the edition number you pass in args but actually edition_number = floor(edition/EDITION_MARKER_BIT_SIZE).")] 575 | /// #[account(5, signer, name="new_mint_authority", desc="Mint authority of new mint")] 576 | /// #[account(6, signer, name="payer", desc="payer")] 577 | /// #[account(7, signer, name="vault_authority", desc="Vault authority")] 578 | /// #[account(8, name="safety_deposit_store", desc="Safety deposit token store account")] 579 | /// #[account(9, name="safety_deposit_box", desc="Safety deposit box")] 580 | /// #[account(10, name="vault", desc="Vault")] 581 | /// #[account(11, name="new_metadata_update_authority", desc="Update authority info for new metadata")] 582 | /// #[account(12, name="metadata", desc="Master record metadata account")] 583 | /// #[account(13, name="token_program", desc="Token program")] 584 | /// #[account(14, name="token_vault_program", desc="Token vault program")] 585 | /// #[account(15, name="system_program", desc="System program")] 586 | /// #[account(16, name="rent", desc="Rent info")] 587 | mint_new_edition_from_master_edition_via_vault_proxy: struct { 588 | edition: u64, 589 | }, 590 | /// Puff a Metadata - make all of it's variable length fields (name/uri/symbol) a fixed length using a null character 591 | /// so that it can be found using offset searches by the RPC to make client lookups cheaper. 592 | /// 593 | /// #[account(0, writable, name="metadata", desc="Metadata account")] 594 | puff_metadata: void, 595 | /// Update a Metadata with is_mutable as a parameter 596 | /// 597 | /// #[account(0, writable, name="metadata", desc="Metadata account")] 598 | /// #[account(1, signer, name="update_authority", desc="Update authority key")] 599 | update_metadata_account_v2: struct { 600 | data: ?DataV2, 601 | update_authority_id: ?sol.PublicKey, 602 | primary_sale_happened: ?bool, 603 | is_mutable: ?bool, 604 | }, 605 | /// Create Metadata object. 606 | /// 607 | /// #[account(0, writable, name="metadata", desc="Metadata key (pda of ['metadata', program id, mint id])")] 608 | /// #[account(1, name="mint", desc="Mint of token asset")] 609 | /// #[account(2, signer, name="mint_authority", desc="Mint authority")] 610 | /// #[account(3, signer, name="payer", desc="payer")] 611 | /// #[account(4, name="update_authority", desc="update authority info")] 612 | /// #[account(5, name="system_program", desc="System program")] 613 | /// #[account(6, name="rent", desc="Rent info")] 614 | create_metadata_account_v2: struct { 615 | /// Note that unique metadatas are disabled for now. 616 | data: DataV2, 617 | /// Whether you want your metadata to be updateable in the future. 618 | is_mutable: bool, 619 | }, 620 | /// Register a Metadata as a Master Edition V2, which means Edition V2s can be minted. 621 | /// Henceforth, no further tokens will be mintable from this primary mint. Will throw an error if more than one 622 | /// token exists, and will throw an error if less than one token exists in this primary mint. 623 | /// 624 | /// #[account(0, writable, name="edition", desc="Unallocated edition V2 account with address as pda of ['metadata', program id, mint, 'edition']")] 625 | /// #[account(1, writable, name="mint", desc="Metadata mint")] 626 | /// #[account(2, signer, name="update_authority", desc="Update authority")] 627 | /// #[account(3, signer, name="mint_authority", desc="Mint authority on the metadata's mint - THIS WILL TRANSFER AUTHORITY AWAY FROM THIS KEY")] 628 | /// #[account(4, signer, name="payer", desc="payer")] 629 | /// #[account(5, writable, name="metadata", desc="Metadata account")] 630 | /// #[account(6, name="token_program", desc="Token program")] 631 | /// #[account(7, name="system_program", desc="System program")] 632 | /// #[account(8, name="rent", desc="Rent info")] 633 | create_master_edition_v3: struct { 634 | /// If set, means that no more than this number of editions can ever be minted. This is immutable. 635 | max_supply: ?u64, 636 | }, 637 | /// If a MetadataAccount Has a Collection allow the UpdateAuthority of the Collection to Verify the NFT Belongs in the Collection. 638 | /// 639 | /// #[account(0, writable, name="metadata", desc="Metadata account")] 640 | /// #[account(1, signer, name="collection_authority", desc="Collection Update authority")] 641 | /// #[account(2, signer, name="payer", desc="payer")] 642 | /// #[account(3, name="collection_mint", desc="Mint of the Collection")] 643 | /// #[account(4, name="collection", desc="Metadata Account of the Collection")] 644 | /// #[account(5, name="collection_master_edition_account", desc="MasterEdition2 Account of the Collection Token")] 645 | verify_collection: void, 646 | /// Utilize or Use an NFT , burns the NFT and returns the lamports to the update authority if the use method is burn and its out of uses. 647 | /// Use Authority can be the Holder of the NFT, or a Delegated Use Authority. 648 | /// 649 | /// #[account(0, writable, name="metadata", desc="Metadata account")] 650 | /// #[account(1, writable, name="token_account", desc="Token Account Of NFT")] 651 | /// #[account(2, writable, name="mint", desc="Mint of the Metadata")] 652 | /// #[account(3, signer, name="use_authority", desc="A Use Authority / Can be the current Owner of the NFT")] 653 | /// #[account(4, name="owner", desc="Owner")] 654 | /// #[account(5, name="token_program", desc="Token program")] 655 | /// #[account(6, name="ata_program", desc="Associated Token program")] 656 | /// #[account(7, name="system_program", desc="System program")] 657 | /// #[account(8, name="rent", desc="Rent info")] 658 | /// #[account(9, optional, writable, name="use_authority_record", desc="Use Authority Record PDA If present the program Assumes a delegated use authority")] 659 | /// #[account(10, optional, name="burner", desc="Program As Signer (Burner)")] 660 | utilize: struct { 661 | number_of_uses: u64, 662 | }, 663 | /// Approve another account to call [utilize] on this NFT. 664 | /// 665 | /// #[account(0, writable, name="use_authority_record", desc="Use Authority Record PDA")] 666 | /// #[account(1, signer, name="owner", desc="Owner")] 667 | /// #[account(2, signer, name="payer", desc="Payer")] 668 | /// #[account(3, name="user", desc="A Use Authority")] 669 | /// #[account(4, writable, name="owner_token_account", desc="Owned Token Account Of Mint")] 670 | /// #[account(5, name="metadata", desc="Metadata account")] 671 | /// #[account(6, name="mint", desc="Mint of Metadata")] 672 | /// #[account(7, name="burner", desc="Program As Signer (Burner)")] 673 | /// #[account(8, name="token_program", desc="Token program")] 674 | /// #[account(9, name="system_program", desc="System program")] 675 | /// #[account(10, name="rent", desc="Rent info")] 676 | approve_use_authority: struct { 677 | number_of_uses: u64, 678 | }, 679 | /// Revoke account to call [utilize] on this NFT. 680 | /// 681 | /// #[account(0, writable, name="use_authority_record", desc="Use Authority Record PDA")] 682 | /// #[account(1, signer, name="owner", desc="Owner")] 683 | /// #[account(2, name="user", desc="A Use Authority")] 684 | /// #[account(3, writable, name="owner_token_account", desc="Owned Token Account Of Mint")] 685 | /// #[account(4, name="mint", desc="Mint of Metadata")] 686 | /// #[account(5, name="metadata", desc="Metadata account")] 687 | /// #[account(6, name="token_program", desc="Token program")] 688 | /// #[account(7, name="system_program", desc="System program")] 689 | /// #[account(8, name="rent", desc="Rent info")] 690 | revoke_use_authority: void, 691 | /// If a MetadataAccount Has a Collection allow an Authority of the Collection to unverify an NFT in a Collection. 692 | /// 693 | /// #[account(0, writable, name="metadata", desc="Metadata account")] 694 | /// #[account(1, signer, name="collection_authority", desc="Collection Authority")] 695 | /// #[account(2, name="collection_mint", desc="Mint of the Collection")] 696 | /// #[account(3, name="collection", desc="Metadata Account of the Collection")] 697 | /// #[account(4, name="collection_master_edition_account", desc="MasterEdition2 Account of the Collection Token")] 698 | /// #[account(5, optional, name="collection_authority_record", desc="Collection Authority Record PDA")] 699 | unverify_collection: void, 700 | /// Approve another account to verify NFTs belonging to a collection, [verify_collection] on the collection NFT. 701 | /// 702 | /// #[account(0, writable, name="collection_authority_record", desc="Collection Authority Record PDA")] 703 | /// #[account(1, name="new_collection_authority", desc="A Collection Authority")] 704 | /// #[account(2, signer, name="update_authority", desc="Update Authority of Collection NFT")] 705 | /// #[account(3, signer, name="payer", desc="Payer")] 706 | /// #[account(4, name="metadata", desc="Collection Metadata account")] 707 | /// #[account(5, name="mint", desc="Mint of Collection Metadata")] 708 | /// #[account(6, name="system_program", desc="System program")] 709 | /// #[account(7, name="rent", desc="Rent info")] 710 | approve_collection_authority: void, 711 | /// Revoke account to call [verify_collection] on this NFT. 712 | /// 713 | /// #[account(0, writable, name="collection_authority_record", desc="Collection Authority Record PDA")] 714 | /// #[account(1, signer, name="update_authority", desc="Update Authority of Collection NFT")] 715 | /// #[account(2, name="metadata", desc="Metadata account")] 716 | /// #[account(3, name="mint", desc="Mint of Metadata")] 717 | revoke_collection_authority: void, 718 | /// Allows the same Update Authority (Or Delegated Authority) on an NFT and Collection to perform [update_metadata_accounts_v2] 719 | /// with collection and [verify_collection] on the NFT/Collection in one instruction. 720 | /// 721 | /// #[account(0, writable, name="metadata", desc="Metadata account")] 722 | /// #[account(1, signer, name="collection_authority", desc="Collection Update authority")] 723 | /// #[account(2, signer, name="payer", desc="Payer")] 724 | /// #[account(3, name="update_authority", desc="Update Authority of Collection NFT and NFT")] 725 | /// #[account(4, name="collection_mint", desc="Mint of the Collection")] 726 | /// #[account(5, name="collection", desc="Metadata Account of the Collection")] 727 | /// #[account(6, name="collection_master_edition_account", desc="MasterEdition2 Account of the Collection Token")] 728 | /// #[account(7, optional, name="collection_authority_record", desc="Collection Authority Record PDA")] 729 | set_and_verify_collection: void, 730 | /// Allow freezing of an NFT if this user is the delegate of the NFT. 731 | /// 732 | /// #[account(0, signer, name="delegate", desc="Delegate")] 733 | /// #[account(1, writable, name="token_account", desc="Token account to freeze")] 734 | /// #[account(2, name="edition", desc="Edition")] 735 | /// #[account(3, name="mint", desc="Token mint")] 736 | /// #[account(4, name="token_program", desc="Token Program")] 737 | freeze_delegated_account: void, 738 | /// Allow thawing of an NFT if this user is the delegate of the NFT. 739 | /// 740 | /// #[account(0, signer, name="delegate", desc="Delegate")] 741 | /// #[account(1, writable, name="token_account", desc="Token account to thaw")] 742 | /// #[account(2, name="edition", desc="Edition")] 743 | /// #[account(3, name="mint", desc="Token mint")] 744 | /// #[account(4, name="token_program", desc="Token Program")] 745 | thaw_delegated_account: void, 746 | /// Remove Creator Verificaton. 747 | /// 748 | /// #[account(0, writable, name="metadata", desc="Metadata (pda of ['metadata', program id, mint id])")] 749 | /// #[account(1, signer, name="creator", desc="Creator")] 750 | remove_creator_verification: void, 751 | 752 | /// Completely burn a NFT, including closing the metadata account. 753 | /// 754 | /// #[account(0, writable, name="metadata", desc="Metadata (pda of ['metadata', program id, mint id])")] 755 | /// #[account(1, signer, writable, name="owner", desc="NFT owner")] 756 | /// #[account(2, writable, name="mint", desc="Mint of the NFT")] 757 | /// #[account(3, writable, name="token_account", desc="Token account to close")] 758 | /// #[account(4, writable, name="master_edition_account", desc="MasterEdition2 of the NFT")] 759 | /// #[account(5, name="spl token program", desc="SPL Token Program")] 760 | /// #[account(6, optional, writable, name="collection_metadata", desc="Metadata of the Collection")] 761 | burn_nft, 762 | }; 763 | 764 | pub const Entity = union(enum(u8)) { 765 | uninitialized, 766 | edition_v1: Edition, 767 | master_edition_v1: MasterEditionV1, 768 | reservation_list_v1: ReservationListV1, 769 | metadata_v1: Metadata, 770 | reservation_list_v2: ReservationListV2, 771 | master_edition_v2: MasterEditionV2, 772 | edition_marker: EditionMarker, 773 | use_authority_record: UseAuthorityRecord, 774 | collection_authority_record: CollectionAuthorityRecord, 775 | }; 776 | 777 | pub const Data = struct { 778 | /// The name of the asset. 779 | name: []const u8, 780 | /// The symbol of the asset. 781 | symbol: []const u8, 782 | /// URI pointing to JSON representing the asset. 783 | uri: []const u8, 784 | /// Royalty basis points that goes to creators in secondary sales (0 - 10,000). 785 | seller_fee_basis_points: u16, 786 | /// Array of creators, optional. 787 | creators: ?[]Creator, 788 | }; 789 | 790 | pub const DataV2 = struct { 791 | /// The name of the asset. 792 | name: []const u8, 793 | /// The symbol of the asset. 794 | symbol: []const u8, 795 | /// URI pointing to JSON representing the asset. 796 | uri: []const u8, 797 | /// Royalty basis points that goes to creators in secondary sales (0 - 10,000). 798 | seller_fee_basis_points: u16, 799 | /// Array of creators, optional. 800 | creators: ?[]const Creator, 801 | /// The collection that this asset belongs to. 802 | collection: ?Collection, 803 | /// Uses for this asset. 804 | uses: ?Uses, 805 | 806 | pub fn setName(self: *DataV2, new_name: []const u8) void { 807 | const ptr = @intToPtr([*]u8, @ptrToInt(self.name.ptr)); 808 | for (new_name) |c, i| { 809 | ptr[i] = c; 810 | } 811 | if (new_name.len < self.name.len) { 812 | for (self.name[new_name.len..]) |_, i| { 813 | ptr[new_name.len + i] = 0; 814 | } 815 | } 816 | } 817 | 818 | pub fn setSymbol(self: *DataV2, new_symbol: []const u8) void { 819 | const ptr = @intToPtr([*]u8, @ptrToInt(self.symbol.ptr)); 820 | for (new_symbol) |c, i| { 821 | ptr[i] = c; 822 | } 823 | if (new_symbol.len < self.symbol.len) { 824 | for (self.symbol[new_symbol.len..]) |_, i| { 825 | ptr[new_symbol.len + i] = 0; 826 | } 827 | } 828 | } 829 | 830 | pub fn setUri(self: *DataV2, new_uri: []const u8) void { 831 | const ptr = @intToPtr([*]u8, @ptrToInt(self.uri.ptr)); 832 | for (new_uri) |c, i| { 833 | ptr[i] = c; 834 | } 835 | if (new_uri.len < self.uri.len) { 836 | for (self.uri[new_uri.len..]) |_, i| { 837 | ptr[new_uri.len + i] = 0; 838 | } 839 | } 840 | } 841 | 842 | pub fn setCreators(self: *DataV2, new_creators: []const Creator) void { 843 | const ptr = @intToPtr([*]Creator, @ptrToInt(self.creators.ptr)); 844 | for (new_creators) |c, i| ptr[i] = c; 845 | } 846 | 847 | pub fn toV1(self: DataV2) Data { 848 | return .{ 849 | .name = self.name, 850 | .symbol = self.symbol, 851 | .uri = self.uri, 852 | .seller_fee_basis_points = self.seller_fee_basis_points, 853 | .creators = self.creators, 854 | }; 855 | } 856 | }; 857 | 858 | pub const UseMethod = enum { 859 | burn, 860 | multiple, 861 | single, 862 | }; 863 | 864 | pub const Uses = struct { 865 | use_method: UseMethod, 866 | remaining: u64, 867 | total: u64, 868 | }; 869 | 870 | pub const TokenStandard = enum { 871 | /// This is a master edition. 872 | non_fungible, 873 | /// A token with metadata that can also have attributes. 874 | fungible_asset, 875 | /// A token with simple metadata. 876 | fungible, 877 | /// This is a limited edition. 878 | non_fungible_edition, 879 | }; 880 | 881 | pub const UseAuthorityRecord = struct { 882 | allowed_uses: u64, 883 | bump: u8, 884 | }; 885 | 886 | pub const CollectionAuthorityRecord = struct { 887 | bump: u8, 888 | }; 889 | 890 | pub const Collection = struct { 891 | verified: bool, 892 | id: sol.PublicKey, 893 | }; 894 | 895 | pub const Metadata = struct { 896 | update_authority_id: sol.PublicKey, 897 | mint_id: sol.PublicKey, 898 | data: Data, 899 | /// Immutable. Once flipped, all sales of this metadata are considered secondary. 900 | primary_sale_happened: bool, 901 | /// Whether or not `data` is mutable, default is not. 902 | is_mutable: bool, 903 | /// Nonce for easy calculation of editions, if present. 904 | edition_nonce: ?u8, 905 | /// Since we cannot easily change Metadata, we add the new DataV2 fields here at the end. 906 | token_standard: ?TokenStandard, 907 | /// The collection this metadata belongs to. 908 | collection: ?Collection, 909 | /// Uses for this asset. 910 | uses: ?Uses, 911 | }; 912 | 913 | pub const MasterEditionV2 = struct { 914 | supply: u64, 915 | max_supply: ?u64, 916 | }; 917 | 918 | pub const MasterEditionV1 = struct { 919 | supply: u64, 920 | max_supply: ?u64, 921 | /// Can be used to mint tokens that give one-time permission to mint a single limited edition. 922 | printing_mint_id: sol.PublicKey, 923 | 924 | /// If you don't know how many printing tokens you are going to need, but you do know 925 | /// you are going to need some amount in the future, you can use a token from this mint. 926 | /// Coming back to the token metadata program with one of these tokens allows you to mint (one time) 927 | /// any number of printing tokens you want. This is used for instance by the Auction Manager with 928 | /// participation NFTs, where we don't know how many people will bid and need participation 929 | /// because when the auction begins we just don't know how many printing tokens we will need, 930 | /// but at the end we will. At the end it then burns this token with its token metadata to get 931 | /// the printing tokens it needs to give to bidders. Each bidder then redeems a printing token 932 | /// to get their limited editions. 933 | one_time_printing_authorization_mint_id: sol.PublicKey, 934 | }; 935 | 936 | pub const Edition = struct { 937 | /// Points at the MasterEdition struct. 938 | parent_id: sol.PublicKey, 939 | /// Starting at 0 for the master record, this is incremented for each edition that is minted. 940 | edition: u64, 941 | }; 942 | 943 | pub const Creator = struct { 944 | id: sol.PublicKey, 945 | verified: bool, 946 | /// In percentages, NOT basis points. 947 | share: u8, 948 | }; 949 | 950 | pub const ReservationListV2 = struct { 951 | /// Present for reverse lookups. 952 | master_edition_id: sol.PublicKey, 953 | /// What supply counter was on MasterEdition when this reservation was created. 954 | supply_snapshot: ?u64, 955 | reservations: []const Reservation, 956 | /// How many reservations there are going to be, given on first set_reservation call. 957 | total_reservation_spots: u64, 958 | /// Cached count of reservation spots in the reservation vector to save on CPU. 959 | current_reservation_spots: u64, 960 | }; 961 | 962 | pub const Reservation = struct { 963 | id: sol.PublicKey, 964 | spots_remaining: u64, 965 | total_spots: u64, 966 | }; 967 | 968 | pub const ReservationListV1 = struct { 969 | /// Present for reverse lookups. 970 | master_edition_id: sol.PublicKey, 971 | supply_snapshot: ?u64, 972 | reservations: []const ReservationV1, 973 | }; 974 | 975 | pub const ReservationV1 = struct { 976 | id: sol.PublicKey, 977 | spots_remaining: u8, 978 | total_spots: u8, 979 | }; 980 | 981 | pub const EditionMarker = struct { 982 | ledger: [31]u8, 983 | }; 984 | --------------------------------------------------------------------------------