Skip to content

Commit 2697614

Browse files
authored
fix(rpc): default simulateTransaction Accounts.Encoding to base64 (closes #446) (#447)
* fix(rpc): default simulateTransaction Accounts.Encoding to base64 When a caller passes SimulateTransactionAccountsOpts without setting Encoding explicitly, the SDK previously forwarded the zero-value empty string on the wire ({"encoding":""}), and the Solana validator rejected the simulateTransaction call with `Invalid params: missing field encoding` instead of running the simulation. Mirror the default the rest of the package already applies: substitute solana.EncodingBase64 when the caller-provided Encoding is empty, matching TransactionOpts.ToMap, getAccountInfo, getMultipleAccounts, and getProgramAccounts. Regression tests in simulateTransaction_test.go pin both branches: empty Encoding -> base64 on the wire, and an explicit Encoding (e.g. jsonParsed) forwarded unchanged. * test(rpc): use checked type assertions in encoding-explicit case The explicit-encoding test used bare type assertions that would panic on an unexpected wire shape instead of failing the test cleanly. Switch to the comma-ok form with require, matching the default-encoding test in the same file.
1 parent 34beab1 commit 2697614

2 files changed

Lines changed: 106 additions & 1 deletion

File tree

rpc/simulateTransaction.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,17 @@ func (cl *Client) SimulateRawTransactionWithOpts(
168168
obj["minContextSlot"] = *opts.MinContextSlot
169169
}
170170
if opts.Accounts != nil {
171+
// Solana's simulateTransaction rejects an empty `encoding`
172+
// string with `Invalid params: missing field encoding`, so
173+
// default to base64 the same way the top-level transaction
174+
// encoding does. Matches TransactionOpts.ToMap and the
175+
// per-method defaults in getAccountInfo / getMultipleAccounts.
176+
accountsEncoding := opts.Accounts.Encoding
177+
if accountsEncoding == "" {
178+
accountsEncoding = solana.EncodingBase64
179+
}
171180
obj["accounts"] = M{
172-
"encoding": opts.Accounts.Encoding,
181+
"encoding": accountsEncoding,
173182
"addresses": opts.Accounts.Addresses,
174183
}
175184
}

rpc/simulateTransaction_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2026 github.com/solana-foundation
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package rpc
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
stdjson "github.com/goccy/go-json"
22+
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
24+
25+
"github.com/gagliardetto/solana-go"
26+
)
27+
28+
// TestClient_SimulateRawTransactionWithOpts_AccountsEncodingDefault pins
29+
// that when the caller passes a SimulateTransactionAccountsOpts without
30+
// setting Encoding explicitly, the SDK substitutes base64 on the wire.
31+
//
32+
// Regression test: previously the empty-string Encoding was forwarded as
33+
// {"encoding":""} and the Solana validator rejected the call with
34+
// `Invalid params: missing field encoding` instead of running the
35+
// simulation.
36+
func TestClient_SimulateRawTransactionWithOpts_AccountsEncodingDefault(t *testing.T) {
37+
responseBody := `{"context":{"slot":1},"value":{"err":null,"logs":[],"accounts":null}}`
38+
server, closer := mockJSONRPC(t, stdjson.RawMessage(wrapIntoRPC(responseBody)))
39+
defer closer()
40+
client := New(server.URL)
41+
42+
address := solana.MustPublicKeyFromBase58("7xLk17EQQ5KLDLDe44wCmupJKJjTGd8hs3eSVVhCx932")
43+
_, err := client.SimulateRawTransactionWithOpts(
44+
context.Background(),
45+
[]byte("rawtx"),
46+
&SimulateTransactionOpts{
47+
Accounts: &SimulateTransactionAccountsOpts{
48+
// Encoding intentionally left zero-value.
49+
Addresses: []solana.PublicKey{address},
50+
},
51+
},
52+
)
53+
require.NoError(t, err)
54+
55+
reqBody := server.RequestBody(t)
56+
params, ok := reqBody["params"].([]any)
57+
require.True(t, ok, "params must be a JSON array, got %T", reqBody["params"])
58+
require.Len(t, params, 2)
59+
cfg, ok := params[1].(map[string]any)
60+
require.True(t, ok, "config object expected, got %T", params[1])
61+
accounts, ok := cfg["accounts"].(map[string]any)
62+
require.True(t, ok, "accounts config expected, got %T", cfg["accounts"])
63+
assert.Equal(t, string(solana.EncodingBase64), accounts["encoding"], "empty Encoding must default to base64 on the wire")
64+
}
65+
66+
// TestClient_SimulateRawTransactionWithOpts_AccountsEncodingExplicit pins
67+
// that an explicit Encoding (e.g. jsonParsed) is preserved unchanged.
68+
func TestClient_SimulateRawTransactionWithOpts_AccountsEncodingExplicit(t *testing.T) {
69+
responseBody := `{"context":{"slot":1},"value":{"err":null,"logs":[],"accounts":null}}`
70+
server, closer := mockJSONRPC(t, stdjson.RawMessage(wrapIntoRPC(responseBody)))
71+
defer closer()
72+
client := New(server.URL)
73+
74+
address := solana.MustPublicKeyFromBase58("7xLk17EQQ5KLDLDe44wCmupJKJjTGd8hs3eSVVhCx932")
75+
_, err := client.SimulateRawTransactionWithOpts(
76+
context.Background(),
77+
[]byte("rawtx"),
78+
&SimulateTransactionOpts{
79+
Accounts: &SimulateTransactionAccountsOpts{
80+
Encoding: solana.EncodingJSONParsed,
81+
Addresses: []solana.PublicKey{address},
82+
},
83+
},
84+
)
85+
require.NoError(t, err)
86+
87+
reqBody := server.RequestBody(t)
88+
params, ok := reqBody["params"].([]any)
89+
require.True(t, ok, "params must be a JSON array, got %T", reqBody["params"])
90+
require.Len(t, params, 2)
91+
cfg, ok := params[1].(map[string]any)
92+
require.True(t, ok, "config object expected, got %T", params[1])
93+
accounts, ok := cfg["accounts"].(map[string]any)
94+
require.True(t, ok, "accounts config expected, got %T", cfg["accounts"])
95+
assert.Equal(t, string(solana.EncodingJSONParsed), accounts["encoding"], "explicit Encoding must be forwarded unchanged")
96+
}

0 commit comments

Comments
 (0)