From e9a256ac421b9fecac072f0c74dde9d978aa93ed Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 9 Sep 2026 18:41:55 -0400 Subject: [PATCH] fix(oc): match the Coinbase stable swapper upgrade The stable swapper program dropped its address whitelist and moved to role-based authorities (coinbase/stable-swapper#20). The server side landed in code-payments/ocp-server#257; this brings the client's instruction and account parsing in line. Swap instruction: the `whitelist` account after `user` is gone, so the instruction now carries 15 accounts. The `address_whitelist` PDA derivation and its tests go with it. Every client-built swap (stateful, stateless sweep, USDC->USDF onramp, USDF deposit) used the same `CoinbaseStableSwapperProgram_Swap`, so all four pick this up. Pool account: `operations_authority` was replaced by `pause`, `unpause`, `treasury` and `configure` authorities, which pushes `fee_recipient` from byte 72 to byte 136. The onramp reads this field from chain to pick the fee recipient for USDC->USDF and USDF deposit transactions; at the old offset it would read `treasury_authority` instead. The parser now checks the account discriminator and is covered by the same mainnet fixture ocp-server uses (pool CrDL9SoCyW1tBgn8k7rgGSpWhnszneWDbvKvqPAU4PL9 after the 2026-09-08 MigrateAuthorities instruction). Compute unit limits are unchanged here: the server-driven swaps take the limit from server parameters, and the client-built transactions already set 200_000. --- .../internal/solana/extensions/PublicKey.kt | 7 -- .../model/CoinbaseStablecoinPoolAccount.kt | 47 ++++++++++-- .../solana/model/CoinbaseSwapAccounts.kt | 4 - .../CoinbaseStableSwapperProgram_Swap.kt | 2 - .../CoinbaseStablecoinSwapperInstructions.kt | 1 - .../swap/UsdcDepositSweepInstructions.kt | 1 - .../solana/swap/UsdcToUsdfSwapInstructions.kt | 1 - .../solana/swap/UsdfDepositInstructions.kt | 1 - .../solana/extensions/PdaDerivationTest.kt | 23 +----- .../CoinbaseStablecoinPoolAccountTest.kt | 73 +++++++++++++++++++ .../CoinbaseStableSwapperProgramTest.kt | 31 ++++---- ...inbaseStablecoinSwapperInstructionsTest.kt | 8 -- .../swap/StatelessSwapInstructionsTest.kt | 8 -- 13 files changed, 131 insertions(+), 76 deletions(-) create mode 100644 services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseStablecoinPoolAccountTest.kt diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/extensions/PublicKey.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/extensions/PublicKey.kt index a17f9d2460..766c543ae1 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/extensions/PublicKey.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/extensions/PublicKey.kt @@ -172,13 +172,6 @@ internal fun PublicKey.Companion.deriveCoinbaseVaultTokenAccountAddress(vault: P ) } -internal fun PublicKey.Companion.deriveCoinbaseWhitelistAddress(): ProgramDerivedAccount { - return findProgramAddress( - seeds = listOf("address_whitelist".toByteArray(Charsets.UTF_8)), - programId = CoinbaseStableSwapperProgram.address, - ) -} - /// FindProgramAddress mirrors the implementation of the Solana SDK's FindProgramAddress. Its primary /// use case (for Kin and Agora) is for deriving associated accounts. /// diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseStablecoinPoolAccount.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseStablecoinPoolAccount.kt index a23a5e1141..51c2fa418c 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseStablecoinPoolAccount.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseStablecoinPoolAccount.kt @@ -5,23 +5,56 @@ import com.getcode.solana.keys.PublicKey /** * Represents the on-chain CoinbaseStableSwapper liquidity pool account. * - * Layout: - * [8 discriminator][32 operations_authority][32 pause_authority][32 fee_recipient]... + * Mirrors `LiquidityPool` in coinbase/stable-swapper (`programs/stable-swapper/src/state.rs`) + * after the role-based authority migration (coinbase/stable-swapper#20). Layout: + * + * ``` + * [8 discriminator] + * [32 pause_authority][32 unpause_authority][32 treasury_authority][32 configure_authority] + * [32 fee_recipient] + * [4 + 32n withdraw_recipients][4 + 32n supported_tokens] + * [8 fee_rate][1 swaps_paused][1 liquidity_paused][1 bump] + * ``` + * + * Only the fixed-offset prefix is parsed; the client needs the fee recipient and nothing + * after the vectors. */ internal data class CoinbaseStablecoinPoolAccount( + val pauseAuthority: PublicKey, + val unpauseAuthority: PublicKey, + val treasuryAuthority: PublicKey, + val configureAuthority: PublicKey, val feeRecipient: PublicKey, ) { companion object { - private const val FEE_RECIPIENT_OFFSET = 8 + 32 + 32 // discriminator + ops_authority + pause_authority + // LiquidityPool discriminator: [66, 38, 17, 64, 188, 80, 68, 129] + private val DISCRIMINATOR = byteArrayOf(66, 38, 17, 64, 188.toByte(), 80, 68, 129.toByte()) + + private const val KEY_SIZE = 32 + private const val PAUSE_AUTHORITY_OFFSET = 8 + private const val UNPAUSE_AUTHORITY_OFFSET = PAUSE_AUTHORITY_OFFSET + KEY_SIZE + private const val TREASURY_AUTHORITY_OFFSET = UNPAUSE_AUTHORITY_OFFSET + KEY_SIZE + private const val CONFIGURE_AUTHORITY_OFFSET = TREASURY_AUTHORITY_OFFSET + KEY_SIZE + private const val FEE_RECIPIENT_OFFSET = CONFIGURE_AUTHORITY_OFFSET + KEY_SIZE + private const val FIXED_PREFIX_SIZE = FEE_RECIPIENT_OFFSET + KEY_SIZE fun fromAccountData(data: ByteArray): CoinbaseStablecoinPoolAccount { - require(data.size >= FEE_RECIPIENT_OFFSET + 32) { - "Account data too short: expected at least ${FEE_RECIPIENT_OFFSET + 32} bytes, got ${data.size}" + require(data.size >= FIXED_PREFIX_SIZE) { + "Account data too short: expected at least $FIXED_PREFIX_SIZE bytes, got ${data.size}" + } + require(data.sliceArray(0 until DISCRIMINATOR.size).contentEquals(DISCRIMINATOR)) { + "Account data is not a CoinbaseStableSwapper LiquidityPool (discriminator mismatch)" } - val feeRecipientBytes = data.sliceArray(FEE_RECIPIENT_OFFSET until FEE_RECIPIENT_OFFSET + 32) return CoinbaseStablecoinPoolAccount( - feeRecipient = PublicKey(feeRecipientBytes.toList()), + pauseAuthority = data.keyAt(PAUSE_AUTHORITY_OFFSET), + unpauseAuthority = data.keyAt(UNPAUSE_AUTHORITY_OFFSET), + treasuryAuthority = data.keyAt(TREASURY_AUTHORITY_OFFSET), + configureAuthority = data.keyAt(CONFIGURE_AUTHORITY_OFFSET), + feeRecipient = data.keyAt(FEE_RECIPIENT_OFFSET), ) } + + private fun ByteArray.keyAt(offset: Int): PublicKey = + PublicKey(sliceArray(offset until offset + KEY_SIZE).toList()) } } diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseSwapAccounts.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseSwapAccounts.kt index 3c9fdeb3d9..9f6ddc6f43 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseSwapAccounts.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseSwapAccounts.kt @@ -4,7 +4,6 @@ import com.getcode.opencode.internal.solana.extensions.deriveAssociatedAccount import com.getcode.opencode.internal.solana.extensions.deriveCoinbasePoolAddress import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseTokenVaultAddress import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseVaultTokenAccountAddress -import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseWhitelistAddress import com.getcode.solana.keys.PublicKey internal data class CoinbaseSwapAccounts( @@ -13,7 +12,6 @@ internal data class CoinbaseSwapAccounts( val outVault: PublicKey, val inVaultTokenAccount: PublicKey, val outVaultTokenAccount: PublicKey, - val whitelist: PublicKey, ) { fun feeRecipientTokenAccount(feeRecipient: PublicKey, fromMint: PublicKey): PublicKey { return PublicKey.deriveAssociatedAccount( @@ -29,7 +27,6 @@ internal data class CoinbaseSwapAccounts( val outVault = PublicKey.deriveCoinbaseTokenVaultAddress(pool, toMint).publicKey val inVaultTokenAccount = PublicKey.deriveCoinbaseVaultTokenAccountAddress(inVault).publicKey val outVaultTokenAccount = PublicKey.deriveCoinbaseVaultTokenAccountAddress(outVault).publicKey - val whitelist = PublicKey.deriveCoinbaseWhitelistAddress().publicKey return CoinbaseSwapAccounts( pool = pool, @@ -37,7 +34,6 @@ internal data class CoinbaseSwapAccounts( outVault = outVault, inVaultTokenAccount = inVaultTokenAccount, outVaultTokenAccount = outVaultTokenAccount, - whitelist = whitelist, ) } } diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/programs/CoinbaseStableSwapperProgram_Swap.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/programs/CoinbaseStableSwapperProgram_Swap.kt index c156d67ed9..297c30bbb6 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/programs/CoinbaseStableSwapperProgram_Swap.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/solana/programs/CoinbaseStableSwapperProgram_Swap.kt @@ -18,7 +18,6 @@ internal class CoinbaseStableSwapperProgram_Swap( private val fromMint: PublicKey, private val toMint: PublicKey, private val user: PublicKey, - private val whitelist: PublicKey, private val amountIn: Long, private val minAmountOut: Long, ) : InstructionType { @@ -38,7 +37,6 @@ internal class CoinbaseStableSwapperProgram_Swap( AccountMeta.readonly(publicKey = fromMint), AccountMeta.readonly(publicKey = toMint), AccountMeta.writable(publicKey = user, signer = true), - AccountMeta.readonly(publicKey = whitelist), AccountMeta.readonly(publicKey = TokenProgram.address), AccountMeta.readonly(publicKey = AssociatedTokenProgram.address), AccountMeta.readonly(publicKey = SystemProgram.address), diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/CoinbaseStablecoinSwapperInstructions.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/CoinbaseStablecoinSwapperInstructions.kt index 3d78a61738..ad682aca9b 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/CoinbaseStablecoinSwapperInstructions.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/CoinbaseStablecoinSwapperInstructions.kt @@ -130,7 +130,6 @@ internal fun buildStablecoinSwapperInstructions( fromMint = fromMintMetadata.address, toMint = toMintMetadata.address, user = swapAuthority, - whitelist = swapAccounts.whitelist, amountIn = amount, minAmountOut = minOutput, ).instruction() diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdcDepositSweepInstructions.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdcDepositSweepInstructions.kt index 6dcff213eb..c4610ba9bf 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdcDepositSweepInstructions.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdcDepositSweepInstructions.kt @@ -111,7 +111,6 @@ internal fun buildStatelessSwapInstructions( fromMint = fromMint.address, toMint = toMint.address, user = owner, - whitelist = swapAccounts.whitelist, amountIn = amount, minAmountOut = amount, // 1:1 stable pair ).instruction() diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdcToUsdfSwapInstructions.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdcToUsdfSwapInstructions.kt index 62243ecd1a..5d4f74476c 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdcToUsdfSwapInstructions.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdcToUsdfSwapInstructions.kt @@ -103,7 +103,6 @@ internal fun buildUsdcToUsdfSwapInstructions( fromMint = Mint.usdc, toMint = Mint.usdf, user = sender, - whitelist = swapAccounts.whitelist, amountIn = amount, minAmountOut = 0, ).instruction() diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdfDepositInstructions.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdfDepositInstructions.kt index 0fd537cd85..5767eb0a81 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdfDepositInstructions.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/solana/swap/UsdfDepositInstructions.kt @@ -97,7 +97,6 @@ internal fun buildUsdfDepositInstructions( fromMint = Mint.usdc, toMint = Mint.usdf, user = sender, - whitelist = swapAccounts.whitelist, amountIn = amount, minAmountOut = amount, // 1:1 stable pair ).instruction() diff --git a/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/extensions/PdaDerivationTest.kt b/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/extensions/PdaDerivationTest.kt index 02b327010f..c199c269c9 100644 --- a/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/extensions/PdaDerivationTest.kt +++ b/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/extensions/PdaDerivationTest.kt @@ -247,22 +247,6 @@ class PdaDerivationTest { assertNotEquals(result1.publicKey, result2.publicKey) } - // --- deriveCoinbaseWhitelistAddress --- - - @Test - fun coinbaseWhitelistIsDeterministic() { - val result1 = PublicKey.deriveCoinbaseWhitelistAddress() - val result2 = PublicKey.deriveCoinbaseWhitelistAddress() - assertEquals(result1.publicKey, result2.publicKey) - assertEquals(result1.bump, result2.bump) - } - - @Test - fun coinbaseWhitelistIsOffCurve() { - val result = PublicKey.deriveCoinbaseWhitelistAddress() - assertTrue(!Ed25519.onCurve(result.publicKey.bytes.toByteArray())) - } - // --- Coinbase PDA chain: pool -> vault -> vaultTokenAccount --- @Test @@ -271,11 +255,10 @@ class PdaDerivationTest { val mint = testKey(10) val vault = PublicKey.deriveCoinbaseTokenVaultAddress(pool, mint).publicKey val vaultTA = PublicKey.deriveCoinbaseVaultTokenAccountAddress(vault).publicKey - val whitelist = PublicKey.deriveCoinbaseWhitelistAddress().publicKey - // All four should be distinct - val all = setOf(pool, vault, vaultTA, whitelist) - assertEquals(4, all.size, "pool, vault, vaultTA, and whitelist should all be distinct") + // All three should be distinct + val all = setOf(pool, vault, vaultTA) + assertEquals(3, all.size, "pool, vault, and vaultTA should all be distinct") } // --- Known value: well-known associated token address --- diff --git a/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseStablecoinPoolAccountTest.kt b/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseStablecoinPoolAccountTest.kt new file mode 100644 index 0000000000..822bab7678 --- /dev/null +++ b/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/model/CoinbaseStablecoinPoolAccountTest.kt @@ -0,0 +1,73 @@ +package com.getcode.opencode.internal.solana.model + +import com.getcode.solana.keys.base58 +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +@OptIn(ExperimentalEncodingApi::class) +class CoinbaseStablecoinPoolAccountTest { + + // First 315 bytes of mainnet pool CrDL9SoCyW1tBgn8k7rgGSpWhnszneWDbvKvqPAU4PL9 + // at slot ~445672210, after the 2026-09-08 MigrateAuthorities instruction. The + // live account is 2107 bytes with the remainder zeroed. Same fixture as ocp-server. + private val mainnetPrefix = "QiYRQLxQRIEFHqE9vluQFKO1wbEwnd22aRe9qGrV03SIsz1AV7GqT/yEzcR/f+ALaKG4KMxbBfZ5dTNFPqxxZHMfbTqNfj6St0p+" + + "+yObz2IILMcKsoko07O+oRSDek7YwzrH9TroL1+QbHdT/t9qpcq4Kyx8OPWZm79AIUM9UlN+X6ujF0hPgzT4z8SXtrVTfBhZj7Lz" + + "SPTgCpHoi6cjfPqXvflOJvRBAgAAAN0H70q0C5DeChX575Umuo4KwnYx+lqZmPTGnq1wMK2QSoyv1lJlvQkMgeq0VkN3NML2MHaz" + + "cTzSusODf5c6RhACAAAAxvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWE908SAij1Ps5+uycukm1pjSlLt4RVI9SSqIPLN" + + "Ru+AcQAAAAAAAAAAAAD/" + + private fun mainnetData(): ByteArray { + val prefix = Base64.decode(mainnetPrefix) + assertEquals(315, prefix.size) + return prefix.copyOf(2107) + } + + @Test + fun `parses mainnet pool account after authority migration`() { + val pool = CoinbaseStablecoinPoolAccount.fromAccountData(mainnetData()) + + assertEquals("Lz8QXHjETKQnt1fzsKbN4AyQEhhVAFB2YKwAMqksr7G", pool.pauseAuthority.base58()) + assertEquals("HzjC9U1WifkqLhYMx592UG1fJqX3BtRjCqPPPTo3hA8R", pool.unpauseAuthority.base58()) + assertEquals("DLVVcd3xfwWeCwGz1EUQbqaNC88NooN6s9ifWo87QZst", pool.treasuryAuthority.base58()) + assertEquals("Aimdv5hcHfm2PKuGwDW9H81iibZoYKLv3TPMEaZhmvqG", pool.configureAuthority.base58()) + assertEquals("4ZnFXk7KyB5khDqjWSHqHBQH1nQCnmvkr1pRFivWcP7e", pool.feeRecipient.base58()) + } + + @Test + fun `fee recipient is read at the post-migration offset`() { + // Pre-migration layout put fee_recipient at byte 72; reading there now yields + // treasury_authority, which would send pool fees to the wrong account. + val pool = CoinbaseStablecoinPoolAccount.fromAccountData(mainnetData()) + assertEquals(pool.treasuryAuthority, keyAt(mainnetData(), 72)) + assertEquals(pool.feeRecipient, keyAt(mainnetData(), 136)) + } + + @Test + fun `rejects truncated data`() { + val data = mainnetData() + for (size in listOf(0, 8, 135, 167)) { + assertFailsWith("size $size") { + CoinbaseStablecoinPoolAccount.fromAccountData(data.copyOf(size)) + } + } + // Exactly the fixed prefix is enough. + CoinbaseStablecoinPoolAccount.fromAccountData(data.copyOf(168)) + } + + @Test + fun `rejects wrong discriminator`() { + val data = mainnetData() + // TokenVault discriminator: [121, 7, 84, 254, 151, 228, 43, 144] + byteArrayOf(121, 7, 84, 254.toByte(), 151.toByte(), 228.toByte(), 43, 144.toByte()) + .copyInto(data) + assertFailsWith { + CoinbaseStablecoinPoolAccount.fromAccountData(data) + } + } + + private fun keyAt(data: ByteArray, offset: Int) = + com.getcode.solana.keys.PublicKey(data.sliceArray(offset until offset + 32).toList()) +} diff --git a/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/programs/CoinbaseStableSwapperProgramTest.kt b/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/programs/CoinbaseStableSwapperProgramTest.kt index 92bbfff95f..8dd33e0817 100644 --- a/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/programs/CoinbaseStableSwapperProgramTest.kt +++ b/services/opencode/src/test/kotlin/com/getcode/opencode/internal/solana/programs/CoinbaseStableSwapperProgramTest.kt @@ -32,7 +32,7 @@ class CoinbaseStableSwapperProgramTest { userFromTokenAccount = testKey(6), toTokenAccount = testKey(7), feeRecipientTokenAccount = testKey(8), feeRecipient = testKey(9), fromMint = testKey(10), toMint = testKey(11), - user = testKey(12), whitelist = testKey(13), + user = testKey(12), amountIn = 0L, minAmountOut = 0L, ) val encoded = ix.encode() @@ -57,7 +57,7 @@ class CoinbaseStableSwapperProgramTest { userFromTokenAccount = testKey(6), toTokenAccount = testKey(7), feeRecipientTokenAccount = testKey(8), feeRecipient = testKey(9), fromMint = testKey(10), toMint = testKey(11), - user = testKey(12), whitelist = testKey(13), + user = testKey(12), amountIn = 1000L, minAmountOut = 900L, ) // 8 bytes discriminator + 8 bytes amountIn + 8 bytes minAmountOut = 24 @@ -72,7 +72,7 @@ class CoinbaseStableSwapperProgramTest { userFromTokenAccount = testKey(6), toTokenAccount = testKey(7), feeRecipientTokenAccount = testKey(8), feeRecipient = testKey(9), fromMint = testKey(10), toMint = testKey(11), - user = testKey(12), whitelist = testKey(13), + user = testKey(12), amountIn = 1_000_000L, minAmountOut = 999_000L, ) val encoded = ix.encode() @@ -93,17 +93,17 @@ class CoinbaseStableSwapperProgramTest { // --- Swap instruction accounts --- @Test - fun swapInstructionHas16Accounts() { + fun swapInstructionHas15Accounts() { val ix = CoinbaseStableSwapperProgram_Swap( pool = testKey(1), inVault = testKey(2), outVault = testKey(3), inVaultTokenAccount = testKey(4), outVaultTokenAccount = testKey(5), userFromTokenAccount = testKey(6), toTokenAccount = testKey(7), feeRecipientTokenAccount = testKey(8), feeRecipient = testKey(9), fromMint = testKey(10), toMint = testKey(11), - user = testKey(12), whitelist = testKey(13), + user = testKey(12), amountIn = 1000L, minAmountOut = 900L, ) - assertEquals(16, ix.instruction().accounts.size) + assertEquals(15, ix.instruction().accounts.size) } @Test @@ -114,7 +114,7 @@ class CoinbaseStableSwapperProgramTest { userFromTokenAccount = testKey(6), toTokenAccount = testKey(7), feeRecipientTokenAccount = testKey(8), feeRecipient = testKey(9), fromMint = testKey(10), toMint = testKey(11), - user = testKey(12), whitelist = testKey(13), + user = testKey(12), amountIn = 1000L, minAmountOut = 900L, ) assertEquals(CoinbaseStableSwapperProgram.address, ix.instruction().program) @@ -122,20 +122,20 @@ class CoinbaseStableSwapperProgramTest { @Test fun swapAccountOrder() { - val keys = (1..13).map { testKey(it) } + val keys = (1..12).map { testKey(it) } val ix = CoinbaseStableSwapperProgram_Swap( pool = keys[0], inVault = keys[1], outVault = keys[2], inVaultTokenAccount = keys[3], outVaultTokenAccount = keys[4], userFromTokenAccount = keys[5], toTokenAccount = keys[6], feeRecipientTokenAccount = keys[7], feeRecipient = keys[8], fromMint = keys[9], toMint = keys[10], - user = keys[11], whitelist = keys[12], + user = keys[11], amountIn = 1000L, minAmountOut = 900L, ) val accounts = ix.instruction().accounts // Verify account order matches server: pool, inVault, outVault, inVaultTA, outVaultTA, - // userFromTA, toTA, feeRecipientTA, feeRecipient, fromMint, toMint, user, whitelist, + // userFromTA, toTA, feeRecipientTA, feeRecipient, fromMint, toMint, user, // tokenProgram, associatedTokenProgram, systemProgram assertEquals(keys[0], accounts[0].publicKey) // pool assertEquals(keys[1], accounts[1].publicKey) // inVault @@ -149,10 +149,9 @@ class CoinbaseStableSwapperProgramTest { assertEquals(keys[9], accounts[9].publicKey) // fromMint assertEquals(keys[10], accounts[10].publicKey) // toMint assertEquals(keys[11], accounts[11].publicKey) // user - assertEquals(keys[12], accounts[12].publicKey) // whitelist - assertEquals(TokenProgram.address, accounts[13].publicKey) - assertEquals(AssociatedTokenProgram.address, accounts[14].publicKey) - assertEquals(SystemProgram.address, accounts[15].publicKey) + assertEquals(TokenProgram.address, accounts[12].publicKey) + assertEquals(AssociatedTokenProgram.address, accounts[13].publicKey) + assertEquals(SystemProgram.address, accounts[14].publicKey) } @Test @@ -163,7 +162,7 @@ class CoinbaseStableSwapperProgramTest { userFromTokenAccount = testKey(6), toTokenAccount = testKey(7), feeRecipientTokenAccount = testKey(8), feeRecipient = testKey(9), fromMint = testKey(10), toMint = testKey(11), - user = testKey(12), whitelist = testKey(13), + user = testKey(12), amountIn = 1000L, minAmountOut = 900L, ) val accounts = ix.instruction().accounts @@ -186,7 +185,7 @@ class CoinbaseStableSwapperProgramTest { userFromTokenAccount = testKey(6), toTokenAccount = testKey(7), feeRecipientTokenAccount = testKey(8), feeRecipient = testKey(9), fromMint = testKey(10), toMint = testKey(11), - user = testKey(12), whitelist = testKey(13), + user = testKey(12), amountIn = 1000L, minAmountOut = 900L, ) val accounts = ix.instruction().accounts diff --git a/services/opencode/src/test/kotlin/com/getcode/opencode/solana/swap/CoinbaseStablecoinSwapperInstructionsTest.kt b/services/opencode/src/test/kotlin/com/getcode/opencode/solana/swap/CoinbaseStablecoinSwapperInstructionsTest.kt index 00896e1d95..4ac2e038f9 100644 --- a/services/opencode/src/test/kotlin/com/getcode/opencode/solana/swap/CoinbaseStablecoinSwapperInstructionsTest.kt +++ b/services/opencode/src/test/kotlin/com/getcode/opencode/solana/swap/CoinbaseStablecoinSwapperInstructionsTest.kt @@ -3,7 +3,6 @@ package com.getcode.opencode.solana.swap import com.getcode.opencode.internal.solana.extensions.deriveCoinbasePoolAddress import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseTokenVaultAddress import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseVaultTokenAccountAddress -import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseWhitelistAddress import com.getcode.opencode.internal.solana.extensions.deriveAssociatedAccount import com.getcode.opencode.internal.solana.programs.AssociatedTokenProgram import com.getcode.opencode.internal.solana.programs.CoinbaseStableSwapperProgram @@ -254,13 +253,6 @@ class CoinbaseStablecoinSwapperInstructionsTest { assertTrue(ix.accounts[11].isSigner) } - @Test - fun `swap instruction has correct whitelist PDA`() { - val ix = buildInstructions()[7] - val expectedWhitelist = PublicKey.deriveCoinbaseWhitelistAddress().publicKey - assertEquals(expectedWhitelist, ix.accounts[12].publicKey) - } - // --- CloseAccount verification --- @Test diff --git a/services/opencode/src/test/kotlin/com/getcode/opencode/solana/swap/StatelessSwapInstructionsTest.kt b/services/opencode/src/test/kotlin/com/getcode/opencode/solana/swap/StatelessSwapInstructionsTest.kt index 37cb7acfee..8acbfe2985 100644 --- a/services/opencode/src/test/kotlin/com/getcode/opencode/solana/swap/StatelessSwapInstructionsTest.kt +++ b/services/opencode/src/test/kotlin/com/getcode/opencode/solana/swap/StatelessSwapInstructionsTest.kt @@ -4,7 +4,6 @@ import com.getcode.opencode.internal.solana.extensions.deriveAssociatedAccount import com.getcode.opencode.internal.solana.extensions.deriveCoinbasePoolAddress import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseTokenVaultAddress import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseVaultTokenAccountAddress -import com.getcode.opencode.internal.solana.extensions.deriveCoinbaseWhitelistAddress import com.getcode.opencode.internal.solana.extensions.deriveDepositAccount import com.getcode.opencode.internal.solana.extensions.deriveVirtualMachineAccount import com.getcode.opencode.internal.solana.programs.AssociatedTokenProgram @@ -296,13 +295,6 @@ class StatelessSwapInstructionsTest { assertTrue(ix.accounts[11].isSigner) } - @Test - fun `swap instruction has correct whitelist PDA`() { - val ix = buildInstructions()[4] - val expectedWhitelist = PublicKey.deriveCoinbaseWhitelistAddress().publicKey - assertEquals(expectedWhitelist, ix.accounts[12].publicKey) - } - // --- CreateIdempotent and Swap destination linkage --- @Test