Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import software.sava.core.programs.Discriminator;
import software.sava.core.tx.Instruction;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -1377,7 +1380,175 @@ public static Instruction withdrawExcessLamports(final SolanaAccounts solanaAcco
signerAccounts
);
}
public static Instruction initializeMetadataPointer(final SolanaAccounts solanaAccounts,
final PublicKey mintAccount,
final PublicKey authority,
final PublicKey metadataAccount) {
return initializeMetadataPointer(solanaAccounts.invokedToken2022Program(), mintAccount,authority, metadataAccount);
}
public static Instruction initializeMetadataPointer(final AccountMeta invokedTokenProgram,
final PublicKey mintAccount,
final PublicKey authority,
final PublicKey metadataAccount) {
final var keys = List.of(createWrite(mintAccount));
byte[] data = new byte[1+1+32+32];
data[0] = (byte)TokenInstruction.MetadataPointerExtension.ordinal();
data[1] = (byte)0;

authority.write(data, 2);

metadataAccount.write(data, 34);

return createInstruction(invokedTokenProgram, keys, data);
}

public static Instruction updateMetadataPointer(final SolanaAccounts solanaAccounts,
final PublicKey mintAccount,
final PublicKey authority,
final PublicKey metadataAccount){
return updateMetadataPointer(solanaAccounts.invokedToken2022Program(),mintAccount,authority,metadataAccount);
}
public static Instruction updateMetadataPointer(final AccountMeta invokedTokenProgram,
final PublicKey mintAccount,
final PublicKey authority,
final PublicKey metadataAccount) {

final var keys = List.of(
AccountMeta.createWrite(mintAccount),
AccountMeta.createReadOnlySigner(authority)
);

byte[] data = new byte[1+1+32];
data[0] = (byte)TokenInstruction.MetadataPointerExtension.ordinal();
data[1] = (byte)1;

metadataAccount.write(data, 2);

return createInstruction(
invokedTokenProgram,
keys,
data
);
}

public static Instruction initializeTokenMetadataInstruction(
final SolanaAccounts solanaAccounts,
final PublicKey metadataAccount,
final PublicKey updateAuthority,
final PublicKey mintAuthority,
final PublicKey mintAccount,
final String name,
final String symbol,
final String uri
) {
final var keys = List.of(
AccountMeta.createWrite(metadataAccount),
AccountMeta.createMeta(updateAuthority, false, false),
AccountMeta.createMeta(mintAccount, false, false),
AccountMeta.createMeta(mintAuthority, false, true)
);

byte[] data = buildInitializeTokenMetadataData(name, symbol, uri);

return createInstruction(
solanaAccounts.invokedToken2022Program(),
keys,
data
);
}

private static byte[] buildInitializeTokenMetadataData(
String name,
String symbol,
String uri) {

byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
byte[] symbolBytes = symbol.getBytes(StandardCharsets.UTF_8);
byte[] uriBytes = uri.getBytes(StandardCharsets.UTF_8);

byte[] discriminator = new byte[]{
(byte) 0xD2, (byte) 0xE1, (byte) 0x1E, (byte) 0xA2,
(byte) 0x58, (byte) 0xB8, (byte) 0x4D, (byte) 0x8D
};

int dataSize = discriminator.length
+ Integer.BYTES + nameBytes.length
+ Integer.BYTES + symbolBytes.length
+ Integer.BYTES + uriBytes.length;

byte[] data = new byte[dataSize];
int offset = 0;

System.arraycopy(discriminator, 0, data, offset, discriminator.length);
offset += discriminator.length;

ByteUtil.putInt32LE(data, offset, nameBytes.length);
offset += Integer.BYTES;
System.arraycopy(nameBytes, 0, data, offset, nameBytes.length);
offset += nameBytes.length;

ByteUtil.putInt32LE(data, offset, symbolBytes.length);
offset += Integer.BYTES;
System.arraycopy(symbolBytes, 0, data, offset, symbolBytes.length);
offset += symbolBytes.length;

ByteUtil.putInt32LE(data, offset, uriBytes.length);
offset += Integer.BYTES;
System.arraycopy(uriBytes, 0, data, offset, uriBytes.length);

return data;
}

public static Instruction initializeTransferHook(final SolanaAccounts solanaAccounts,
final PublicKey mintAccount,
final PublicKey authority,
final PublicKey programAccount) {
return initializeTransferHook(solanaAccounts.invokedToken2022Program(), mintAccount,authority, programAccount);
}
public static Instruction initializeTransferHook(final AccountMeta invokedTokenProgram,
final PublicKey mintAccount,
final PublicKey authority,
final PublicKey programAccount) {
final var keys = List.of(AccountMeta.createWrite(mintAccount));
byte[] data = new byte[1+1+32+32];
data[0] = (byte)TokenInstruction.TransferHookExtension.ordinal();
data[1] = (byte)0;

authority.write(data, 2);
programAccount.write(data,34);
return createInstruction(invokedTokenProgram, keys, data);
}

