diff --git a/src/main/java/com/lmax/solana4j/Solana.java b/src/main/java/com/lmax/solana4j/Solana.java index 93a0b566..2a16dfb2 100644 --- a/src/main/java/com/lmax/solana4j/Solana.java +++ b/src/main/java/com/lmax/solana4j/Solana.java @@ -43,7 +43,7 @@ public static PublicKey account(final byte[] bytes) public static ProgramDerivedAddress programDerivedAddress(final PublicKey owner, final PublicKey programId) { - return SolanaEncoding.programDerivedAddress(owner, programId); + return SolanaEncoding.deriveProgramAddress(owner, programId); } public static AssociatedTokenAddress associatedTokenAddress(final PublicKey owner, final PublicKey mint, final PublicKey tokenProgramAccount) diff --git a/src/main/java/com/lmax/solana4j/api/AssociatedTokenAddress.java b/src/main/java/com/lmax/solana4j/api/AssociatedTokenAddress.java index ff056f6c..bb176974 100644 --- a/src/main/java/com/lmax/solana4j/api/AssociatedTokenAddress.java +++ b/src/main/java/com/lmax/solana4j/api/AssociatedTokenAddress.java @@ -3,4 +3,5 @@ public interface AssociatedTokenAddress extends ProgramDerivedAddress { PublicKey mint(); + PublicKey owner(); } diff --git a/src/main/java/com/lmax/solana4j/api/ProgramDerivedAddress.java b/src/main/java/com/lmax/solana4j/api/ProgramDerivedAddress.java index b56a7287..eab793c0 100644 --- a/src/main/java/com/lmax/solana4j/api/ProgramDerivedAddress.java +++ b/src/main/java/com/lmax/solana4j/api/ProgramDerivedAddress.java @@ -4,8 +4,6 @@ public interface ProgramDerivedAddress { PublicKey address(); - PublicKey owner(); - PublicKey programId(); int nonce(); diff --git a/src/main/java/com/lmax/solana4j/api/PublicKey.java b/src/main/java/com/lmax/solana4j/api/PublicKey.java index dd24feb3..274e1681 100644 --- a/src/main/java/com/lmax/solana4j/api/PublicKey.java +++ b/src/main/java/com/lmax/solana4j/api/PublicKey.java @@ -8,6 +8,7 @@ public interface PublicKey int PUBLIC_KEY_LENGTH = 32; String base58(); + byte[] bytes(); void write(ByteBuffer buffer); } diff --git a/src/main/java/com/lmax/solana4j/api/Slot.java b/src/main/java/com/lmax/solana4j/api/Slot.java index 9db3f55b..e7f4bd1f 100644 --- a/src/main/java/com/lmax/solana4j/api/Slot.java +++ b/src/main/java/com/lmax/solana4j/api/Slot.java @@ -5,4 +5,5 @@ public interface Slot { void write(ByteBuffer buffer); + byte[] bytes(); } diff --git a/src/main/java/com/lmax/solana4j/encoding/SolanaAccount.java b/src/main/java/com/lmax/solana4j/encoding/SolanaAccount.java index 3a036d6d..c73fbd6a 100644 --- a/src/main/java/com/lmax/solana4j/encoding/SolanaAccount.java +++ b/src/main/java/com/lmax/solana4j/encoding/SolanaAccount.java @@ -28,6 +28,12 @@ public String base58() return Base58.encode(bytes); } + @Override + public byte[] bytes() + { + return bytes; + } + @Override public void write(final ByteBuffer buffer) { diff --git a/src/main/java/com/lmax/solana4j/programs/SolanaAddressLookupTable.java b/src/main/java/com/lmax/solana4j/encoding/SolanaAddressLookupTable.java similarity index 95% rename from src/main/java/com/lmax/solana4j/programs/SolanaAddressLookupTable.java rename to src/main/java/com/lmax/solana4j/encoding/SolanaAddressLookupTable.java index 0231d2ab..0b79671c 100644 --- a/src/main/java/com/lmax/solana4j/programs/SolanaAddressLookupTable.java +++ b/src/main/java/com/lmax/solana4j/encoding/SolanaAddressLookupTable.java @@ -1,4 +1,4 @@ -package com.lmax.solana4j.programs; +package com.lmax.solana4j.encoding; import com.lmax.solana4j.api.AddressLookupTable; import com.lmax.solana4j.api.PublicKey; diff --git a/src/main/java/com/lmax/solana4j/encoding/SolanaAssociatedTokenAddress.java b/src/main/java/com/lmax/solana4j/encoding/SolanaAssociatedTokenAddress.java index a095e25d..4aad1f36 100644 --- a/src/main/java/com/lmax/solana4j/encoding/SolanaAssociatedTokenAddress.java +++ b/src/main/java/com/lmax/solana4j/encoding/SolanaAssociatedTokenAddress.java @@ -1,58 +1,45 @@ package com.lmax.solana4j.encoding; import com.lmax.solana4j.api.AssociatedTokenAddress; +import com.lmax.solana4j.api.ProgramDerivedAddress; import com.lmax.solana4j.api.PublicKey; -import net.i2p.crypto.eddsa.math.Curve; -import java.nio.ByteBuffer; +import java.util.List; import java.util.Objects; -import static com.lmax.solana4j.programs.AssociatedTokenProgram.ASSOCIATED_TOKEN_PROGRAM_ACCOUNT; +import static com.lmax.solana4j.programs.AssociatedTokenMetadataProgram.ASSOCIATED_TOKEN_PROGRAM_ACCOUNT; import static java.util.Objects.requireNonNull; -import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.ED_25519_CURVE_SPEC; final class SolanaAssociatedTokenAddress extends SolanaProgramDerivedAddress implements AssociatedTokenAddress { private final PublicKey mint; + private final PublicKey owner; public static SolanaAssociatedTokenAddress deriveAssociatedTokenAddress(final PublicKey owner, final PublicKey mint, final PublicKey tokenProgramAccount) { - final Curve curve = ED_25519_CURVE_SPEC.getCurve(); - int bumpSeed = 255; - - byte[] programAddress = new byte[32]; - try - { - while (bumpSeed > 0) - { - final ByteBuffer seeds = ByteBuffer.allocate(197); - owner.write(seeds); - tokenProgramAccount.write(seeds); - mint.write(seeds); - seeds.put(getNextSeed(bumpSeed)); - - final ByteBuffer programAccountBuf = ByteBuffer.allocate(32); - ASSOCIATED_TOKEN_PROGRAM_ACCOUNT.write(programAccountBuf); - - programAddress = createProgramAddress(seeds, programAccountBuf); - curve.createPoint(programAddress, false); - bumpSeed--; - } - } - catch (final RuntimeException re) - { - return new SolanaAssociatedTokenAddress(new SolanaAccount(programAddress), owner, mint, tokenProgramAccount, bumpSeed); - } - - throw new RuntimeException("Could not find a program address off the curve."); + final ProgramDerivedAddress programDerivedAddress = + SolanaProgramDerivedAddress.deriveProgramAddress(List.of(owner.bytes(), tokenProgramAccount.bytes(), mint.bytes()), ASSOCIATED_TOKEN_PROGRAM_ACCOUNT); + return new SolanaAssociatedTokenAddress(new SolanaAccount(programDerivedAddress.address().bytes()), owner, mint, tokenProgramAccount, programDerivedAddress.nonce()); } SolanaAssociatedTokenAddress(final PublicKey address, final PublicKey owner, final PublicKey mint, final PublicKey programAccount, final int nonce) { - super(address, owner, programAccount, nonce); - + super(address, programAccount, nonce); this.mint = requireNonNull(mint, "The mint public key must be specified, but was null"); + this.owner = requireNonNull(owner, "The owner public key must be specified, but was null"); + } + + @Override + public PublicKey mint() + { + return mint; + } + + @Override + public PublicKey owner() + { + return owner; } @Override @@ -71,30 +58,24 @@ public boolean equals(final Object o) return false; } final SolanaAssociatedTokenAddress that = (SolanaAssociatedTokenAddress) o; - return Objects.equals(mint, that.mint); + return Objects.equals(mint, that.mint) && Objects.equals(owner, that.owner); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), mint); + return Objects.hash(super.hashCode(), mint, owner); } @Override public String toString() { return "SolanaAssociatedTokenAddress{" + - "mint=" + mint + - ", address=" + address + - ", owner=" + owner + - ", programId=" + programAccount + - ", nonce=" + nonce + - '}'; - } - - @Override - public PublicKey mint() - { - return mint; + "mint=" + mint + + ", address=" + address + + ", owner=" + owner + + ", programId=" + programAccount + + ", nonce=" + nonce + + '}'; } } diff --git a/src/main/java/com/lmax/solana4j/encoding/SolanaEncoding.java b/src/main/java/com/lmax/solana4j/encoding/SolanaEncoding.java index 71f8bbb2..f7dadb2b 100644 --- a/src/main/java/com/lmax/solana4j/encoding/SolanaEncoding.java +++ b/src/main/java/com/lmax/solana4j/encoding/SolanaEncoding.java @@ -1,5 +1,6 @@ package com.lmax.solana4j.encoding; +import com.lmax.solana4j.api.AddressLookupTable; import com.lmax.solana4j.api.AssociatedTokenAddress; import com.lmax.solana4j.api.Blockhash; import com.lmax.solana4j.api.Destination; @@ -12,6 +13,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; public final class SolanaEncoding { @@ -38,11 +40,16 @@ public static PublicKey account(final byte[] bytes) return new SolanaAccount(bytes); } - public static ProgramDerivedAddress programDerivedAddress(final PublicKey owner, final PublicKey programId) + public static ProgramDerivedAddress deriveProgramAddress(final PublicKey owner, final PublicKey programId) { return SolanaProgramDerivedAddress.deriveProgramAddress(owner, programId); } + public static ProgramDerivedAddress deriveProgramAddress(final List seeds, final PublicKey programId) + { + return SolanaProgramDerivedAddress.deriveProgramAddress(seeds, programId); + } + public static AssociatedTokenAddress associatedTokenAddress(final PublicKey owner, final PublicKey mint, final PublicKey tokenProgramAccount) { return SolanaAssociatedTokenAddress.deriveAssociatedTokenAddress(owner, mint, tokenProgramAccount); @@ -66,4 +73,9 @@ public static Destination destination(final PublicKey destination, final long am { return new SolanaDestination(destination, amount); } + + public static AddressLookupTable addressLookupTable(final PublicKey lookupTableAddress, final List addressLookups) + { + return new SolanaAddressLookupTable(lookupTableAddress, addressLookups); + } } diff --git a/src/main/java/com/lmax/solana4j/encoding/SolanaProgramDerivedAddress.java b/src/main/java/com/lmax/solana4j/encoding/SolanaProgramDerivedAddress.java index 2d87f870..c1b720dd 100644 --- a/src/main/java/com/lmax/solana4j/encoding/SolanaProgramDerivedAddress.java +++ b/src/main/java/com/lmax/solana4j/encoding/SolanaProgramDerivedAddress.java @@ -2,80 +2,76 @@ import com.lmax.solana4j.api.ProgramDerivedAddress; import com.lmax.solana4j.api.PublicKey; -import net.i2p.crypto.eddsa.math.Curve; +import net.i2p.crypto.eddsa.math.GroupElement; import org.bitcoinj.core.Sha256Hash; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; +import java.util.List; import java.util.Objects; +import static com.lmax.solana4j.api.PublicKey.PUBLIC_KEY_LENGTH; import static java.util.Objects.requireNonNull; import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.ED_25519_CURVE_SPEC; class SolanaProgramDerivedAddress implements ProgramDerivedAddress { + public static final byte[] PROGRAM_DERIVED_ADDRESS_BYTES = "ProgramDerivedAddress".getBytes(StandardCharsets.UTF_8); + private static final int BUMP_LENGTH = 1; + final PublicKey address; - final PublicKey owner; final PublicKey programAccount; final int nonce; - // programId is just the Solana term for the public key of the program account - public static ProgramDerivedAddress deriveProgramAddress(final PublicKey owner, final PublicKey programId) + public static ProgramDerivedAddress deriveProgramAddress(final List seeds, final PublicKey programId) { - final Curve curve = ED_25519_CURVE_SPEC.getCurve(); - int bumpSeed = 255; + final int seedLength = seeds.stream().mapToInt(seed -> seed.length).sum(); + final int byteLength = seedLength + PUBLIC_KEY_LENGTH + PROGRAM_DERIVED_ADDRESS_BYTES.length + BUMP_LENGTH; - byte[] programAddress = new byte[32]; - try + int bumpSeed = 255; + while (bumpSeed > 0) { - while (bumpSeed > 0) - { - final ByteBuffer seeds = ByteBuffer.allocate(33); - owner.write(seeds); - seeds.put(getNextSeed(bumpSeed)); + final ByteBuffer seedsBuffer = ByteBuffer.allocate(byteLength); + seeds.forEach(seedsBuffer::put); + seedsBuffer.put(new byte[]{(byte) bumpSeed}); + + programId.write(seedsBuffer); + seedsBuffer.put(PROGRAM_DERIVED_ADDRESS_BYTES); - final ByteBuffer programIdBuf = ByteBuffer.allocate(32); - programId.write(programIdBuf); + final byte[] programAddress = Sha256Hash.hash(seedsBuffer.array()); - programAddress = createProgramAddress(seeds, programIdBuf); - curve.createPoint(programAddress, false); - bumpSeed--; + if (isOffCurve(programAddress)) + { + return new SolanaProgramDerivedAddress(new SolanaAccount(programAddress), programId, bumpSeed); } + bumpSeed--; } - catch (final RuntimeException re) - { - return new SolanaProgramDerivedAddress(new SolanaAccount(programAddress), owner, programId, bumpSeed); - } - throw new RuntimeException("Could not find a program address off the curve."); } - /* protected */ static byte[] createProgramAddress(final ByteBuffer seeds, final ByteBuffer programAccount) + private static boolean isOffCurve(final byte[] programAddress) { - final MessageDigest messageDigest = Sha256Hash.newDigest(); - - messageDigest.update(seeds.flip()); - messageDigest.update(programAccount.flip()); - messageDigest.update("ProgramDerivedAddress".getBytes(StandardCharsets.UTF_8)); - - return messageDigest.digest(); + //is this the best way? + try + { + final GroupElement point = ED_25519_CURVE_SPEC.getCurve().createPoint(programAddress, false); + return !point.isOnCurve(); + } + catch (final IllegalArgumentException e) + { + return true; + } } - /* protected */ static byte[] getNextSeed(final int currentSignedByteValue) + // programId is just the Solana term for the public key of the program account + public static ProgramDerivedAddress deriveProgramAddress(final PublicKey owner, final PublicKey programId) { - final byte[] seed = new byte[1]; - - seed[0] = (byte) (currentSignedByteValue); - - return seed; + return deriveProgramAddress(List.of(owner.bytes()), programId); } - - SolanaProgramDerivedAddress(final PublicKey address, final PublicKey owner, final PublicKey programAccount, final int nonce) + SolanaProgramDerivedAddress(final PublicKey address, final PublicKey programAccount, final int nonce) { this.address = requireNonNull(address, "The address public key must be specified, but was null"); - this.owner = requireNonNull(owner, "The owner public key must be specified, but was null"); this.programAccount = requireNonNull(programAccount, "The programId public key must be specified, but was null"); this.nonce = nonce; } @@ -92,24 +88,23 @@ public boolean equals(final Object o) return false; } final SolanaProgramDerivedAddress that = (SolanaProgramDerivedAddress) o; - return nonce == that.nonce && Objects.equals(address, that.address) && Objects.equals(owner, that.owner) && Objects.equals(programAccount, that.programAccount); + return nonce == that.nonce && Objects.equals(address, that.address) && Objects.equals(programAccount, that.programAccount); } @Override public int hashCode() { - return Objects.hash(address, owner, programAccount, nonce); + return Objects.hash(address, programAccount, nonce); } @Override public String toString() { return "SolanaProgramDerivedAccount{" + - "address=" + address + - ", owner=" + owner + - ", programAccount=" + programAccount + - ", nonce=" + nonce + - '}'; + "address=" + address + + ", programAccount=" + programAccount + + ", nonce=" + nonce + + '}'; } @Override @@ -118,12 +113,6 @@ public PublicKey address() return address; } - @Override - public PublicKey owner() - { - return owner; - } - @Override public PublicKey programId() { diff --git a/src/main/java/com/lmax/solana4j/encoding/SolanaSlot.java b/src/main/java/com/lmax/solana4j/encoding/SolanaSlot.java index ddda221e..9b1188fe 100644 --- a/src/main/java/com/lmax/solana4j/encoding/SolanaSlot.java +++ b/src/main/java/com/lmax/solana4j/encoding/SolanaSlot.java @@ -27,6 +27,12 @@ public void write(final ByteBuffer buffer) buffer.put(bytes); } + @Override + public byte[] bytes() + { + return bytes; + } + @Override public boolean equals(final Object o) { diff --git a/src/main/java/com/lmax/solana4j/programs/SysVar.java b/src/main/java/com/lmax/solana4j/encoding/SysVar.java similarity index 97% rename from src/main/java/com/lmax/solana4j/programs/SysVar.java rename to src/main/java/com/lmax/solana4j/encoding/SysVar.java index 983fc8a6..be3eefe0 100644 --- a/src/main/java/com/lmax/solana4j/programs/SysVar.java +++ b/src/main/java/com/lmax/solana4j/encoding/SysVar.java @@ -1,4 +1,4 @@ -package com.lmax.solana4j.programs; +package com.lmax.solana4j.encoding; import com.lmax.solana4j.Solana; import com.lmax.solana4j.api.PublicKey; diff --git a/src/main/java/com/lmax/solana4j/programs/TokenMetadata.java b/src/main/java/com/lmax/solana4j/encoding/TokenMetadata.java similarity index 89% rename from src/main/java/com/lmax/solana4j/programs/TokenMetadata.java rename to src/main/java/com/lmax/solana4j/encoding/TokenMetadata.java index 3e5aefcc..9d9b53eb 100644 --- a/src/main/java/com/lmax/solana4j/programs/TokenMetadata.java +++ b/src/main/java/com/lmax/solana4j/encoding/TokenMetadata.java @@ -1,4 +1,4 @@ -package com.lmax.solana4j.programs; +package com.lmax.solana4j.encoding; public class TokenMetadata { diff --git a/src/main/java/com/lmax/solana4j/programs/AddressLookupTableProgram.java b/src/main/java/com/lmax/solana4j/programs/AddressLookupTableProgram.java index c5282451..ae80f716 100644 --- a/src/main/java/com/lmax/solana4j/programs/AddressLookupTableProgram.java +++ b/src/main/java/com/lmax/solana4j/programs/AddressLookupTableProgram.java @@ -2,22 +2,18 @@ import com.lmax.solana4j.Solana; import com.lmax.solana4j.api.AddressLookupTable; +import com.lmax.solana4j.api.ProgramDerivedAddress; import com.lmax.solana4j.api.PublicKey; import com.lmax.solana4j.api.Slot; import com.lmax.solana4j.api.TransactionBuilderBase; -import net.i2p.crypto.eddsa.math.Curve; +import com.lmax.solana4j.encoding.SolanaEncoding; import org.bitcoinj.core.Base58; -import org.bitcoinj.core.Sha256Hash; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; import java.util.ArrayList; import java.util.List; -import static net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable.ED_25519_CURVE_SPEC; - public final class AddressLookupTableProgram { private static final byte[] ADDRESS_LOOKUP_TABLE_PROGRAM = Base58.decode("AddressLookupTab1e1111111111111111111111111"); @@ -40,11 +36,11 @@ private AddressLookupTableProgram(final TransactionBuilderBase tb) this.tb = tb; } - public void createAddressLookupTableInstruction(final AddressWithBumpSeed addressWithBumpSeed, final PublicKey authority, final PublicKey payer, final Slot recentSlot) + public void createAddressLookupTableInstruction(final ProgramDerivedAddress programDerivedAddress, final PublicKey authority, final PublicKey payer, final Slot recentSlot) { tb.append(ib -> ib .program(PROGRAM_ACCOUNT) - .account(Solana.account(addressWithBumpSeed.getLookupTableAddress()), false, true) + .account(programDerivedAddress.address(), false, true) .account(authority, true, false) .account(payer, true, false) .account(SystemProgram.SYSTEM_PROGRAM_ACCOUNT, false, false) @@ -52,7 +48,7 @@ public void createAddressLookupTableInstruction(final AddressWithBumpSeed addres { bb.order(ByteOrder.LITTLE_ENDIAN).putInt(CREATE_LOOKUP_TABLE_INSTRUCTION); recentSlot.write(bb); - bb.put((byte) addressWithBumpSeed.getBumpSeed()); + bb.put((byte) programDerivedAddress.nonce()); } ) ); @@ -98,65 +94,6 @@ public static AddressLookupTable deserializeAddressLookupTable(final PublicKey l addresses.add(address); } - return new SolanaAddressLookupTable(lookupTableAddress, addresses); - } - - - public static AddressWithBumpSeed deriveLookupTableAddress(final PublicKey authority, final Slot slot) - { - final Curve curve = ED_25519_CURVE_SPEC.getCurve(); - int bumpSeed = 255; - - byte[] programAddress = new byte[32]; - try - { - while (bumpSeed > 0) - { - final ByteBuffer slotBuffer = ByteBuffer.allocate(8); - slotBuffer.order(ByteOrder.LITTLE_ENDIAN); - slot.write(slotBuffer); - - final ByteBuffer seeds = ByteBuffer.allocate(41); - authority.write(seeds); - seeds.order(ByteOrder.LITTLE_ENDIAN); - seeds.put(slotBuffer.flip()); - - seeds.put(getNextSeed(bumpSeed)); - - final ByteBuffer programId = ByteBuffer.allocate(32); - PROGRAM_ACCOUNT.write(programId); - - programAddress = createProgramAddress(seeds, programId); - curve.createPoint(programAddress, false); - bumpSeed--; - } - } - catch (final RuntimeException re) - { - return new AddressWithBumpSeed(programAddress, bumpSeed); - } - - throw new RuntimeException("Could not find a program address off the curve."); + return SolanaEncoding.addressLookupTable(lookupTableAddress, addresses); } - - private static byte[] createProgramAddress(final ByteBuffer seeds, final ByteBuffer programId) - { - final MessageDigest messageDigest = Sha256Hash.newDigest(); - - messageDigest.update(seeds.flip()); - messageDigest.update(programId.flip()); - messageDigest.update("ProgramDerivedAddress".getBytes(StandardCharsets.UTF_8)); - - return messageDigest.digest(); - } - - private static byte[] getNextSeed(final int currentSignedByteValue) - { - final byte[] seed = new byte[1]; - - seed[0] = (byte) (currentSignedByteValue); - - return seed; - } - } diff --git a/src/main/java/com/lmax/solana4j/programs/AddressWithBumpSeed.java b/src/main/java/com/lmax/solana4j/programs/AddressWithBumpSeed.java deleted file mode 100644 index 9883d112..00000000 --- a/src/main/java/com/lmax/solana4j/programs/AddressWithBumpSeed.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lmax.solana4j.programs; - -public final class AddressWithBumpSeed -{ - private final byte[] lookupTableAddress; - private final int bumpSeed; - - AddressWithBumpSeed(final byte[] lookupTableAddress, final int bumpSeed) - { - this.lookupTableAddress = lookupTableAddress; - this.bumpSeed = bumpSeed; - } - - public byte[] getLookupTableAddress() - { - return lookupTableAddress; - } - - public int getBumpSeed() - { - return bumpSeed; - } -} diff --git a/src/main/java/com/lmax/solana4j/programs/AssociatedTokenMetadataProgram.java b/src/main/java/com/lmax/solana4j/programs/AssociatedTokenMetadataProgram.java index 5a9bf169..8e768fb1 100644 --- a/src/main/java/com/lmax/solana4j/programs/AssociatedTokenMetadataProgram.java +++ b/src/main/java/com/lmax/solana4j/programs/AssociatedTokenMetadataProgram.java @@ -2,6 +2,7 @@ import com.lmax.solana4j.Solana; import com.lmax.solana4j.api.PublicKey; +import com.lmax.solana4j.encoding.TokenMetadata; import net.i2p.crypto.eddsa.math.Curve; import org.bitcoinj.core.Base58; import org.bitcoinj.core.Sha256Hash; @@ -16,6 +17,9 @@ public final class AssociatedTokenMetadataProgram { + private static final byte[] ASSOCIATED_TOKEN_PROGRAM_ID = Base58.decode("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + public static final PublicKey ASSOCIATED_TOKEN_PROGRAM_ACCOUNT = Solana.account(ASSOCIATED_TOKEN_PROGRAM_ID); + private static final byte[] METADATA_MAGIC_STRING = "metadata".getBytes(UTF_8); private static final byte[] ADDRESS_MAGIC_STRING = "ProgramDerivedAddress".getBytes(UTF_8); private static final byte[] ASSOCIATED_TOKEN_METADATA_PROGRAM_ID = Base58.decode("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"); diff --git a/src/main/java/com/lmax/solana4j/programs/AssociatedTokenProgram.java b/src/main/java/com/lmax/solana4j/programs/AssociatedTokenProgram.java deleted file mode 100644 index 80fde49a..00000000 --- a/src/main/java/com/lmax/solana4j/programs/AssociatedTokenProgram.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.lmax.solana4j.programs; - -import com.lmax.solana4j.Solana; -import com.lmax.solana4j.api.PublicKey; -import org.bitcoinj.core.Base58; - - -public final class AssociatedTokenProgram -{ - private static final byte[] ASSOCIATED_TOKEN_PROGRAM_ID = Base58.decode("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); - public static final PublicKey ASSOCIATED_TOKEN_PROGRAM_ACCOUNT = Solana.account(ASSOCIATED_TOKEN_PROGRAM_ID); -} diff --git a/src/main/java/com/lmax/solana4j/programs/SystemProgram.java b/src/main/java/com/lmax/solana4j/programs/SystemProgram.java index e80402a0..5f330ac1 100644 --- a/src/main/java/com/lmax/solana4j/programs/SystemProgram.java +++ b/src/main/java/com/lmax/solana4j/programs/SystemProgram.java @@ -3,6 +3,7 @@ import com.lmax.solana4j.Solana; import com.lmax.solana4j.api.PublicKey; import com.lmax.solana4j.api.TransactionBuilderBase; +import com.lmax.solana4j.encoding.SysVar; import org.bitcoinj.core.Base58; import java.nio.ByteOrder; diff --git a/src/main/java/com/lmax/solana4j/programs/TokenProgram.java b/src/main/java/com/lmax/solana4j/programs/TokenProgram.java index ae6c2b2d..f1768159 100644 --- a/src/main/java/com/lmax/solana4j/programs/TokenProgram.java +++ b/src/main/java/com/lmax/solana4j/programs/TokenProgram.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.Optional; -import static com.lmax.solana4j.programs.SysVar.RENT; +import static com.lmax.solana4j.encoding.SysVar.RENT; import static com.lmax.solana4j.programs.SystemProgram.SYSTEM_PROGRAM_ACCOUNT; diff --git a/src/test-support/java/com/lmax/solana4j/SolanaDriver.java b/src/test-support/java/com/lmax/solana4j/SolanaDriver.java index 643ac33d..694094e3 100644 --- a/src/test-support/java/com/lmax/solana4j/SolanaDriver.java +++ b/src/test-support/java/com/lmax/solana4j/SolanaDriver.java @@ -1,6 +1,7 @@ package com.lmax.solana4j; import com.lmax.solana4j.api.AddressLookupTable; +import com.lmax.solana4j.api.ProgramDerivedAddress; import com.lmax.solana4j.api.PublicKey; import com.lmax.solana4j.api.Slot; import com.lmax.solana4j.client.api.AccountInfo; @@ -11,7 +12,6 @@ import com.lmax.solana4j.domain.TestKeyPair; import com.lmax.solana4j.domain.TestPublicKey; import com.lmax.solana4j.encoding.SolanaEncoding; -import com.lmax.solana4j.programs.AddressWithBumpSeed; import com.lmax.solana4j.transaction.LegacyTransactionFactory; import com.lmax.solana4j.transaction.TransactionFactory; import com.lmax.solana4j.transaction.V0TransactionFactory; @@ -55,7 +55,7 @@ public AccountInfo getAccountInfo(final TestPublicKey address, final Commitment return solanaApi.getAccountInfo(address.getPublicKeyBase58(), commitment); } - public String createAddressLookupTable(final AddressWithBumpSeed addressWithBumpSeed, + public String createAddressLookupTable(final ProgramDerivedAddress programDerivedAddress, final TestKeyPair authority, final TestKeyPair payer, final Slot slot, @@ -64,7 +64,7 @@ public String createAddressLookupTable(final AddressWithBumpSeed addressWithBump final com.lmax.solana4j.client.api.Blockhash recentBlockhash = solanaApi.getRecentBlockHash(); final String transactionBlob = getTransactionFactory().createAddressLookupTable( - addressWithBumpSeed, + programDerivedAddress, authority.getSolana4jPublicKey(), slot, Solana.blockhash(recentBlockhash.getBytes()), diff --git a/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java b/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java index 605cbcee..92de6f39 100644 --- a/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java +++ b/src/test-support/java/com/lmax/solana4j/SolanaNodeDsl.java @@ -4,6 +4,7 @@ import com.lmax.simpledsl.api.OptionalArg; import com.lmax.simpledsl.api.RequiredArg; import com.lmax.solana4j.api.AddressLookupTable; +import com.lmax.solana4j.api.ProgramDerivedAddress; import com.lmax.solana4j.api.PublicKey; import com.lmax.solana4j.assertion.IsEqualToAssertion; import com.lmax.solana4j.assertion.IsNotNullAssertion; @@ -15,8 +16,8 @@ import com.lmax.solana4j.domain.TestKeyPair; import com.lmax.solana4j.domain.TestKeyPairGenerator; import com.lmax.solana4j.domain.TestPublicKey; +import com.lmax.solana4j.encoding.SolanaEncoding; import com.lmax.solana4j.programs.AddressLookupTableProgram; -import com.lmax.solana4j.programs.AddressWithBumpSeed; import org.bouncycastle.util.encoders.Base64; import java.util.ArrayList; @@ -113,10 +114,12 @@ public void createAddressLookupTable(final String... args) final long recentSlot = solanaDriver.getSlot(Commitment.FINALIZED); - final AddressWithBumpSeed addressWithBumpSeed = AddressLookupTableProgram.deriveLookupTableAddress(Solana.account(authority.getPublicKeyBytes()), Solana.slot(recentSlot)); - testContext.storePublicKey(params.value("lookupTableAddress"), addressWithBumpSeed.getLookupTableAddress()); + final ProgramDerivedAddress programDerivedAddress = SolanaEncoding.deriveProgramAddress( + List.of(authority.getPublicKeyBytes(), Solana.slot(recentSlot).bytes()), + AddressLookupTableProgram.PROGRAM_ACCOUNT); + testContext.storePublicKey(params.value("lookupTableAddress"), new TestPublicKey(programDerivedAddress.address().bytes())); - final String transactionSignature = solanaDriver.createAddressLookupTable(addressWithBumpSeed, authority, payer, Solana.slot(recentSlot), addressLookupTables); + final String transactionSignature = solanaDriver.createAddressLookupTable(programDerivedAddress, authority, payer, Solana.slot(recentSlot), addressLookupTables); new Waiter().waitFor(new IsNotNullAssertion<>(() -> solanaDriver.getTransactionResponse(transactionSignature, FINALIZED).getTransaction())); } diff --git a/src/test-support/java/com/lmax/solana4j/transaction/LegacyTransactionFactory.java b/src/test-support/java/com/lmax/solana4j/transaction/LegacyTransactionFactory.java index 0548e6ba..2ac9f48e 100644 --- a/src/test-support/java/com/lmax/solana4j/transaction/LegacyTransactionFactory.java +++ b/src/test-support/java/com/lmax/solana4j/transaction/LegacyTransactionFactory.java @@ -7,13 +7,13 @@ import com.lmax.solana4j.api.AddressLookupTable; import com.lmax.solana4j.api.Blockhash; import com.lmax.solana4j.api.Destination; +import com.lmax.solana4j.api.ProgramDerivedAddress; import com.lmax.solana4j.api.PublicKey; import com.lmax.solana4j.api.SignedMessageBuilder; import com.lmax.solana4j.api.Slot; import com.lmax.solana4j.domain.BouncyCastleSigner; import com.lmax.solana4j.domain.TestKeyPair; import com.lmax.solana4j.programs.AddressLookupTableProgram; -import com.lmax.solana4j.programs.AddressWithBumpSeed; import com.lmax.solana4j.programs.SystemProgram; import org.bitcoinj.core.Base58; @@ -321,7 +321,7 @@ public String initializeMultiSig( @Override public String createAddressLookupTable( - final AddressWithBumpSeed addressWithBumpSeed, + final ProgramDerivedAddress programDerivedAddress, final PublicKey authority, final Slot recentSlot, final Blockhash blockhash, @@ -336,7 +336,7 @@ public String createAddressLookupTable( .recent(blockhash) .instructions(builder -> AddressLookupTableProgram.factory(builder) .createAddressLookupTableInstruction( - addressWithBumpSeed, + programDerivedAddress, authority, payer, recentSlot) diff --git a/src/test-support/java/com/lmax/solana4j/transaction/TransactionFactory.java b/src/test-support/java/com/lmax/solana4j/transaction/TransactionFactory.java index 9661b86f..987fc910 100644 --- a/src/test-support/java/com/lmax/solana4j/transaction/TransactionFactory.java +++ b/src/test-support/java/com/lmax/solana4j/transaction/TransactionFactory.java @@ -5,10 +5,10 @@ import com.lmax.solana4j.api.AddressLookupTable; import com.lmax.solana4j.api.Blockhash; import com.lmax.solana4j.api.Destination; +import com.lmax.solana4j.api.ProgramDerivedAddress; import com.lmax.solana4j.api.PublicKey; import com.lmax.solana4j.api.Slot; import com.lmax.solana4j.domain.TestKeyPair; -import com.lmax.solana4j.programs.AddressWithBumpSeed; import java.util.List; @@ -99,7 +99,7 @@ String initializeMultiSig( List addressLookupTables); String createAddressLookupTable( - AddressWithBumpSeed addressWithBumpSeed, + ProgramDerivedAddress programDerivedAddress, PublicKey authority, Slot slot, Blockhash blockhash, diff --git a/src/test-support/java/com/lmax/solana4j/transaction/V0TransactionFactory.java b/src/test-support/java/com/lmax/solana4j/transaction/V0TransactionFactory.java index a1e121c4..ae1e30f2 100644 --- a/src/test-support/java/com/lmax/solana4j/transaction/V0TransactionFactory.java +++ b/src/test-support/java/com/lmax/solana4j/transaction/V0TransactionFactory.java @@ -7,13 +7,13 @@ import com.lmax.solana4j.api.AddressLookupTable; import com.lmax.solana4j.api.Blockhash; import com.lmax.solana4j.api.Destination; +import com.lmax.solana4j.api.ProgramDerivedAddress; import com.lmax.solana4j.api.PublicKey; import com.lmax.solana4j.api.SignedMessageBuilder; import com.lmax.solana4j.api.Slot; import com.lmax.solana4j.domain.BouncyCastleSigner; import com.lmax.solana4j.domain.TestKeyPair; import com.lmax.solana4j.programs.AddressLookupTableProgram; -import com.lmax.solana4j.programs.AddressWithBumpSeed; import com.lmax.solana4j.programs.SystemProgram; import org.bitcoinj.core.Base58; @@ -320,7 +320,7 @@ public String initializeMultiSig( @Override public String createAddressLookupTable( - final AddressWithBumpSeed addressWithBumpSeed, + final ProgramDerivedAddress programDerivedAddress, final PublicKey authority, final Slot slot, final Blockhash blockhash, @@ -335,7 +335,7 @@ public String createAddressLookupTable( .recent(blockhash) .instructions(builder -> AddressLookupTableProgram.factory(builder) .createAddressLookupTableInstruction( - addressWithBumpSeed, + programDerivedAddress, authority, payer, slot) diff --git a/src/test/java/com/lmax/solana4j/encoding/SolanaProgramDerivedAddressTest.java b/src/test/java/com/lmax/solana4j/encoding/SolanaProgramDerivedAddressTest.java new file mode 100644 index 00000000..a14d4d55 --- /dev/null +++ b/src/test/java/com/lmax/solana4j/encoding/SolanaProgramDerivedAddressTest.java @@ -0,0 +1,62 @@ +package com.lmax.solana4j.encoding; + +import com.lmax.solana4j.Solana; +import com.lmax.solana4j.api.ProgramDerivedAddress; +import com.lmax.solana4j.api.PublicKey; +import com.lmax.solana4j.api.Slot; +import com.lmax.solana4j.programs.AddressLookupTableProgram; +import org.assertj.core.api.AssertionsForClassTypes; +import org.bitcoinj.core.Base58; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class SolanaProgramDerivedAddressTest +{ + + @Test + void shouldCalculateAProgramDerivedAddress() + { + //Some precomputed values from web3.js + final SolanaAccount owner = new SolanaAccount(Base58.decode("DgmAEaJx5mDvwKUuexiN2urBkuzEMzMrgBMj2SoadyyK")); + final SolanaAccount program = new SolanaAccount(Base58.decode("MuMQqQg7tcaerMu7RKMuAvXjLLF4yeQ6swdqUu4eDNN")); + + final ProgramDerivedAddress programDerivedAddress = SolanaProgramDerivedAddress.deriveProgramAddress(owner, program); + + assertThat(programDerivedAddress.address().base58()).isEqualTo("5BQ9r1Q7HLCPQS6QnTaqcXFhwS8hb7uwhxLknJuNLh8E"); + assertThat(programDerivedAddress.nonce()).isEqualTo(255); + } + + @Test + void shouldCalculateAProgramDerivedAddressFromMultipleSeeds() + { + final SolanaAccount program = new SolanaAccount(Base58.decode("FTCuVnzaBZQXGz7D5mweRnWgY4fbS8rg42SD6envtoUD")); + + final SolanaAccount seed = new SolanaAccount(Base58.decode("Wd4UqPtgrnYAH6pxMrzr6aNv4CmTFgwDfPQi9BYjPt7")); + final ProgramDerivedAddress programDerivedAddress = SolanaProgramDerivedAddress.deriveProgramAddress( + List.of("aString".getBytes(StandardCharsets.UTF_8), "anotherString".getBytes(StandardCharsets.UTF_8), seed.bytes), + program); + + AssertionsForClassTypes.assertThat(programDerivedAddress.address().base58()).isEqualTo("FiZFCNEX1WJbP1UEyr2o4uyhtEFoZc3cMyYScf6LAYDx"); + AssertionsForClassTypes.assertThat(programDerivedAddress.nonce()).isEqualTo(254); + } + + @Test + void shouldCalculateAProgramDerivedAddressForAddressLookupTable() + { + final PublicKey authority = Solana.account(Base58.decode("EYB1g5R8beNtVqDpKpmkKWtLdhBY8Wh7q3QT3U3fbw7y")); + + final Slot recentSlot = Solana.slot(265008810); + + final ProgramDerivedAddress programDerivedAddress = SolanaProgramDerivedAddress.deriveProgramAddress( + List.of(authority.bytes(), recentSlot.bytes()), + AddressLookupTableProgram.PROGRAM_ACCOUNT + ); + + assertThat(programDerivedAddress.address().base58()).isEqualTo("DmTtM8rQMcqR56ksBkayLd9KuYiFaGYZEAPnh5iUtgVV"); + assertThat(programDerivedAddress.nonce()).isEqualTo(255); + } +} diff --git a/src/test/java/com/lmax/solana4j/programs/AddressLookupTableProgramTest.java b/src/test/java/com/lmax/solana4j/programs/AddressLookupTableProgramTest.java deleted file mode 100644 index aed464eb..00000000 --- a/src/test/java/com/lmax/solana4j/programs/AddressLookupTableProgramTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lmax.solana4j.programs; - -import com.lmax.solana4j.Solana; -import com.lmax.solana4j.api.PublicKey; -import org.bitcoinj.core.Base58; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -class AddressLookupTableProgramTest -{ - @Test - void derivesCorrectAddressLookupAddressAndBumpSeed() - { - final PublicKey authority = Solana.account(Base58.decode("EYB1g5R8beNtVqDpKpmkKWtLdhBY8Wh7q3QT3U3fbw7y")); - final AddressWithBumpSeed address = AddressLookupTableProgram.deriveLookupTableAddress(authority, Solana.slot(265008810)); - - assertThat(Base58.encode(address.getLookupTableAddress())).isEqualTo("DmTtM8rQMcqR56ksBkayLd9KuYiFaGYZEAPnh5iUtgVV"); - assertThat(address.getBumpSeed()).isEqualTo(255); - } -} diff --git a/src/test/java/com/lmax/solana4j/programs/AssociatedTokenMetadataProgramTest.java b/src/test/java/com/lmax/solana4j/programs/AssociatedTokenMetadataProgramTest.java index 0ee5ce4e..ae8d7c2b 100644 --- a/src/test/java/com/lmax/solana4j/programs/AssociatedTokenMetadataProgramTest.java +++ b/src/test/java/com/lmax/solana4j/programs/AssociatedTokenMetadataProgramTest.java @@ -2,6 +2,7 @@ import com.lmax.solana4j.Solana; import com.lmax.solana4j.api.PublicKey; +import com.lmax.solana4j.encoding.TokenMetadata; import org.bitcoinj.core.Base58; import org.junit.jupiter.api.Test;