Skip to content

Commit eab53e9

Browse files
authored
Support constructing applications calls with reject version. (#869)
1 parent 50ad09e commit eab53e9

File tree

3 files changed

+95
-4
lines changed

3 files changed

+95
-4
lines changed

src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public LocalsReference(Address address, long appId) {
4949
private List<HoldingReference> holdings;
5050
private List<LocalsReference> locals;
5151
private Long applicationId;
52+
private Long rejectVersion;
5253
private boolean useAccess = false;
5354

5455
/**
@@ -129,6 +130,7 @@ protected void applyTo(Transaction txn) {
129130
if (applicationId != null) txn.applicationId = applicationId;
130131
if (onCompletion != null) txn.onCompletion = onCompletion;
131132
if (applicationArgs != null) txn.applicationArgs = applicationArgs;
133+
if (rejectVersion != null) txn.rejectVersion = rejectVersion;
132134
}
133135

134136
@Override
@@ -224,28 +226,41 @@ public T locals(List<LocalsReference> locals) {
224226

225227
/**
226228
* Enable or disable translation of foreign references into the access field.
227-
*
229+
*
228230
* When useAccess=true:
229231
* - All foreign references (accounts, foreignApps, foreignAssets, boxReferences) are translated
230232
* into a unified access field instead of using separate legacy fields
231233
* - You can still use the same methods (accounts(), foreignApps(), etc.) - they will be translated
232234
* - Advanced features (holdings(), locals()) are also available
233235
* - Compatible with networks that support the access field consensus upgrade
234-
*
236+
*
235237
* When useAccess=false (default):
236238
* - Uses legacy separate fields (accounts, foreignApps, foreignAssets, boxReferences)
237239
* - No translation occurs - references are placed directly in their respective fields
238240
* - Maintains backward compatibility with pre-consensus upgrade networks
239241
* - Advanced features (holdings(), locals()) are not allowed
240-
*
242+
*
241243
* This design allows easy migration - just add .useAccess(true) to enable access field mode
242244
* while keeping your existing foreign reference method calls.
243-
*
245+
*
244246
* @param useAccess true to translate references to access field, false to use legacy fields
245247
* @return this builder instance
246248
*/
247249
public T useAccess(boolean useAccess) {
248250
this.useAccess = useAccess;
249251
return (T) this;
250252
}
253+
254+
/**
255+
* Set the reject version for the application call.
256+
* The lowest application version for which this transaction should immediately fail.
257+
* 0 indicates that no version check should be performed.
258+
*
259+
* @param rejectVersion the minimum application version to reject
260+
* @return this builder instance
261+
*/
262+
public T rejectVersion(Long rejectVersion) {
263+
this.rejectVersion = rejectVersion;
264+
return (T) this;
265+
}
251266
}

src/main/java/com/algorand/algosdk/transaction/Transaction.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ public class Transaction implements Serializable {
165165
@JsonProperty("apep")
166166
public Long extraPages = 0L;
167167

168+
@JsonProperty("aprv")
169+
public Long rejectVersion = 0L;
170+
168171
/* access field - unifies accounts, foreignApps, foreignAssets, and boxReferences */
169172
@JsonProperty("al")
170173
public List<ResourceRef> access = new ArrayList<>();
@@ -246,6 +249,7 @@ private Transaction(@JsonProperty("type") Type type,
246249
@JsonProperty("apls") StateSchema localStateSchema,
247250
@JsonProperty("apsu") byte[] clearStateProgram,
248251
@JsonProperty("apep") Long extraPages,
252+
@JsonProperty("aprv") Long rejectVersion,
249253
// access fields
250254
@JsonProperty("al") List<ResourceRef> access,
251255
// heartbeat fields
@@ -301,6 +305,7 @@ private Transaction(@JsonProperty("type") Type type,
301305
localStateSchema,
302306
clearStateProgram == null ? null : new TEALProgram(clearStateProgram),
303307
extraPages,
308+
rejectVersion,
304309
access == null ? new ArrayList<>() : access,
305310
heartbeatFields
306311
);
@@ -362,6 +367,7 @@ private Transaction(
362367
StateSchema localStateSchema,
363368
TEALProgram clearStateProgram,
364369
Long extraPages,
370+
Long rejectVersion,
365371
List<ResourceRef> access,
366372
HeartbeatTxnFields heartbeatFields
367373
) {
@@ -408,6 +414,7 @@ private Transaction(
408414
if (localStateSchema != null) this.localStateSchema = localStateSchema;
409415
if (clearStateProgram != null) this.clearStateProgram = clearStateProgram;
410416
if (extraPages != null) this.extraPages = extraPages;
417+
if (rejectVersion != null) this.rejectVersion = rejectVersion;
411418
if (access != null) this.access = access;
412419
if (heartbeatFields != null) this.heartbeatFields = heartbeatFields;
413420
}
@@ -622,6 +629,7 @@ public boolean equals(Object o) {
622629
freezeState == that.freezeState &&
623630
rekeyTo.equals(that.rekeyTo) &&
624631
extraPages.equals(that.extraPages) &&
632+
rejectVersion.equals(that.rejectVersion) &&
625633
boxReferences.equals(that.boxReferences) &&
626634
heartbeatFields.equals(that.heartbeatFields);
627635
}

src/test/java/com/algorand/algosdk/transaction/TestTransaction.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,4 +1053,72 @@ public void TxnEstimatedDefaultFee() throws Exception {
10531053
}
10541054
}
10551055

1056+
@Test
1057+
public void testApplicationCallRejectVersion() throws Exception {
1058+
Address from = new Address("VKM6KSCTDHEM6KGEAMSYCNEGIPFJMHDSEMIRAQLK76CJDIRMMDHKAIRMFQ");
1059+
byte[] gh = Encoder.decodeFromBase64("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=");
1060+
1061+
// Test with specific rejectVersion
1062+
Transaction txWithReject = Transaction.ApplicationCallTransactionBuilder()
1063+
.sender(from)
1064+
.applicationId(123L)
1065+
.firstValid(1000)
1066+
.lastValid(2000)
1067+
.genesisHash(gh)
1068+
.rejectVersion(5L)
1069+
.build();
1070+
1071+
// Verify rejectVersion is set
1072+
assertThat(txWithReject.rejectVersion).isEqualTo(5L);
1073+
1074+
// Test serialization/deserialization with msgpack
1075+
byte[] encodedTxn = Encoder.encodeToMsgPack(txWithReject);
1076+
Transaction decodedTxn = Encoder.decodeFromMsgPack(encodedTxn, Transaction.class);
1077+
assertThat(decodedTxn.rejectVersion).isEqualTo(5L);
1078+
assertEqual(txWithReject, decodedTxn);
1079+
1080+
// Test serialization/deserialization with JSON
1081+
ObjectMapper objectMapper = new ObjectMapper();
1082+
String transactionJson = objectMapper.writeValueAsString(txWithReject);
1083+
Transaction jsonDecodedTxn = objectMapper.readValue(transactionJson, Transaction.class);
1084+
assertThat(jsonDecodedTxn.rejectVersion).isEqualTo(5L);
1085+
assertEqual(txWithReject, jsonDecodedTxn);
1086+
1087+
// Verify aprv field is present in JSON when rejectVersion is non-zero
1088+
assertThat(transactionJson).contains("\"aprv\":5");
1089+
1090+
// Test with default rejectVersion (should be 0)
1091+
Transaction txDefault = Transaction.ApplicationCallTransactionBuilder()
1092+
.sender(from)
1093+
.applicationId(456L)
1094+
.firstValid(1000)
1095+
.lastValid(2000)
1096+
.genesisHash(gh)
1097+
.build();
1098+
1099+
// Verify default rejectVersion is 0
1100+
assertThat(txDefault.rejectVersion).isEqualTo(0L);
1101+
1102+
// Verify aprv field is NOT present in JSON when rejectVersion is 0 (default omission)
1103+
String txDefaultJson = objectMapper.writeValueAsString(txDefault);
1104+
assertThat(txDefaultJson).doesNotContain("aprv");
1105+
1106+
// Test explicit 0 rejectVersion
1107+
Transaction txExplicitZero = Transaction.ApplicationCallTransactionBuilder()
1108+
.sender(from)
1109+
.applicationId(456L)
1110+
.firstValid(1000)
1111+
.lastValid(2000)
1112+
.genesisHash(gh)
1113+
.rejectVersion(0L)
1114+
.build();
1115+
1116+
assertThat(txExplicitZero.rejectVersion).isEqualTo(0L);
1117+
assertEqual(txDefault, txExplicitZero);
1118+
1119+
// Verify aprv field is NOT present in JSON when rejectVersion is explicitly 0
1120+
String txExplicitZeroJson = objectMapper.writeValueAsString(txExplicitZero);
1121+
assertThat(txExplicitZeroJson).doesNotContain("aprv");
1122+
}
1123+
10561124
}

0 commit comments

Comments
 (0)