Skip to content

Commit aa1ba61

Browse files
authored
Types: add txn.Access constructor (#866)
* Introduce a Claude.md file and remove non-functional CI badge from README. * Support txn.Access fields for foreign references in app call transaction creation. * Add useAccess example, enforce strict validation, and tweak accessList message pack key. * CR feedback - null check on references and optimized hashcode impl. * Update UseAccess behavior such that callers can use the existing fields and simply enable UseAccess to pass references in the new format. * CR feedback response
1 parent 736ad61 commit aa1ba61

File tree

11 files changed

+1573
-6
lines changed

11 files changed

+1573
-6
lines changed

CLAUDE.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Build and Development Commands
6+
7+
### Build
8+
```bash
9+
mvn package
10+
```
11+
12+
### Testing
13+
```bash
14+
# Run unit tests only
15+
make unit
16+
# or: mvn test -Dcucumber.filter.tags="@unit"
17+
18+
# Run integration tests only
19+
make integration
20+
# or: mvn test -Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest -Dcucumber.filter.tags="@integration"
21+
22+
# Run all tests (unit + integration)
23+
make ci-test
24+
25+
# Run tests with Docker test harness
26+
make docker-test
27+
28+
# Start/stop test harness manually
29+
make harness
30+
make harness-down
31+
```
32+
33+
### Examples
34+
The `examples/` directory contains example code:
35+
```bash
36+
cd examples/
37+
mvn package
38+
java -cp target/sdk-extras-1.0-SNAPSHOT.jar com.algorand.examples.Example
39+
```
40+
41+
## Architecture Overview
42+
43+
This is the official Java SDK for Algorand blockchain, providing client libraries for interacting with algod and indexer nodes.
44+
45+
### Core Package Structure
46+
47+
- **`account/`** - Account management, keypair generation, and logic signature accounts
48+
- **`crypto/`** - Core cryptographic primitives (Ed25519, addresses, signatures, multisig)
49+
- **`transaction/`** - Transaction types, builders, and atomic transaction composer
50+
- **`v2/client/`** - REST clients for algod and indexer APIs (auto-generated from OpenAPI specs)
51+
- **`builder/transaction/`** - Fluent transaction builders for all transaction types
52+
- **`abi/`** - Application Binary Interface support for smart contracts
53+
- **`mnemonic/`** - BIP39 mnemonic phrase utilities
54+
- **`util/`** - Encoding/decoding utilities (MessagePack, Base64, etc.)
55+
56+
### Key Components
57+
58+
#### Transaction System
59+
- **Transaction Builder Pattern**: All transaction types use fluent builders (e.g., `PaymentTransactionBuilder`, `ApplicationCallTransactionBuilder`)
60+
- **Atomic Transaction Composer**: High-level interface for building transaction groups with ABI method calls
61+
- **Transaction Types**: Payment, asset transfer, application calls, key registration, state proof, heartbeat
62+
63+
#### Client Architecture
64+
- **AlgodClient**: REST client for algod daemon (node operations, transaction submission)
65+
- **IndexerClient**: REST client for indexer service (historical queries, account lookups)
66+
- **Generated Code**: Most v2 client classes are auto-generated from OpenAPI specifications
67+
68+
#### Cryptography
69+
- **Ed25519**: Primary signature algorithm using BouncyCastle
70+
- **Multisig**: M-of-N threshold signatures
71+
- **Logic Signatures**: Delegated signing using TEAL programs
72+
- **Address**: 32-byte public key hash with checksum
73+
74+
## Code Generation
75+
76+
The v2 client code (`v2.client.algod.*`, `v2.client.indexer.*`, `v2.client.model.*`) is generated from OpenAPI specs:
77+
- algod.oas2.json - algod daemon API
78+
- indexer.oas2.json - indexer service API
79+
80+
To regenerate clients, use the `generate_java.sh` script from the [generator](https://github.com/algorand/generator/) repository.
81+
82+
## Testing Framework
83+
84+
Uses Cucumber BDD testing with separate unit and integration test suites:
85+
- **Unit tests**: Fast tests using mocked clients (`@unit` tags)
86+
- **Integration tests**: Full integration with test harness (`@integration` tags)
87+
- **Test harness**: Dockerized Algorand network for integration testing
88+
89+
Test files are organized under:
90+
- `src/test/java/com/algorand/algosdk/unit/` - Unit test step definitions
91+
- `src/test/java/com/algorand/algosdk/integration/` - Integration test step definitions
92+
- `src/test/resources/` - Test data and response fixtures
93+
- `test-harness/features/` - Cucumber feature files
94+
95+
## Development Notes
96+
97+
- **Java 8+ Compatibility**: Maintains Java 8 compatibility with Android support (minSdkVersion 26+)
98+
- **Dependencies**: Uses Jackson for JSON, MessagePack for serialization, BouncyCastle for crypto
99+
- **Maven Profiles**: Includes IDE profile for IntelliJ compatibility with mixed Java versions
100+
- **Generated Content**: Do not manually edit generated client code - regenerate from specs instead

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# java-algorand-sdk
22

3-
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/algorand/java-algorand-sdk/tree/develop.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/algorand/java-algorand-sdk/tree/develop)
43
<!-- Commented out until https://github.com/softwaremill/maven-badges/issues/1009 resolved -->
54
<!-- [![Sonatype Central](https://maven-badges.sml.io/sonatype-central/com.algorand/algosdk/badge.svg?style=plastic)](https://maven-badges.sml.io/sonatype-central/com.algorand/algosdk/) -->
65

examples/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
<dependency>
9797
<groupId>com.algorand</groupId>
9898
<artifactId>algosdk</artifactId>
99-
<version>2.0.0</version>
99+
<version>2.9.0</version>
100100
</dependency>
101101
</dependencies>
102102

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package com.algorand.algosdk.example;
2+
3+
import com.algorand.algosdk.account.Account;
4+
import com.algorand.algosdk.builder.transaction.ApplicationCallTransactionBuilder;
5+
import com.algorand.algosdk.builder.transaction.ApplicationBaseTransactionBuilder;
6+
import com.algorand.algosdk.crypto.Address;
7+
import com.algorand.algosdk.transaction.AppBoxReference;
8+
import com.algorand.algosdk.transaction.ResourceRef;
9+
import com.algorand.algosdk.transaction.Transaction;
10+
import com.algorand.algosdk.util.Encoder;
11+
12+
import java.math.BigInteger;
13+
import java.util.Arrays;
14+
import java.util.List;
15+
16+
/**
17+
* Demonstrates the useAccess flag for handling consensus upgrade compatibility.
18+
*
19+
* The useAccess flag controls how foreign references are handled:
20+
* - useAccess=false: Uses legacy fields (accounts, foreignApps, foreignAssets, boxReferences)
21+
* - useAccess=true: Translates the same references into a unified access field
22+
*
23+
* Key insight: You use the SAME builder methods in both modes - just add .useAccess(true)!
24+
* No API changes needed for migration, making upgrades simple and safe.
25+
*/
26+
public class UseAccessFlagExample {
27+
28+
public static void main(String[] args) throws Exception {
29+
Account sender = new Account();
30+
Account otherAccount = new Account();
31+
32+
System.out.println("=== useAccess Flag Examples ===\n");
33+
System.out.println("Shows how easy it is to migrate to access field mode:");
34+
System.out.println("Just add .useAccess(true) - no other code changes needed!\n");
35+
36+
// Example 1: Legacy mode (useAccess=false, default)
37+
demonstrateLegacyMode(sender, otherAccount);
38+
39+
System.out.println("\n" + "=".repeat(50) + "\n");
40+
41+
// Example 2: Same code with useAccess=true - easy migration!
42+
demonstrateEasyMigration(sender, otherAccount);
43+
44+
System.out.println("\n" + "=".repeat(50) + "\n");
45+
46+
System.out.println("\n🎉 That's it! Migration to access field mode is just one line.");
47+
System.out.println("Advanced features like holdings() and locals() are also available with useAccess=true.");
48+
}
49+
50+
private static void demonstrateLegacyMode(Account sender, Account otherAccount) throws Exception {
51+
System.out.println("Example 1: Legacy Mode (useAccess=false)");
52+
System.out.println("-----------------------------------------");
53+
System.out.println("Using standard foreign reference methods with legacy field output");
54+
55+
// Build transaction using foreign reference methods (default behavior)
56+
Transaction txn = ApplicationCallTransactionBuilder.Builder()
57+
.sender(sender.getAddress())
58+
.applicationId(12345L)
59+
.useAccess(false) // Default mode - puts references in separate fields
60+
.accounts(Arrays.asList(otherAccount.getAddress()))
61+
.foreignApps(Arrays.asList(67890L))
62+
.foreignAssets(Arrays.asList(999L))
63+
.firstValid(BigInteger.valueOf(1000))
64+
.lastValid(BigInteger.valueOf(2000))
65+
.genesisHash(Encoder.decodeFromBase64("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="))
66+
.build();
67+
68+
// Show how references are stored in separate legacy fields
69+
System.out.printf("Result: References stored in separate fields%n");
70+
System.out.printf(" - accounts: %d entries%n", txn.accounts.size());
71+
System.out.printf(" - foreignApps: %d entries%n", txn.foreignApps.size());
72+
System.out.printf(" - foreignAssets: %d entries%n", txn.foreignAssets.size());
73+
System.out.printf(" - access field: %d entries (empty)%n", txn.access.size());
74+
System.out.println("✓ Compatible with pre-consensus upgrade networks");
75+
}
76+
77+
private static void demonstrateEasyMigration(Account sender, Account otherAccount) throws Exception {
78+
System.out.println("Example 2: Easy Migration (useAccess=true)");
79+
System.out.println("-------------------------------------------");
80+
System.out.println("SAME CODE as Example 1 - just add .useAccess(true)!");
81+
82+
// Build transaction using the EXACT SAME builder method calls as Example 1
83+
// The only difference is useAccess=true, which translates these into access field
84+
Transaction txn = ApplicationCallTransactionBuilder.Builder()
85+
.sender(sender.getAddress())
86+
.applicationId(12345L)
87+
.accounts(Arrays.asList(otherAccount.getAddress())) // Same as Example 1
88+
.foreignApps(Arrays.asList(67890L)) // Same as Example 1
89+
.foreignAssets(Arrays.asList(999L)) // Same as Example 1
90+
.firstValid(BigInteger.valueOf(1000))
91+
.lastValid(BigInteger.valueOf(2000))
92+
.genesisHash(Encoder.decodeFromBase64("SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="))
93+
.useAccess(true) // 🔥 ONLY CHANGE: Add this one line!
94+
.build();
95+
96+
// Show how the same references are now translated to access field
97+
System.out.printf("Result: Same references, different internal format%n");
98+
System.out.printf(" - accounts: %d entries (empty - translated to access)%n", txn.accounts.size());
99+
System.out.printf(" - foreignApps: %d entries (empty - translated to access)%n", txn.foreignApps.size());
100+
System.out.printf(" - foreignAssets: %d entries (empty - translated to access)%n", txn.foreignAssets.size());
101+
System.out.printf(" - access field: %d entries (contains all references)%n", txn.access.size());
102+
103+
System.out.println("\n🚀 Migration is just one line: .useAccess(true)");
104+
System.out.println("✓ No API changes required");
105+
System.out.println("✓ Same builder methods work in both modes");
106+
System.out.println("✓ Ready for post-consensus upgrade networks");
107+
}
108+
109+
110+
111+
private static String formatAccessEntry(ResourceRef ref) {
112+
if (ref.address != null) {
113+
return "Address: " + ref.address.toString().substring(0, 10) + "...";
114+
}
115+
if (ref.asset != null) {
116+
return "Asset: " + ref.asset;
117+
}
118+
if (ref.app != null) {
119+
return "App: " + ref.app;
120+
}
121+
if (ref.holding != null) {
122+
return String.format("Holding: addr_idx=%d, asset_idx=%d",
123+
ref.holding.addressIndex, ref.holding.assetIndex);
124+
}
125+
if (ref.locals != null) {
126+
return String.format("Locals: addr_idx=%d, app_idx=%d",
127+
ref.locals.addressIndex, ref.locals.appIndex);
128+
}
129+
if (ref.box != null) {
130+
return String.format("Box: app_idx=%d, name='%s'",
131+
ref.box.index, new String(ref.box.name));
132+
}
133+
return "Empty";
134+
}
135+
}

0 commit comments

Comments
 (0)