public static Instruction updateTransferHook(final SolanaAccounts solanaAccounts,
final PublicKey mintAccount,
final PublicKey authority,
final PublicKey programAccount){
return updateTransferHook(solanaAccounts.invokedToken2022Program(),mintAccount,authority,programAccount);
}
public static Instruction updateTransferHook(
final AccountMeta invokedTokenProgram,
final PublicKey mintAccount,
final PublicKey authority,
final PublicKey programAccount) {


final var keys = List.of(
AccountMeta.createWrite(mintAccount),
AccountMeta.createReadOnlySigner(authority)
);

byte[] data = new byte[1+1+32];
data[0] = (byte)TokenInstruction.TransferHookExtension.ordinal();
data[1] = (byte)1;

programAccount.write(data, 2);

return createInstruction(
invokedTokenProgram,
keys,
data
);
}
private Token2022Program() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import software.sava.core.accounts.PublicKey;
import software.sava.core.accounts.SolanaAccounts;
import software.sava.core.accounts.meta.AccountMeta;
import software.sava.core.encoding.Base58;
import software.sava.solana.programs.token.Token2022Program;
import software.sava.solana.programs.token.TokenProgram;

Expand Down Expand Up @@ -94,4 +95,166 @@ void initializeMint() {

assertArrayEquals(expectedData, initMintIx.data());
}

@Test
void createMintWithTransferHook() {
// devnet 3b7rYDCdxymqBXR3FLFgUtUoQyoYGg2ZF2bbKviuSQToSWyZeJmfb2wpjpTsZRf8FCWVMtuNetTAz2EAvmRSZLUi

final var mintAccount = PublicKey.fromBase58Encoded("88WLQK58mbqNjaUBxYjEvhvdsWGQde4s1EqyagvEng2f");
final var mintAuthority = PublicKey.fromBase58Encoded("CvUqgjP892h66aYPC9E8gKTXnTebY8qaU5ehGrgEQSwV");
final var programAccount = PublicKey.fromBase58Encoded("2o6gvxp17hkML8Rz3cvqzbSTFStES287fYeDPeHhF7Vj");

final byte[] expectedData = Base58.decode("""
F2LRfuZ8F9SkUvoY2DcGDFXNksBcb4d4UbpQyYcyRZjde31xioLJauJKYwRxjEjAuzMNPepJSHH3njMhSzFgvdM4Gy""".stripTrailing());


final var solAccounts = SolanaAccounts.MAIN_NET;
var initializeTransferHookIx = Token2022Program.initializeTransferHook(
solAccounts,
mintAccount,
mintAuthority,
programAccount
);

assertEquals(solAccounts.invokedToken2022Program(), initializeTransferHookIx.programId());

var accounts = initializeTransferHookIx.accounts();
assertEquals(1, accounts.size());
assertEquals(AccountMeta.createWrite(mintAccount), accounts.getFirst());


assertArrayEquals(expectedData, initializeTransferHookIx.data());

}

@Test
void createMintWithMetadataPointer() {
// devnet 3b7rYDCdxymqBXR3FLFgUtUoQyoYGg2ZF2bbKviuSQToSWyZeJmfb2wpjpTsZRf8FCWVMtuNetTAz2EAvmRSZLUi

final var mint = PublicKey.fromBase58Encoded("88WLQK58mbqNjaUBxYjEvhvdsWGQde4s1EqyagvEng2f");
final var authority = PublicKey.fromBase58Encoded("CvUqgjP892h66aYPC9E8gKTXnTebY8qaU5ehGrgEQSwV");

final byte[] expectedData = Base58.decode("""
GC7FSeyRsRSWqdePvGFp5oZSbvCin5dinmBb7X5fn9DzNcfCdmyXiTV9iEzEZRrkmv3ixyvggyPXnUNyTekHbNx3Ph""".stripTrailing());


final var solAccounts = SolanaAccounts.MAIN_NET;
var initMetadataPointerIx = Token2022Program.initializeMetadataPointer(
solAccounts,
mint,
authority,
mint
);

assertEquals(solAccounts.invokedToken2022Program(), initMetadataPointerIx.programId());

var accounts = initMetadataPointerIx.accounts();
assertEquals(1, accounts.size());
assertEquals(AccountMeta.createWrite(mint), accounts.getFirst());

assertArrayEquals(expectedData, initMetadataPointerIx.data());

}
@Test
void createMintWithInitializingMetadata() {
// devnet 3b7rYDCdxymqBXR3FLFgUtUoQyoYGg2ZF2bbKviuSQToSWyZeJmfb2wpjpTsZRf8FCWVMtuNetTAz2EAvmRSZLUi

final var name = "SimpleTestCoin";
final var symbol = "STC";
final var uri = "https://example.com/metadata.json";
final var mintAccount = PublicKey.fromBase58Encoded("88WLQK58mbqNjaUBxYjEvhvdsWGQde4s1EqyagvEng2f");
final var metadataAccount = PublicKey.fromBase58Encoded("88WLQK58mbqNjaUBxYjEvhvdsWGQde4s1EqyagvEng2f");
final var mintAuthority = PublicKey.fromBase58Encoded("CvUqgjP892h66aYPC9E8gKTXnTebY8qaU5ehGrgEQSwV");
final var updateAuthority = PublicKey.fromBase58Encoded("CvUqgjP892h66aYPC9E8gKTXnTebY8qaU5ehGrgEQSwV");

final byte[] expectedData = Base58.decode("""
AGUhRKBLRk1Ueut5CpnmUkTSsn2Fpg3v3un8sZ52wUqQh5ZvW8ots8FCc3MtpVSzANADodfMKGeGjhkTv59ziTJPF3XZ9LnH""".stripTrailing());


final var solAccounts = SolanaAccounts.MAIN_NET;
var initializeTokenMetadataIx = Token2022Program.initializeTokenMetadataInstruction(
solAccounts,
metadataAccount,
mintAuthority,
updateAuthority,
mintAccount,
name,
symbol,
uri
);

assertEquals(solAccounts.invokedToken2022Program(), initializeTokenMetadataIx.programId());

var accounts = initializeTokenMetadataIx.accounts();
assertEquals(4, accounts.size());
assertEquals(AccountMeta.createWrite(metadataAccount), accounts.getFirst());
assertEquals(AccountMeta.createRead(updateAuthority), accounts.get(1));
assertEquals(AccountMeta.createRead(mintAccount), accounts.get(2));
assertEquals(AccountMeta.createReadOnlySigner(mintAuthority), accounts.get(3));

assertArrayEquals(expectedData, initializeTokenMetadataIx.data());

}

@Test
void updateTransferHookAccount() {
// devnet THZ3HTPAQZaEj6ggHSaLSxSS5CeGYp88VDa6NyXxY7pV9khHk1xJk1yHqP4jWByHjBUz34UuWLPffWQfeCzjNyi

final var mintAccount = PublicKey.fromBase58Encoded("HCRDkSQ6vM9QxDkJMGNUmVKjWqPYudkEsZRDwoJvyzQE");
final var mintAuthority = PublicKey.fromBase58Encoded("CvUqgjP892h66aYPC9E8gKTXnTebY8qaU5ehGrgEQSwV");
final var newTransferHookProgramId = PublicKey.fromBase58Encoded("7cjXTZvHYGuFarmmsYqjXsyYZY5TMyeNmvidxPJfvQ1Q");

final byte[] expectedData = Base58.decode("""
pD8q5bQ9YX5HQ6qxGodu61fJtfqPFHjAKoTLes7gCwnme2""".stripTrailing());


final var solAccounts = SolanaAccounts.MAIN_NET;
var updateTransferHookIx = Token2022Program.updateTransferHook(
solAccounts,
mintAccount,
mintAuthority,
newTransferHookProgramId
);

assertEquals(solAccounts.invokedToken2022Program(), updateTransferHookIx.programId());

var accounts = updateTransferHookIx.accounts();
assertEquals(2, accounts.size());
assertEquals(AccountMeta.createWrite(mintAccount), accounts.getFirst());
assertEquals(AccountMeta.createReadOnlySigner(mintAuthority), accounts.getLast());

assertArrayEquals(expectedData, updateTransferHookIx.data());

}
@Test
void updateMintMetadataAccount() {
// devnet 3iKA2XCusAq2uCxuGyWhw8oBkdYQMQMq87t5sJTXJpCcD169NDzDjBE3fcuTv6Dg8QpjC4QNmwxZXFhSB8DLZkj2

final var mintAccount = PublicKey.fromBase58Encoded("HCRDkSQ6vM9QxDkJMGNUmVKjWqPYudkEsZRDwoJvyzQE");
final var mintAuthority = PublicKey.fromBase58Encoded("CvUqgjP892h66aYPC9E8gKTXnTebY8qaU5ehGrgEQSwV");
final var newMetadataAddress = PublicKey.fromBase58Encoded("AsFagyk29GvS8dtibZ6vjtbfwjnMzn9xHcEzoAnRusCB");

final byte[] expectedData = Base58.decode("""
t9LQrHqNgyQXjTT4nYpfgsSqXM8sAQVo3zQ8kxyueAPmrf""".stripTrailing());


final var solAccounts = SolanaAccounts.MAIN_NET;
var updateMetadataPointerIx = Token2022Program.updateMetadataPointer(
solAccounts,
mintAccount,
mintAuthority,
newMetadataAddress
);

assertEquals(solAccounts.invokedToken2022Program(), updateMetadataPointerIx.programId());

var accounts = updateMetadataPointerIx.accounts();
assertEquals(2, accounts.size());
assertEquals(AccountMeta.createWrite(mintAccount), accounts.getFirst());
assertEquals(AccountMeta.createReadOnlySigner(mintAuthority), accounts.getLast());

assertArrayEquals(expectedData, updateMetadataPointerIx.data());

}

}