From 3b32c33b0ab979cccfb0d9073e5924cb32633722 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Wed, 25 Jan 2023 15:37:08 -0500 Subject: [PATCH 1/7] algod REST API: Add test for /v2/teal/disassemble (#433) * Add test for /v2/teal/disassemble * Fix tag declaration * Update .test-env Co-authored-by: Michael Diamant Co-authored-by: algochoi <86622919+algochoi@users.noreply.github.com> --- src/test/integration.tags | 1 + .../java/com/algorand/algosdk/integration/Stepdefs.java | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/test/integration.tags b/src/test/integration.tags index 91dace71b..f8b49f9fc 100644 --- a/src/test/integration.tags +++ b/src/test/integration.tags @@ -6,6 +6,7 @@ @auction @c2c @compile +@compile.disassemble @compile.sourcemap @dryrun @kmd diff --git a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java index 2f7aa8c9d..95548d66c 100644 --- a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java +++ b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java @@ -1184,4 +1184,11 @@ public void the_resulting_source_map_is_the_same_as_the_json(String jsonPath) th } } + @Then("disassembly of {string} matches {string}") + public void disassemblyMatches(String bytecodeFilename, String sourceFilename) throws Exception { + String disassembledSource = aclv2.TealDisassemble().source(loadResource(bytecodeFilename)).execute().body().result; + String expectedSource = new String(ResourceUtils.readResource(sourceFilename), StandardCharsets.UTF_8); + + assertThat(disassembledSource).isEqualTo(expectedSource); + } } From b57b34cc47ac4981d163957ae1c159191a40b607 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 16 Mar 2023 11:41:16 -0400 Subject: [PATCH 2/7] Documentation: Adds examples to be pulled into docs (#506) * starting example location * first example working * more examples * adding more examples * Adding atomic group examples * adding asset examples * adding codec examples * adding indexer examples * starting to add kmd examples * found interface for key export * adding kmd examples * adding randoms * adding participation examples * add fee override example * fmt * add abi type examples * adding example of tws in atc * start to add app examples * adding app examples * Adding lsig examples * adding dryrun examples * shorter api keys, fix fee override --------- Co-authored-by: egieseke Co-authored-by: John Lee Co-authored-by: Barbara Poon Co-authored-by: Lucky Baar Co-authored-by: Arthur Kepler <610274+excalq@users.noreply.github.com> --- examples/README.md | 15 ++ examples/application/approval.teal | 100 ++++++++ examples/application/approval_refactored.teal | 107 +++++++++ examples/application/clear.teal | 3 + examples/calculator/approval.teal | 181 +++++++++++++++ examples/calculator/clear.teal | 3 + examples/calculator/contract.json | 74 ++++++ examples/lsig/sample_arg.teal | 5 + examples/lsig/simple.teal | 3 + examples/pom.xml | 119 ++++++++++ examples/runit.sh | 2 + .../com/algorand/examples/ASAExamples.java | 198 ++++++++++++++++ .../main/java/com/algorand/examples/ATC.java | 184 +++++++++++++++ .../algorand/examples/AccountExamples.java | 118 ++++++++++ .../com/algorand/examples/AppExamples.java | 215 ++++++++++++++++++ .../algorand/examples/AtomicTransfers.java | 76 +++++++ .../com/algorand/examples/CodecExamples.java | 82 +++++++ .../java/com/algorand/examples/Debug.java | 72 ++++++ .../com/algorand/examples/ExampleUtils.java | 106 +++++++++ .../algorand/examples/IndexerExamples.java | 57 +++++ .../com/algorand/examples/KMDExamples.java | 120 ++++++++++ .../main/java/com/algorand/examples/LSig.java | 108 +++++++++ .../java/com/algorand/examples/Overview.java | 107 +++++++++ .../com/algorand/examples/Participation.java | 46 ++++ 24 files changed, 2101 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/application/approval.teal create mode 100644 examples/application/approval_refactored.teal create mode 100644 examples/application/clear.teal create mode 100644 examples/calculator/approval.teal create mode 100644 examples/calculator/clear.teal create mode 100644 examples/calculator/contract.json create mode 100644 examples/lsig/sample_arg.teal create mode 100644 examples/lsig/simple.teal create mode 100644 examples/pom.xml create mode 100755 examples/runit.sh create mode 100644 examples/src/main/java/com/algorand/examples/ASAExamples.java create mode 100644 examples/src/main/java/com/algorand/examples/ATC.java create mode 100644 examples/src/main/java/com/algorand/examples/AccountExamples.java create mode 100644 examples/src/main/java/com/algorand/examples/AppExamples.java create mode 100644 examples/src/main/java/com/algorand/examples/AtomicTransfers.java create mode 100644 examples/src/main/java/com/algorand/examples/CodecExamples.java create mode 100644 examples/src/main/java/com/algorand/examples/Debug.java create mode 100644 examples/src/main/java/com/algorand/examples/ExampleUtils.java create mode 100644 examples/src/main/java/com/algorand/examples/IndexerExamples.java create mode 100644 examples/src/main/java/com/algorand/examples/KMDExamples.java create mode 100644 examples/src/main/java/com/algorand/examples/LSig.java create mode 100644 examples/src/main/java/com/algorand/examples/Overview.java create mode 100644 examples/src/main/java/com/algorand/examples/Participation.java diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..16afe5dd3 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,15 @@ +Algorand Java SDK Examples + + + +Running +-------- +Package with `mvn` +```sh +mvn package +``` + +Run an example with java +```sh +java -cp target/sdk-extras-1.0-SNAPSHOT.jar com.algorand.examples.Example +``` \ No newline at end of file diff --git a/examples/application/approval.teal b/examples/application/approval.teal new file mode 100644 index 000000000..07fca803f --- /dev/null +++ b/examples/application/approval.teal @@ -0,0 +1,100 @@ +#pragma version 4 +// Handle each possible OnCompletion type. We don't have to worry about +// handling ClearState, because the ClearStateProgram will execute in that +// case, not the ApprovalProgram. +txn ApplicationID +int 0 +== +bnz handle_approve + +txn OnCompletion +int NoOp +== +bnz handle_noop + +txn OnCompletion +int OptIn +== +bnz handle_approve + +txn OnCompletion +int CloseOut +== +bnz handle_closeout + +txn OnCompletion +int UpdateApplication +== +bnz handle_updateapp + +txn OnCompletion +int DeleteApplication +== +bnz handle_deleteapp + +// Unexpected OnCompletion value. Should be unreachable. +err + +handle_noop: +// Handle NoOp + +// read global state +byte "counter" +dup +app_global_get + +// increment the value +int 1 ++ + +// store to scratch space +dup +store 0 + +// update global state +app_global_put + +// read local state for sender +int 0 +byte "counter" +app_local_get + +// increment the value +int 1 ++ +store 1 + +// update local state for sender +int 0 +byte "counter" +load 1 +app_local_put + +// load return value as approval +load 0 +return + + +handle_closeout: +// Handle CloseOut +//approval +int 1 +return + +handle_deleteapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_updateapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_approve: +int 1 +return \ No newline at end of file diff --git a/examples/application/approval_refactored.teal b/examples/application/approval_refactored.teal new file mode 100644 index 000000000..1af5a7ebe --- /dev/null +++ b/examples/application/approval_refactored.teal @@ -0,0 +1,107 @@ +#pragma version 4 +// Handle each possible OnCompletion type. We don't have to worry about +// handling ClearState, because the ClearStateProgram will execute in that +// case, not the ApprovalProgram. + +txn ApplicationID +int 0 +== +bnz handle_approve + +txn OnCompletion +int NoOp +== +bnz handle_noop + +txn OnCompletion +int OptIn +== +bnz handle_approve + +txn OnCompletion +int CloseOut +== +bnz handle_closeout + +txn OnCompletion +int UpdateApplication +== +bnz handle_updateapp + +txn OnCompletion +int DeleteApplication +== +bnz handle_deleteapp + +// Unexpected OnCompletion value. Should be unreachable. +err + +handle_noop: +// Handle NoOp + +// read global state +byte "counter" +dup +app_global_get + +// increment the value +int 1 ++ + +// store to scratch space +dup +store 0 + +// update global state +app_global_put + +// read local state for sender +int 0 +byte "counter" +app_local_get + +// increment the value +int 1 ++ +store 1 + +// update local state for sender +// update "counter" +int 0 +byte "counter" +load 1 +app_local_put + +// update "timestamp" +int 0 +byte "timestamp" +txn ApplicationArgs 0 +app_local_put + +// load return value as approval +load 0 +return + +handle_closeout: +// Handle CloseOut +//approval +int 1 +return + +handle_deleteapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_updateapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_approve: +int 1 +return \ No newline at end of file diff --git a/examples/application/clear.teal b/examples/application/clear.teal new file mode 100644 index 000000000..d793651c8 --- /dev/null +++ b/examples/application/clear.teal @@ -0,0 +1,3 @@ +#pragma version 4 +int 1 +return \ No newline at end of file diff --git a/examples/calculator/approval.teal b/examples/calculator/approval.teal new file mode 100644 index 000000000..32acbb840 --- /dev/null +++ b/examples/calculator/approval.teal @@ -0,0 +1,181 @@ +#pragma version 8 +intcblock 0 1 +bytecblock 0x151f7c75 +txn NumAppArgs +intc_0 // 0 +== +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" +== +bnz main_l9 +txna ApplicationArgs 0 +pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" +== +bnz main_l8 +txna ApplicationArgs 0 +pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" +== +bnz main_l7 +txna ApplicationArgs 0 +pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" +== +bnz main_l6 +err +main_l6: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 9 +txna ApplicationArgs 2 +btoi +store 10 +load 9 +load 10 +callsub div_3 +store 11 +bytec_0 // 0x151f7c75 +load 11 +itob +concat +log +intc_1 // 1 +return +main_l7: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 6 +txna ApplicationArgs 2 +btoi +store 7 +load 6 +load 7 +callsub sub_2 +store 8 +bytec_0 // 0x151f7c75 +load 8 +itob +concat +log +intc_1 // 1 +return +main_l8: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 3 +txna ApplicationArgs 2 +btoi +store 4 +load 3 +load 4 +callsub mul_1 +store 5 +bytec_0 // 0x151f7c75 +load 5 +itob +concat +log +intc_1 // 1 +return +main_l9: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 0 +txna ApplicationArgs 2 +btoi +store 1 +load 0 +load 1 +callsub add_0 +store 2 +bytec_0 // 0x151f7c75 +load 2 +itob +concat +log +intc_1 // 1 +return +main_l10: +txn OnCompletion +intc_0 // NoOp +== +bnz main_l12 +err +main_l12: +txn ApplicationID +intc_0 // 0 +== +assert +intc_1 // 1 +return + +// add +add_0: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 ++ +frame_bury 0 +retsub + +// mul +mul_1: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +* +frame_bury 0 +retsub + +// sub +sub_2: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +- +frame_bury 0 +retsub + +// div +div_3: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +/ +frame_bury 0 +retsub \ No newline at end of file diff --git a/examples/calculator/clear.teal b/examples/calculator/clear.teal new file mode 100644 index 000000000..e741f0e57 --- /dev/null +++ b/examples/calculator/clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +pushint 0 // 0 +return \ No newline at end of file diff --git a/examples/calculator/contract.json b/examples/calculator/contract.json new file mode 100644 index 000000000..4b23fa17e --- /dev/null +++ b/examples/calculator/contract.json @@ -0,0 +1,74 @@ +{ + "name": "Calculator", + "methods": [ + { + "name": "add", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Add a and b, return the result" + }, + { + "name": "mul", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Multiply a and b, return the result" + }, + { + "name": "sub", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Subtract b from a, return the result" + }, + { + "name": "div", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Divide a by b, return the result" + } + ], + "networks": {} +} \ No newline at end of file diff --git a/examples/lsig/sample_arg.teal b/examples/lsig/sample_arg.teal new file mode 100644 index 000000000..8f21008e4 --- /dev/null +++ b/examples/lsig/sample_arg.teal @@ -0,0 +1,5 @@ +#pragma version 5 +arg_0 +btoi +int 123 +== \ No newline at end of file diff --git a/examples/lsig/simple.teal b/examples/lsig/simple.teal new file mode 100644 index 000000000..d62986563 --- /dev/null +++ b/examples/lsig/simple.teal @@ -0,0 +1,3 @@ +#pragma version 5 +int 1 +return \ No newline at end of file diff --git a/examples/pom.xml b/examples/pom.xml new file mode 100644 index 000000000..3b06f5d04 --- /dev/null +++ b/examples/pom.xml @@ -0,0 +1,119 @@ + + 4.0.0 + com.algorand + sdk-extras + jar + 1.0-SNAPSHOT + sdk-extras + http://maven.apache.org + + + 1.8 + 1.8 + + + + + com.fasterxml.jackson.core + jackson-annotations + 2.10.0 + + + com.fasterxml.jackson.core + jackson-core + 2.10.0 + + + com.fasterxml.jackson.core + jackson-databind + 2.10.5.1 + + + com.squareup.okhttp + logging-interceptor + 2.7.5 + + + com.squareup.okhttp + okhttp + 2.7.5 + + + com.squareup.okio + okio + 1.6.0 + + + commons-codec + commons-codec + + 1.12 + + + io.gsonfire + gson-fire + 1.8.0 + + + io.swagger + swagger-annotations + 1.5.18 + + + org.apache.commons + commons-lang3 + + 3.8 + + + org.bouncycastle + bcprov-jdk15to18 + 1.66 + + + org.msgpack + jackson-dataformat-msgpack + 0.9.0 + + + org.threeten + threetenbp + 1.3.5 + + + com.google.guava + guava + 28.2-android + + + junit + junit + 3.8.1 + test + + + com.algorand + algosdk + 2.0.0 + + + + + + + maven-assembly-plugin + + + + com.algorand.examples.Example + + + + jar-with-dependencies + + + + + + diff --git a/examples/runit.sh b/examples/runit.sh new file mode 100755 index 000000000..2b3eab9f4 --- /dev/null +++ b/examples/runit.sh @@ -0,0 +1,2 @@ +mvn clean compile assembly:single +java -cp target/sdk-extras-1.0-SNAPSHOT-jar-with-dependencies.jar com.algorand.examples.Example diff --git a/examples/src/main/java/com/algorand/examples/ASAExamples.java b/examples/src/main/java/com/algorand/examples/ASAExamples.java new file mode 100644 index 000000000..e68389743 --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/ASAExamples.java @@ -0,0 +1,198 @@ +package com.algorand.examples; + +import java.util.List; + +import org.bouncycastle.pqc.crypto.ExchangePair; + +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.Asset; +import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class ASAExamples { + + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + Account acct1 = accts.get(0); + Account acct2 = accts.get(1); + + Long asaId = createAsset(algodClient, acct1); + + printAssetInfo(algodClient, asaId); + configureAsset(algodClient, acct1, asaId); + optInToAsset(algodClient, acct2, asaId); + xferAsset(algodClient, acct1, acct2, asaId); + freezeAsset(algodClient, acct1, acct2, asaId); + clawbackAsset(algodClient, acct1, acct2, asaId); + deleteAsset(algodClient, acct1, asaId); + } + + public static void printAssetInfo(AlgodClient algodClient, Long asaId) throws Exception { + // example: ASSET_INFO + // Retrieve the asset info of the newly created asset + Response assetResp = algodClient.GetAssetByID(asaId).execute(); + Asset assetInfo = assetResp.body(); + System.out.printf("Asset Name: %s\n", assetInfo.params.name); + // example: ASSET_INFO + } + + public static Long createAsset(AlgodClient algodClient, Account acct) throws Exception { + // example: ASSET_CREATE + // Account 1 creates an asset called `rug` with a total supply + // of 1000 units and sets itself to the freeze/clawback/manager/reserve roles + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + + // Under the covers, this is an AssetConfig with asset id set to 0 + Transaction createTxn = Transaction.AssetCreateTransactionBuilder().suggestedParams(sp) + .sender(acct.getAddress()) + .assetTotal(1000) + .assetDecimals(0) + .defaultFrozen(false) + .assetUnitName("rug") + .assetName("Really Useful Gift") + .url("https://path/to/my/asset/details") + .manager(acct.getAddress()) + .reserve(acct.getAddress()) + .freeze(acct.getAddress()) + .clawback(acct.getAddress()) + .build(); + + SignedTransaction signedCreateTxn = acct.signTransaction(createTxn); + Response submitResult = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedCreateTxn)).execute(); + String txId = submitResult.body().txId; + PendingTransactionResponse result = Utils.waitForConfirmation(algodClient, txId, 4); + + // Grab the asset id for the asset we just created + Long asaId = result.assetIndex; + System.out.printf("Created asset with id: %d\n", asaId); + + // example: ASSET_CREATE + return asaId; + } + + public static void configureAsset(AlgodClient algodClient, Account acct, Long asaId) throws Exception { + // example: ASSET_CONFIG + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + // Wipe the `reserve` address through an AssetConfigTransaction + Transaction reconfigureTxn = Transaction.AssetConfigureTransactionBuilder().suggestedParams(sp) + .sender(acct.getAddress()) + .assetIndex(asaId) + .manager(acct.getAddress()) + .freeze(acct.getAddress()) + .clawback(acct.getAddress()) + .strictEmptyAddressChecking(false) + .reserve(new byte[32]) + .build(); + + // example: ASSET_CONFIG + SignedTransaction signedReconfigure = acct.signTransaction(reconfigureTxn); + ExampleUtils.sendPrint(algodClient, signedReconfigure, "config asset"); + } + + public static void optInToAsset(AlgodClient algodClient, Account acct, Long asaId) throws Exception { + // example: ASSET_OPTIN + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + // Under the covers, this is an AssetTransfer from me to me for amount 0 + // with asset id set to the asset we wish to start accepting + Transaction optInTxn = Transaction.AssetAcceptTransactionBuilder().suggestedParams(sp) + .sender(acct.getAddress()) + .assetIndex(asaId) + .build(); + + // example: ASSET_OPTIN + SignedTransaction signedOptIn = acct.signTransaction(optInTxn); + ExampleUtils.sendPrint(algodClient, signedOptIn, "opt in asset"); + } + + public static void xferAsset(AlgodClient algodClient, Account sender, Account receiver, Long asaId) + throws Exception { + // example: ASSET_XFER + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + // Under the covers, this is an AssetTransfer from me to me for amount 0 + // with asset id set to the asset we wish to start accepting + Transaction xferTxn = Transaction.AssetTransferTransactionBuilder().suggestedParams(sp) + .sender(sender.getAddress()) + .assetReceiver(receiver.getAddress()) + .assetIndex(asaId) + .assetAmount(1) + .build(); + + // example: ASSET_XFER + SignedTransaction signedXfer = sender.signTransaction(xferTxn); + ExampleUtils.sendPrint(algodClient, signedXfer, "xfer asset"); + } + + public static void freezeAsset(AlgodClient algodClient, Account sender, Account receiver, Long asaId) + throws Exception { + // example: ASSET_FREEZE + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + // Set the freeze state on the account, only the account that is set to the + // freeze role + // on the asset may issue this transaction + Transaction freezeTxn = Transaction.AssetFreezeTransactionBuilder().suggestedParams(sp) + .sender(sender.getAddress()) + .freezeTarget(receiver.getAddress()) + .freezeState(true) + .assetIndex(asaId) + .build(); + + // example: ASSET_FREEZE + SignedTransaction signedFreeze = sender.signTransaction(freezeTxn); + ExampleUtils.sendPrint(algodClient, signedFreeze, "freeze asset"); + } + + public static void clawbackAsset(AlgodClient algodClient, Account sender, Account receiver, Long asaId) + throws Exception { + // example: ASSET_CLAWBACK + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + // revoke an asset from an account, only the account that is set to the clawback + // role + // on the asset may issue this transaction + Transaction clawbackTxn = Transaction.AssetClawbackTransactionBuilder().suggestedParams(sp) + .sender(sender.getAddress()) + .assetClawbackFrom(receiver.getAddress()) + .assetReceiver(sender.getAddress()) + .assetIndex(asaId) + .assetAmount(1) + .build(); + + // example: ASSET_CLAWBACK + SignedTransaction signedClawback = sender.signTransaction(clawbackTxn); + ExampleUtils.sendPrint(algodClient, signedClawback, "clawback asset"); + } + + public static void deleteAsset(AlgodClient algodClient, Account acct, Long asaId) throws Exception { + // example: ASSET_DELETE + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + // Under the covers, an AssetDestroyTransaction is an AssetConfig with all of + // its + // configurable fields set to empty + // All units of the asset _must_ be owned by the creator account and this + // transaction _must_ + // be issued by the account set to the manager role on the asset + Transaction destroyTxn = Transaction.AssetDestroyTransactionBuilder().suggestedParams(sp) + .sender(acct.getAddress()) + .assetIndex(asaId) + .build(); + + // example: ASSET_DELETE + SignedTransaction signedDestroy = acct.signTransaction(destroyTxn); + ExampleUtils.sendPrint(algodClient, signedDestroy, "clawback asset"); + } +} diff --git a/examples/src/main/java/com/algorand/examples/ATC.java b/examples/src/main/java/com/algorand/examples/ATC.java new file mode 100644 index 000000000..a9423c0c3 --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/ATC.java @@ -0,0 +1,184 @@ +package com.algorand.examples; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.codec.binary.Hex; + +import com.algorand.algosdk.abi.Contract; +import com.algorand.algosdk.abi.Method; +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.builder.transaction.ApplicationCreateTransactionBuilder; +import com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder; +import com.algorand.algosdk.builder.transaction.PaymentTransactionBuilder; +import com.algorand.algosdk.crypto.TEALProgram; +import com.algorand.algosdk.logic.StateSchema; +import com.algorand.algosdk.transaction.AppBoxReference; +import com.algorand.algosdk.transaction.AtomicTransactionComposer; +import com.algorand.algosdk.transaction.MethodCallParams; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.transaction.TransactionWithSigner; +import com.algorand.algosdk.transaction.AtomicTransactionComposer.ExecuteResult; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.CompileResponse; +import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class ATC { + + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + Account acct = accts.get(0); + + Long appId = deployApp(algodClient, acct); + + // Get suggested params from client + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + + // example: ATC_CREATE + AtomicTransactionComposer atc = new AtomicTransactionComposer(); + // example: ATC_CREATE + + // example: ATC_ADD_TRANSACTION + // Create a transaction + Transaction ptxn = PaymentTransactionBuilder.Builder().amount(10000).suggestedParams(sp) + .sender(acct.getAddress()).receiver(acct.getAddress()).build(); + + // Construct TransactionWithSigner + TransactionWithSigner tws = new TransactionWithSigner(ptxn, + acct.getTransactionSigner()); + + // Pass TransactionWithSigner to atc + atc.addTransaction(tws); + // example: ATC_ADD_TRANSACTION + + // example: ATC_CONTRACT_INIT + // Read the json from disk + String jsonContract = Files.readString(Paths.get("calculator/contract.json")); + // Create Contract from Json + Contract contract = Encoder.decodeFromJson(jsonContract, Contract.class); + // example: ATC_CONTRACT_INIT + + // example: ATC_ADD_METHOD_CALL + // create methodCallParams by builder (or create by constructor) for add method + List methodArgs = new ArrayList(); + methodArgs.add(1); + methodArgs.add(1); + + MethodCallTransactionBuilder mctb = MethodCallTransactionBuilder.Builder(); + + MethodCallParams mcp = mctb.applicationId(appId).signer(acct.getTransactionSigner()) + .sender(acct.getAddress()) + .method(contract.getMethodByName("add")).methodArguments(methodArgs) + .onComplete(Transaction.OnCompletion.NoOpOC).suggestedParams(sp).build(); + + atc.addMethodCall(mcp); + // example: ATC_ADD_METHOD_CALL + + // example: ATC_RESULTS + ExecuteResult res = atc.execute(algodClient, 2); + System.out.printf("App call (%s) confirmed in round %d\n", res.txIDs, res.confirmedRound); + res.methodResults.forEach(methodResult -> { + System.out.printf("Result from calling '%s' method: %s\n", methodResult.method.name, + methodResult.value); + }); + // example: ATC_RESULTS + + // example: ATC_BOX_REF + MethodCallTransactionBuilder mct_builder = MethodCallTransactionBuilder.Builder(); + + List boxRefs = new ArrayList<>(); + boxRefs.add(new AppBoxReference(appId.intValue(), "cool-box".getBytes())); + MethodCallParams box_ref_mcp = mct_builder + .suggestedParams(sp) + .applicationId(appId) + .sender(acct.getAddress()) + .method(contract.getMethodByName("add")) + .methodArguments(methodArgs) + .signer(acct.getTransactionSigner()) + .onComplete(Transaction.OnCompletion.NoOpOC) + // Include reference to a box so the app logic may + // use it during evaluation + .boxReferences(boxRefs) + .build(); + // example: ATC_BOX_REF + } + + public static void atcWithTws(AlgodClient algodClient, Account account) throws Exception { + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + + Transaction ptxn = PaymentTransactionBuilder.Builder().amount(10000).suggestedParams(sp) + .sender(account.getAddress()).receiver(account.getAddress()).build(); + + // Construct TransactionWithSigner + TransactionWithSigner tws = new TransactionWithSigner(ptxn, + account.getTransactionSigner()); + + MethodCallTransactionBuilder mctb = MethodCallTransactionBuilder.Builder(); + + Method m = new Method("doit(pay,bool)void"); + + List methodArgs = new ArrayList<>(); + Boolean isHeads = true; + methodArgs.add(tws); + methodArgs.add(isHeads); + MethodCallParams mcp = mctb.applicationId(123l).signer(account.getTransactionSigner()) + .sender(account.getAddress()) + .method(m).methodArguments(methodArgs) + .onComplete(Transaction.OnCompletion.NoOpOC).suggestedParams(sp).build(); + + AtomicTransactionComposer atc = new AtomicTransactionComposer(); + atc.addMethodCall(mcp); + + List stxns = atc.gatherSignatures(); + System.out.printf("Created %d transactions\n", stxns.size()); + // should be the app call + SignedTransaction stxn = stxns.get(1); + List args = stxn.tx.applicationArgs; + for (int i = 0; i < args.size(); i++) { + System.out.printf("Arg %d is %s\n", i, Hex.encodeHexString(args.get(i))); + } + } + + public static Long deployApp(AlgodClient algodClient, Account acct1) throws Exception { + String approvalSource = Files.readString(Paths.get("calculator/approval.teal")); + String clearSource = Files.readString(Paths.get("calculator/clear.teal")); + + CompileResponse approvalResponse = algodClient.TealCompile().source(approvalSource.getBytes()).execute() + .body(); + CompileResponse clearResponse = algodClient.TealCompile().source(clearSource.getBytes()).execute() + .body(); + + TEALProgram approvalProg = new TEALProgram(approvalResponse.result); + TEALProgram clearProg = new TEALProgram(clearResponse.result); + + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + + StateSchema schema = new StateSchema(0, 0); + + Transaction appCreate = ApplicationCreateTransactionBuilder.Builder().sender(acct1.getAddress()) + .approvalProgram(approvalProg).clearStateProgram(clearProg).localStateSchema(schema) + .globalStateSchema(schema) + .suggestedParams(sp).build(); + SignedTransaction signedAppCreate = acct1.signTransaction(appCreate); + + Response createResult = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedAppCreate)).execute(); + + PendingTransactionResponse result = Utils.waitForConfirmation(algodClient, createResult.body().txId, 4); + + return result.applicationIndex; + } + +} diff --git a/examples/src/main/java/com/algorand/examples/AccountExamples.java b/examples/src/main/java/com/algorand/examples/AccountExamples.java new file mode 100644 index 000000000..84d21d1c2 --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/AccountExamples.java @@ -0,0 +1,118 @@ +package com.algorand.examples; + +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.crypto.Ed25519PublicKey; +import com.algorand.algosdk.crypto.MultisigAddress; +import com.algorand.algosdk.mnemonic.Mnemonic; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class AccountExamples { + + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + + // Grab some accounts from the sandbox kmd + Account acct1 = accts.get(0); + Account acct2 = accts.get(1); + Account acct3 = accts.get(2); + + MultisigAddress msig = createMsig(acct1, acct2, acct3); + + // Pay the multisig address so it can issue transactions for the demo + Response spResponse = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = spResponse.body(); + Transaction ptxn = Transaction.PaymentTransactionBuilder() + .sender(acct1.getAddress()).amount(1000000).receiver(msig.toAddress()) + .suggestedParams(sp).build(); + SignedTransaction stxn = acct1.signTransaction(ptxn); + ExampleUtils.sendPrint(algodClient, stxn, "seed msig"); + + // example: MULTISIG_SIGN + // Construct transaction with sender as address of msig + Transaction msigPayTxn = Transaction.PaymentTransactionBuilder() + .sender(msig.toAddress()) + .amount(1000) + .receiver(acct1.getAddress()) + .suggestedParams(sp) + .build(); + + // For each subsig, sign or append to the existing partially signed transaction + SignedTransaction signedMsigPayTxn = acct1.signMultisigTransaction(msig, msigPayTxn); + signedMsigPayTxn = acct2.appendMultisigTransaction(msig, signedMsigPayTxn); + Response msigSubResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedMsigPayTxn)).execute(); + // example: MULTISIG_SIGN + ExampleUtils.printTxnResults(algodClient, msigSubResponse.body(), "msig pay"); + + rekeyAcct(algodClient, acct1, acct2); + + } + public static Account recoverFromMnemonic() throws GeneralSecurityException { + // example: ACCOUNT_RECOVER_MNEMONIC + // Space delimited 25 word mnemonic + String mn = "cost piano sample enough south bar diet garden nasty mystery mesh sadness convince bacon best patch surround protect drum actress entire vacuum begin abandon hair"; + // We can get the private key + byte[] key = Mnemonic.toKey(mn); + // Or just init the account directly from the mnemonic + Account acct = new Account(mn); + // example: ACCOUNT_RECOVER_MNEMONIC + return acct; + } + + public static MultisigAddress createMsig(Account addr1, Account addr2, Account addr3) + throws NoSuchAlgorithmException { + // example: MULTISIG_CREATE + int version = 1; // no other versions at the time of writing + int threshold = 2; // we're making a 2/3 msig + + // Populate a list of Ed25519 pubkeys + List accts = new ArrayList<>(); + accts.add(addr1.getEd25519PublicKey()); + accts.add(addr2.getEd25519PublicKey()); + accts.add(addr3.getEd25519PublicKey()); + // create the MultisigAddress object + MultisigAddress msig = new MultisigAddress(version, threshold, accts); + System.out.printf("msig address: %s\n", msig.toAddress().toString()); + // example: MULTISIG_CREATE + return msig; + } + + public static void rekeyAcct(AlgodClient algodClient, Account acct1, Account acct2) throws Exception { + TransactionParametersResponse sp = algodClient.TransactionParams().execute().body(); + + // example: ACCOUNT_REKEY + + // Any kind of transaction can contain a rekey, here we use a Payment + // transaction + Transaction rekeyTxn = Transaction.PaymentTransactionBuilder().sender(acct1.getAddress()) + .receiver(acct1.getAddress()).suggestedParams(sp).rekey(acct2.getAddress()).build(); + SignedTransaction signedRekeyTxn = acct1.signTransaction(rekeyTxn); + Response resp = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedRekeyTxn)).execute(); + ExampleUtils.printTxnResults(algodClient, resp.body(), "rekey"); + + // Create a transaction to rekey it back + Transaction rekeyBack = Transaction.PaymentTransactionBuilder().sender(acct1.getAddress()) + .receiver(acct1.getAddress()).suggestedParams(sp).rekey(acct1.getAddress()).build(); + + // note we sign with acct2's key + SignedTransaction signedRekeyBack = acct2.signTransaction(rekeyBack); + Response rekeyBackResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedRekeyBack)).execute(); + ExampleUtils.printTxnResults(algodClient, rekeyBackResponse.body(), "rekey back"); + // example: ACCOUNT_REKEY + } + +} \ No newline at end of file diff --git a/examples/src/main/java/com/algorand/examples/AppExamples.java b/examples/src/main/java/com/algorand/examples/AppExamples.java new file mode 100644 index 000000000..811b3676e --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/AppExamples.java @@ -0,0 +1,215 @@ +package com.algorand.examples; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.builder.transaction.ApplicationCallTransactionBuilder; +import com.algorand.algosdk.builder.transaction.ApplicationClearTransactionBuilder; +import com.algorand.algosdk.builder.transaction.ApplicationCloseTransactionBuilder; +import com.algorand.algosdk.builder.transaction.ApplicationCreateTransactionBuilder; +import com.algorand.algosdk.builder.transaction.ApplicationDeleteTransactionBuilder; +import com.algorand.algosdk.builder.transaction.ApplicationOptInTransactionBuilder; +import com.algorand.algosdk.builder.transaction.ApplicationUpdateTransactionBuilder; +import com.algorand.algosdk.crypto.TEALProgram; +import com.algorand.algosdk.logic.StateSchema; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.CompileResponse; +import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class AppExamples { + + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + + Account creator = accts.get(0); + Account user = accts.get(1); + + // example: APP_SCHEMA + int localInts = 1; + int localBytes = 1; + int globalInts = 1; + int globalBytes = 0; + StateSchema localSchema = new StateSchema(localInts, localBytes); + StateSchema globalSchema = new StateSchema(globalInts, globalBytes); + // example: APP_SCHEMA + + // example: APP_SOURCE + // Read in the `teal` source files as a string + String approvalSource = Files.readString(Paths.get("application/approval.teal")); + String clearSource = Files.readString(Paths.get("application/clear.teal")); + // example: APP_SOURCE + + // example: APP_COMPILE + CompileResponse approvalResponse = algodClient.TealCompile().source(approvalSource.getBytes()).execute() + .body(); + CompileResponse clearResponse = algodClient.TealCompile().source(clearSource.getBytes()).execute() + .body(); + + TEALProgram approvalProg = new TEALProgram(approvalResponse.result); + TEALProgram clearProg = new TEALProgram(clearResponse.result); + // example: APP_COMPILE + + // example: APP_CREATE + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + + Transaction appCreate = ApplicationCreateTransactionBuilder.Builder() + .sender(creator.getAddress()) + .suggestedParams(sp) + .approvalProgram(approvalProg) + .clearStateProgram(clearProg) + .localStateSchema(localSchema) + .globalStateSchema(globalSchema) + .build(); + + SignedTransaction signedAppCreate = creator.signTransaction(appCreate); + Response createResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedAppCreate)).execute(); + + PendingTransactionResponse result = Utils.waitForConfirmation(algodClient, createResponse.body().txId, 4); + Long appId = result.applicationIndex; + System.out.printf("Created application with id: %d\n", appId); + // example: APP_CREATE + + // example: APP_OPTIN + Transaction optInTxn = ApplicationOptInTransactionBuilder.Builder() + .sender(user.getAddress()) + .suggestedParams(sp) + .applicationId(appId) + .build(); + + SignedTransaction signedOptIn = user.signTransaction(optInTxn); + Response optInResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedOptIn)).execute(); + + PendingTransactionResponse optInResult = Utils.waitForConfirmation(algodClient, optInResponse.body().txId, 4); + assert optInResult.confirmedRound > 0; + // example: APP_OPTIN + + // example: APP_NOOP + Transaction noopTxn = ApplicationCallTransactionBuilder.Builder() + .sender(user.getAddress()) + .suggestedParams(sp) + .applicationId(appId) + .build(); + + SignedTransaction signedNoop = user.signTransaction(noopTxn); + Response noopResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedNoop)).execute(); + + PendingTransactionResponse noopResult = Utils.waitForConfirmation(algodClient, noopResponse.body().txId, 4); + assert noopResult.confirmedRound > 0; + // example: APP_NOOP + + // example: APP_READ_STATE + // example: APP_READ_STATE + + // example: APP_UPDATE + String approvalSourceUpdated = Files.readString(Paths.get("application/approval_refactored.teal")); + CompileResponse approvalUpdatedResponse = algodClient.TealCompile().source(approvalSourceUpdated.getBytes()) + .execute() + .body(); + TEALProgram approvalProgUpdated = new TEALProgram(approvalUpdatedResponse.result); + + Transaction appUpdate = ApplicationUpdateTransactionBuilder.Builder() + .sender(creator.getAddress()) + .suggestedParams(sp) + .applicationId(appId) + .approvalProgram(approvalProgUpdated) + .clearStateProgram(clearProg) + .build(); + + SignedTransaction signedAppUpdate = creator.signTransaction(appUpdate); + Response updateResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedAppUpdate)).execute(); + PendingTransactionResponse updateResult = Utils.waitForConfirmation(algodClient, updateResponse.body().txId, 4); + assert updateResult.confirmedRound > 0; + // example: APP_UPDATE + + // example: APP_CALL + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss"); + Date date = new Date(System.currentTimeMillis()); + + List appArgs = new ArrayList(); + appArgs.add(formatter.format(date).toString().getBytes()); + + // create unsigned transaction + Transaction callTransaction = ApplicationCallTransactionBuilder.Builder() + .sender(user.getAddress()) + .suggestedParams(sp) + .applicationId(appId) + .args(appArgs) + .build(); + + SignedTransaction signedCallTransaction = user.signTransaction(callTransaction); + Response callResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedCallTransaction)).execute(); + + PendingTransactionResponse callResult = Utils.waitForConfirmation(algodClient, callResponse.body().txId, 4); + assert callResult.confirmedRound > 0; + // display results + if (callResult.globalStateDelta != null) { + System.out.printf("\tGlobal state: %s\n", callResult.globalStateDelta); + } + + if (callResult.localStateDelta != null) { + System.out.printf("\tLocal state: %s\n", callResult.localStateDelta); + } + // example: APP_CALL + + // example: APP_CLOSEOUT + Transaction closeOutTxn = ApplicationCloseTransactionBuilder.Builder() + .sender(user.getAddress()) + .suggestedParams(sp) + .applicationId(appId) + .build(); + + SignedTransaction signedCloseOut = user.signTransaction(closeOutTxn); + Response closeOutResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedCloseOut)).execute(); + + PendingTransactionResponse closeOutResult = Utils.waitForConfirmation(algodClient, closeOutResponse.body().txId, + 4); + assert closeOutResult.confirmedRound > 0; + // example: APP_CLOSEOUT + + // example: APP_DELETE + Transaction appDelete = ApplicationDeleteTransactionBuilder.Builder() + .sender(creator.getAddress()) + .suggestedParams(sp) + .applicationId(appId) + .build(); + + SignedTransaction signedAppDelete = creator.signTransaction(appDelete); + Response deleteResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(signedAppDelete)).execute(); + PendingTransactionResponse deleteResult = Utils.waitForConfirmation(algodClient, deleteResponse.body().txId, 4); + assert deleteResult.confirmedRound > 0; + // example: APP_DELETE + + // example: APP_CLEAR + Transaction clearTxn = ApplicationClearTransactionBuilder.Builder() + .sender(user.getAddress()) + .suggestedParams(sp) + .applicationId(appId) + .build(); + + SignedTransaction signedClear = user.signTransaction(clearTxn); + // ... sign, send, wait + // example: APP_CLEAR + } + +} \ No newline at end of file diff --git a/examples/src/main/java/com/algorand/examples/AtomicTransfers.java b/examples/src/main/java/com/algorand/examples/AtomicTransfers.java new file mode 100644 index 000000000..95fc9c86b --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/AtomicTransfers.java @@ -0,0 +1,76 @@ +package com.algorand.examples; + +import java.util.List; + +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.crypto.Digest; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.transaction.TxGroup; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class AtomicTransfers { + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + + Account acct1 = accts.get(0); + Account acct2 = accts.get(1); + + // example: ATOMIC_CREATE_TXNS + Response rsp = algodClient.TransactionParams().execute(); + + // payment from account 1 to account 2 + Transaction ptxn1 = Transaction.PaymentTransactionBuilder().sender(acct1.getAddress()) + .amount(1000000).receiver(acct2.getAddress()).suggestedParams(rsp.body()).build(); + // txn_1 = transaction.PaymentTxn(addr1, suggested_params, addr2, 100000) + + // payment from account 2 to account 1 + Transaction ptxn2 = Transaction.PaymentTransactionBuilder().sender(acct2.getAddress()) + .amount(2000000).receiver(acct1.getAddress()).suggestedParams(rsp.body()).build(); + // example: ATOMIC_CREATE_TXNS + + // example: ATOMIC_GROUP_TXNS + // Assign group id to the transactions (order matters!) + Transaction[] txs = TxGroup.assignGroupID(ptxn1, ptxn2); + + // Or, equivalently + // compute group id and assign it to transactions + Digest gid = TxGroup.computeGroupID(txs); + ptxn1.group = gid; + ptxn2.group = gid; + // example: ATOMIC_GROUP_TXNS + + // example: ATOMIC_GROUP_SIGN + // sign transactions + SignedTransaction signedPtxn1 = acct1.signTransaction(ptxn1); + SignedTransaction signedPtxn2 = acct2.signTransaction(ptxn2); + // # example: ATOMIC_GROUP_SIGN + + // example: ATOMIC_GROUP_ASSEMBLE + // combine the signed transactions into a single list + SignedTransaction[] stxns = new SignedTransaction[] { signedPtxn1, signedPtxn2 }; + // example: ATOMIC_GROUP_ASSEMBLE + + // example: ATOMIC_GROUP_SEND + // Only the first transaction id is returned + Response txResponse = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(stxns)).execute(); + String txid = txResponse.body().txId; + + // Wait for the transaction id to be confirmed + // If the results from other transactions are needed, grab the txid from those + // directly and + // call waitForConfirmation on each + PendingTransactionResponse txResult = Utils.waitForConfirmation(algodClient, txid, 4); + System.out.printf("Transaction %s confirmed in round %d\n", txid, txResult.confirmedRound); + // example: ATOMIC_GROUP_SEND + } + +} \ No newline at end of file diff --git a/examples/src/main/java/com/algorand/examples/CodecExamples.java b/examples/src/main/java/com/algorand/examples/CodecExamples.java new file mode 100644 index 000000000..0507bd63b --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/CodecExamples.java @@ -0,0 +1,82 @@ +package com.algorand.examples; + +import java.math.BigInteger; +import java.util.List; + +import org.apache.commons.codec.binary.Hex; + +import com.algorand.algosdk.abi.ABIType; +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class CodecExamples { + + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + Account acct = accts.get(0); + + // example: CODEC_ADDRESS + String addrAsStr = "4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4"; + // Instantiate a new Address object with string + Address addr = new Address(addrAsStr); + // Or with the bytes + Address addrAgain = new Address(addr.getBytes()); + assert addrAgain.equals(addr); + // example: CODEC_ADDRESS + + // example: CODEC_BASE64 + String encodedStr = "SGksIEknbSBkZWNvZGVkIGZyb20gYmFzZTY0"; + byte[] decodedBytes = Encoder.decodeFromBase64(encodedStr); + String reEncodedStr = Encoder.encodeToBase64(decodedBytes); + assert encodedStr.equals(reEncodedStr); + // example: CODEC_BASE64 + + // example: CODEC_UINT64 + BigInteger val = BigInteger.valueOf(1337); + byte[] encodedVal = Encoder.encodeUint64(val); + BigInteger decodedVal = Encoder.decodeUint64(encodedVal); + assert val.equals(decodedVal); + // example: CODEC_UINT64 + + // example: CODEC_TRANSACTION_UNSIGNED + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + // Wipe the `reserve` address through an AssetConfigTransaction + Transaction ptxn = Transaction.PaymentTransactionBuilder().suggestedParams(sp) + .sender(acct.getAddress()).receiver(acct.getAddress()).amount(100).build(); + + byte[] encodedTxn = Encoder.encodeToMsgPack(ptxn); + + Transaction decodedTxn = Encoder.decodeFromMsgPack(encodedTxn, Transaction.class); + assert decodedTxn.equals(ptxn); + // example: CODEC_TRANSACTION_UNSIGNED + + // example: CODEC_TRANSACTION_SIGNED + SignedTransaction signedTxn = acct.signTransaction(ptxn); + byte[] encodedSignedTxn = Encoder.encodeToMsgPack(signedTxn); + + SignedTransaction decodedSignedTransaction = Encoder.decodeFromMsgPack(encodedSignedTxn, + SignedTransaction.class); + assert decodedSignedTransaction.equals(signedTxn); + // example: CODEC_TRANSACTION_SIGNED + + // example: CODEC_ABI_TYPES + ABIType tupleCodec = ABIType.valueOf("(uint64,bool,string)"); + Object[] data = new Object[]{123, false, "hi"}; + byte[] encoded = tupleCodec.encode(data); + System.out.printf("Encoded: '%s'\n", Hex.encodeHexString(encoded)); + + ABIType boolCodec = ABIType.valueOf("bool"); + byte[] singleEncoded = boolCodec.encode(true); + System.out.printf("Single Encoded: '%s'\n", Hex.encodeHexString(singleEncoded)); + // example: CODEC_ABI_TYPES + + } +} diff --git a/examples/src/main/java/com/algorand/examples/Debug.java b/examples/src/main/java/com/algorand/examples/Debug.java new file mode 100644 index 000000000..d0a06d714 --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/Debug.java @@ -0,0 +1,72 @@ +package com.algorand.examples; + +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import com.algorand.algosdk.abi.Contract; +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder; +import com.algorand.algosdk.transaction.AtomicTransactionComposer; +import com.algorand.algosdk.transaction.MethodCallParams; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.DryrunRequest; +import com.algorand.algosdk.v2.client.model.DryrunResponse; +import com.algorand.algosdk.v2.client.model.DryrunTxnResult; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class Debug { + + + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + Account acct = accts.get(0); + + Long appId = ATC.deployApp(algodClient, acct); + + String jsonContract = Files.readString(Paths.get("calculator/contract.json")); + Contract contract = Encoder.decodeFromJson(jsonContract, Contract.class); + + // example: DEBUG_DRYRUN_DUMP + // Set up the transactions we'd like to dryrun + AtomicTransactionComposer atc = new AtomicTransactionComposer(); + + List methodArgs = new ArrayList(); + methodArgs.add(1); + methodArgs.add(1); + + TransactionParametersResponse sp = algodClient.TransactionParams().execute().body(); + + MethodCallTransactionBuilder mctb = MethodCallTransactionBuilder.Builder(); + MethodCallParams mcp = mctb.applicationId(appId).signer(acct.getTransactionSigner()) + .suggestedParams(sp) + .sender(acct.getAddress()) + .method(contract.getMethodByName("add")) + .methodArguments(methodArgs) + .onComplete(Transaction.OnCompletion.NoOpOC) + .build(); + atc.addMethodCall(mcp); + + DryrunRequest drr = Utils.createDryrun(algodClient, atc.gatherSignatures(), "", 0L, 0L); + + FileOutputStream outfile = new FileOutputStream("my-dryrun.msgpack"); + outfile.write(Encoder.encodeToMsgPack(drr)); + outfile.close(); + // example: DEBUG_DRYRUN_DUMP + + // example: DEBUG_DRYRUN_SUBMIT + Response resp = algodClient.TealDryrun().request(drr).execute(); + DryrunResponse drResp = resp.body(); + DryrunTxnResult dryrunTxnResult = drResp.txns.get(0); + System.out.println(dryrunTxnResult.appCallMessages); + System.out.println(Utils.appTrace(dryrunTxnResult)); + // example: DEBUG_DRYRUN_SUBMIT + } +} diff --git a/examples/src/main/java/com/algorand/examples/ExampleUtils.java b/examples/src/main/java/com/algorand/examples/ExampleUtils.java new file mode 100644 index 000000000..07fc8c1e3 --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/ExampleUtils.java @@ -0,0 +1,106 @@ +package com.algorand.examples; + +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.IndexerClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.kmd.client.KmdClient; +import com.algorand.algosdk.kmd.client.api.KmdApi; +import com.algorand.algosdk.kmd.client.model.APIV1Wallet; +import com.algorand.algosdk.kmd.client.model.ExportKeyRequest; +import com.algorand.algosdk.kmd.client.model.InitWalletHandleTokenRequest; +import com.algorand.algosdk.kmd.client.model.ListKeysRequest; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.kmd.client.ApiException; + +public class ExampleUtils { + + private static String kmd_host = "http://localhost:4002"; + private static String kmd_token = "a".repeat(64); + private static KmdApi kmd = null; + + private static String algod_host = "http://localhost"; + private static int algod_port = 4001; + private static String algod_token = "a".repeat(64); + + private static String indexer_host = "http://localhost"; + private static int indexer_port = 8980; + private static String indexer_token = "a".repeat(64); + + public static AlgodClient getAlgodClient() { + return new AlgodClient(algod_host, algod_port, algod_token); + } + + public static IndexerClient getIndexerClient() { + return new IndexerClient(indexer_host, indexer_port, indexer_token); + } + + public static List getSandboxAccounts() throws Exception { + // Initialize KMD v1 client + KmdClient kmdClient = new KmdClient(); + kmdClient.setBasePath(kmd_host); + kmdClient.setApiKey(kmd_token); + kmd = new KmdApi(kmdClient); + + // Get accounts from sandbox. + String walletHandle = getDefaultWalletHandle(); + List
addresses = getWalletAccounts(walletHandle); + + List accts = new ArrayList<>(); + for (Address addr : addresses) { + byte[] pk = lookupPrivateKey(addr, walletHandle); + accts.add(new Account(pk)); + } + return accts; + } + + public static byte[] lookupPrivateKey(Address addr, String walletHandle) throws ApiException { + ExportKeyRequest req = new ExportKeyRequest(); + req.setAddress(addr.toString()); + req.setWalletHandleToken(walletHandle); + req.setWalletPassword(""); + return kmd.exportKey(req).getPrivateKey(); + } + + public static String getDefaultWalletHandle() throws ApiException { + for (APIV1Wallet w : kmd.listWallets().getWallets()) { + if (w.getName().equals("unencrypted-default-wallet")) { + InitWalletHandleTokenRequest tokenreq = new InitWalletHandleTokenRequest(); + tokenreq.setWalletId(w.getId()); + tokenreq.setWalletPassword(""); + return kmd.initWalletHandleToken(tokenreq).getWalletHandleToken(); + } + } + throw new RuntimeException("Default wallet not found."); + } + + public static List
getWalletAccounts(String walletHandle) throws ApiException, NoSuchAlgorithmException { + List
accounts = new ArrayList<>(); + ListKeysRequest keysRequest = new ListKeysRequest(); + keysRequest.setWalletHandleToken(walletHandle); + for (String addr : kmd.listKeysInWallet(keysRequest).getAddresses()) { + accounts.add(new Address(addr)); + } + return accounts; + } + + public static void sendPrint(AlgodClient algodClient, SignedTransaction stxn, String name) throws Exception { + Response submitResult = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(stxn)).execute(); + printTxnResults(algodClient, submitResult.body(), name); + } + + public static void printTxnResults(AlgodClient client, PostTransactionsResponse ptr, String name) throws Exception { + PendingTransactionResponse result = Utils.waitForConfirmation(client, ptr.txId, 4); + System.out.printf("%s\ttransaction (%s) was confirmed in round %d\n", name, ptr.txId, result.confirmedRound); + } +} \ No newline at end of file diff --git a/examples/src/main/java/com/algorand/examples/IndexerExamples.java b/examples/src/main/java/com/algorand/examples/IndexerExamples.java new file mode 100644 index 000000000..c269af75a --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/IndexerExamples.java @@ -0,0 +1,57 @@ +package com.algorand.examples; + +import com.algorand.algosdk.v2.client.common.IndexerClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.Asset; +import com.algorand.algosdk.v2.client.model.AssetResponse; +import com.algorand.algosdk.v2.client.model.TransactionsResponse; + +public class IndexerExamples { + + public static void main(String[] args) throws Exception { + // example: CREATE_INDEXER_CLIENT + String indexerHost = "http://localhost"; + int indexerPort = 8980; + String indexerToken = "a".repeat(64); + IndexerClient indexerClient = new IndexerClient(indexerHost, indexerPort, indexerToken); + // example: CREATE_INDEXER_CLIENT + + // example: INDEXER_LOOKUP_ASSET + Long asaId = 25l; + Response assetResponse = indexerClient.lookupAssetByID(asaId).execute(); + Asset assetInfo = assetResponse.body().asset; + System.out.printf("Name for %d: %s\n", asaId, assetInfo.params.name); + // example: INDEXER_LOOKUP_ASSET + + // example: INDEXER_SEARCH_MIN_AMOUNT + Response transactionSearchResult = indexerClient.searchForTransactions() + .minRound(10l).maxRound(500l).currencyGreaterThan(10l).execute(); + TransactionsResponse txResp = transactionSearchResult.body(); + System.out.printf("Found %d transactions that match criteria\n", txResp.transactions.size()); + // example: INDEXER_SEARCH_MIN_AMOUNT + + // example: INDEXER_PAGINATE_RESULTS + String nextToken = ""; + boolean hasResults = true; + // Start with empty nextToken and while there are + // results in the transaction results, query again with the next page + while(hasResults){ + Response searchResults = indexerClient.searchForTransactions().minRound(1000l) + .maxRound(1500l).currencyGreaterThan(10l).next(nextToken).execute(); + TransactionsResponse txnRes = searchResults.body(); + // + // ... do something with transaction results + // + hasResults = txnRes.transactions.size()>0; + nextToken = txnRes.nextToken; + } + // example: INDEXER_PAGINATE_RESULTS + + // example: INDEXER_PREFIX_SEARCH + byte[] prefix = new String("showing prefix").getBytes(); + Response prefixResults = indexerClient.searchForTransactions().notePrefix(prefix).execute(); + // ... + // example: INDEXER_PREFIX_SEARCH + } + +} diff --git a/examples/src/main/java/com/algorand/examples/KMDExamples.java b/examples/src/main/java/com/algorand/examples/KMDExamples.java new file mode 100644 index 000000000..9f20115bb --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/KMDExamples.java @@ -0,0 +1,120 @@ +package com.algorand.examples; + +import org.bouncycastle.util.Arrays; + +import com.algorand.algosdk.kmd.client.ApiException; +import com.algorand.algosdk.kmd.client.KmdClient; +import com.algorand.algosdk.kmd.client.api.KmdApi; +import com.algorand.algosdk.kmd.client.model.APIV1POSTKeyExportResponse; +import com.algorand.algosdk.kmd.client.model.APIV1POSTKeyImportResponse; +import com.algorand.algosdk.kmd.client.model.APIV1POSTKeyResponse; +import com.algorand.algosdk.kmd.client.model.APIV1POSTMasterKeyExportResponse; +import com.algorand.algosdk.kmd.client.model.APIV1POSTWalletInitResponse; +import com.algorand.algosdk.kmd.client.model.APIV1POSTWalletResponse; +import com.algorand.algosdk.kmd.client.model.APIV1Wallet; +import com.algorand.algosdk.kmd.client.model.CreateWalletRequest; +import com.algorand.algosdk.kmd.client.model.ExportKeyRequest; +import com.algorand.algosdk.kmd.client.model.ExportMasterKeyRequest; +import com.algorand.algosdk.kmd.client.model.GenerateKeyRequest; +import com.algorand.algosdk.kmd.client.model.ImportKeyRequest; +import com.algorand.algosdk.kmd.client.model.InitWalletHandleTokenRequest; +import com.algorand.algosdk.mnemonic.Mnemonic; + +public class KMDExamples { + + public static void main(String[] args) throws ApiException { + // example: KMD_CREATE_CLIENT + String kmdHost = "http://localhost:4002"; + String kmdToken = "a".repeat(64); + + KmdClient kmdClient = new KmdClient(); + kmdClient.setBasePath(kmdHost); + kmdClient.setApiKey(kmdToken); + + KmdApi kmd = new KmdApi(kmdClient); + // example: KMD_CREATE_CLIENT + + + String walletName = "MyNewWallet"; + String password = "supersecretpassword"; + + // example: KMD_CREATE_WALLET + // create a new CreateWalletRequest and set parameters + CreateWalletRequest cwr = new CreateWalletRequest(); + cwr.setWalletName(walletName); + cwr.setWalletPassword(password); + cwr.setWalletDriverName("sqlite"); // other option is `ledger` + // using our client, pass the request + APIV1POSTWalletResponse result = kmd.createWallet(cwr); + APIV1Wallet wallet = result.getWallet(); + System.out.printf("Wallet name: %s\n", wallet.getName()); + // example: KMD_CREATE_WALLET + + String handleToken = getHandle(kmd, wallet, password); + + // example: KMD_BACKUP_WALLET + ExportMasterKeyRequest mker = new ExportMasterKeyRequest(); + mker.setWalletHandleToken(handleToken); + mker.setWalletPassword(password); + APIV1POSTMasterKeyExportResponse masterKeyResp = kmd.exportMasterKey(mker); + byte[] backupKey = masterKeyResp.getMasterDerivationKey(); + // example: KMD_BACKUP_WALLET + + // example: KMD_RECOVER_WALLET + // create a new CreateWalletRequest and set parameters + CreateWalletRequest recoverRequest = new CreateWalletRequest(); + recoverRequest.setWalletName("Recovered:"+walletName); + recoverRequest.setWalletPassword(password); + recoverRequest.setWalletDriverName("sqlite"); + // Pass the specific derivation key we want to use + // to recover the wallet + recoverRequest.setMasterDerivationKey(backupKey); + APIV1POSTWalletResponse recoverResponse = kmd.createWallet(recoverRequest); + APIV1Wallet recoveredWallet = recoverResponse.getWallet(); + System.out.printf("Wallet name: %s\n", recoveredWallet.getName()); + // example: KMD_RECOVER_WALLET + + // example: KMD_CREATE_ACCOUNT + // create a request to generate a new key, using the handle token + GenerateKeyRequest gkr = new GenerateKeyRequest(); + gkr.setWalletHandleToken(handleToken); + APIV1POSTKeyResponse generatedKey = kmd.generateKey(gkr); + String addr = generatedKey.getAddress(); + System.out.printf("New account: %s\n", addr); + // example: KMD_CREATE_ACCOUNT + + // example: KMD_EXPORT_ACCOUNT + ExportKeyRequest ekr = new ExportKeyRequest(); + ekr.setAddress(addr); + ekr.setWalletHandleToken(handleToken); + ekr.setWalletPassword(password); + APIV1POSTKeyExportResponse exportedKeyResp = kmd.exportKey(ekr); + byte[] exportedKey = exportedKeyResp.getPrivateKey(); + String mn = Mnemonic.fromKey(Arrays.copyOfRange(exportedKey, 0, 32)); + System.out.printf("Exported mnemonic: %s\n", mn); + // example: KMD_EXPORT_ACCOUNT + + String recoveredWalletHandleToken = getHandle(kmd, recoveredWallet, password); + + // example: KMD_IMPORT_ACCOUNT + ImportKeyRequest ikr = new ImportKeyRequest(); + ikr.setPrivateKey(Arrays.copyOfRange(exportedKey, 0, 32)); + ikr.setWalletHandleToken(recoveredWalletHandleToken); + APIV1POSTKeyImportResponse importResp = kmd.importKey(ikr); + System.out.printf("Imported account: %s\n", importResp.getAddress()); + // /example: KMD_IMPORT_ACCOUNT + } + + public static String getHandle(KmdApi kmd, APIV1Wallet wallet, String password) throws ApiException { + // example: KMD_CREATE_HANDLE_TOKEN + // grab a handle that we can re-use + InitWalletHandleTokenRequest tokenReq = new InitWalletHandleTokenRequest(); + tokenReq.setWalletId(wallet.getId()); + tokenReq.setWalletPassword(password); + APIV1POSTWalletInitResponse handleTokenResp = kmd.initWalletHandleToken(tokenReq); + String handleToken = handleTokenResp.getWalletHandleToken(); + // example: KMD_CREATE_HANDLE_TOKEN + return handleToken; + } + +} diff --git a/examples/src/main/java/com/algorand/examples/LSig.java b/examples/src/main/java/com/algorand/examples/LSig.java new file mode 100644 index 000000000..924b8563c --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/LSig.java @@ -0,0 +1,108 @@ +package com.algorand.examples; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.account.LogicSigAccount; +import com.algorand.algosdk.crypto.LogicsigSignature; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.CompileResponse; +import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class LSig { + + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + Account seedAcct = accts.get(0); + + byte[] tealBinary = compileLsig(algodClient, "lsig/simple.teal"); + // example: LSIG_INIT + LogicSigAccount lsig = new LogicSigAccount(tealBinary, null); + // example: LSIG_INIT + + byte[] tealBinaryWithArgs = compileLsig(algodClient, "lsig/simple.teal"); + // example: LSIG_PASS_ARGS + List tealArgs = new ArrayList(); + // The arguments _must_ be byte arrays + byte[] arg1 = Encoder.encodeUint64(123l); + tealArgs.add(arg1); + LogicSigAccount lsigWithArgs = new LogicSigAccount(tealBinaryWithArgs, tealArgs); + // example: LSIG_PASS_ARGS + + // Create a transaction to seed the lsig address + TransactionParametersResponse seedParams = algodClient.TransactionParams().execute().body(); + Transaction seedTxn = Transaction.PaymentTransactionBuilder() + .suggestedParams(seedParams) + .sender(seedAcct.getAddress()) + .amount(10000000) + .receiver(lsigWithArgs.getAddress()) + .build(); + ExampleUtils.sendPrint(algodClient, seedAcct.signTransaction(seedTxn), "seed lsig"); + + // example: LSIG_SIGN_FULL + TransactionParametersResponse params = algodClient.TransactionParams().execute().body(); + // create a transaction + Transaction txn = Transaction.PaymentTransactionBuilder() + .sender(lsig.getAddress()) + .amount(100000) + .receiver(seedAcct.getAddress()) + .suggestedParams(params) + .build(); + // create the LogicSigTransaction with contract account LogicSigAccount + SignedTransaction stx = Account.signLogicsigTransaction(lsig.lsig, txn); + // send raw LogicSigTransaction to network + Response submitResult = algodClient.RawTransaction() + .rawtxn(Encoder.encodeToMsgPack(stx)).execute(); + String txid = submitResult.body().txId; + // Wait for transaction confirmation + PendingTransactionResponse pTrx = Utils.waitForConfirmation(algodClient, txid, 4); + System.out.printf("Transaction %s confirmed in round %d\n", txid, pTrx.confirmedRound); + // example: LSIG_SIGN_FULL + + // example: LSIG_DELEGATE_FULL + // account signs the logic, and now the logic may be passed instead + // of a signature for a transaction + LogicsigSignature delegateLsig = seedAcct.signLogicsig(lsigWithArgs.lsig); + params = algodClient.TransactionParams().execute().body(); + // create a transaction where the sender is the signer of the lsig + txn = Transaction.PaymentTransactionBuilder() + .sender(seedAcct.getAddress()) + .amount(100000) + .receiver(delegateLsig.toAddress()) + .suggestedParams(params) + .build(); + // Sign the transaction with the delegate lsig + stx = Account.signLogicsigTransaction(delegateLsig, txn); + // send raw LogicSigTransaction to network + submitResult = algodClient.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stx)).execute(); + txid = submitResult.body().txId; + // Wait for transaction confirmation + PendingTransactionResponse delegatResponse = Utils.waitForConfirmation(algodClient, txid, 4); + System.out.printf("Transaction %s confirmed in round %d\n", txid, delegatResponse.confirmedRound); + // example: LSIG_DELEGATE_FULL + } + + public static byte[] compileLsig(AlgodClient algodClient, String path) throws Exception { + // example: LSIG_COMPILE + String tealsrc = Files.readString(Paths.get("lsig/simple.teal")); + Response compileResp = algodClient.TealCompile().source(tealsrc.getBytes()).execute(); + System.out.printf("Program: %s\n", compileResp.body().result); + System.out.printf("Address: %s\n", compileResp.body().hash); + byte[] tealBinary = Encoder.decodeFromBase64(compileResp.body().result); + // example: LSIG_COMPILE + return tealBinary; + } + +} diff --git a/examples/src/main/java/com/algorand/examples/Overview.java b/examples/src/main/java/com/algorand/examples/Overview.java new file mode 100644 index 000000000..c2c72d2fb --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/Overview.java @@ -0,0 +1,107 @@ +package com.algorand.examples; + +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import com.algorand.algosdk.v2.client.Utils; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.PendingTransactionResponse; +import com.algorand.algosdk.v2.client.model.PostTransactionsResponse; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; +import com.algorand.algosdk.account.Account; +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.util.Encoder; + +public class Overview { + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + List accts = ExampleUtils.getSandboxAccounts(); + // grab the first one from the sandbox kmd + Account acct = accts.get(0); + Account acct2 = accts.get(1); + + // example: TRANSACTION_PAYMENT_CREATE + Response suggestedParams = algodClient.TransactionParams().execute(); + Integer amount = 1000000; // 1 Algo + Transaction ptxn = Transaction.PaymentTransactionBuilder() + .sender(acct.getAddress()) + .amount(amount) + .receiver(acct2.getAddress()) + .suggestedParams(suggestedParams.body()).build(); + // example: TRANSACTION_PAYMENT_CREATE + + // example: TRANSACTION_PAYMENT_SIGN + SignedTransaction sptxn = acct.signTransaction(ptxn); + // example: TRANSACTION_PAYMENT_SIGN + + // example: TRANSACTION_PAYMENT_SUBMIT + // encode the transaction + byte[] encodedTxBytes = Encoder.encodeToMsgPack(sptxn); + // submit the transaction to the algod server + Response resp = algodClient.RawTransaction().rawtxn(encodedTxBytes).execute(); + // wait for the transaction to be confirmed + String txid = resp.body().txId; + PendingTransactionResponse result = Utils.waitForConfirmation(algodClient, txid, 4); + System.out.printf("Transaction %s confirmed in round %d\n", txid, result.confirmedRound); + // example: TRANSACTION_PAYMENT_SUBMIT + + // example: SP_MIN_FEE + Response tpr = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = tpr.body(); + System.out.printf("Min fee from suggested params: %d\n", sp.minFee); + // example: SP_MIN_FEE + + // example: TRANSACTION_FEE_OVERRIDE + BigInteger nullFee = null; + Transaction feeOverrideTxn = Transaction.PaymentTransactionBuilder() + .sender(acct.getAddress()) + .receiver(acct2.getAddress()) + .suggestedParams(suggestedParams.body()) + // since suggestedParams sets a fee, we have to `null` it out + // or trying to set flatFee will fail with both set + .fee(nullFee) + // override the fee given by suggested params to set a flat + // fee of 2x minfee to cover another transaction in the same group + .flatFee(2 * suggestedParams.body().minFee).build(); + // example: TRANSACTION_FEE_OVERRIDE + + // example: CONST_MIN_FEE + System.out.printf("Min fee from const: %d\n", Account.MIN_TX_FEE_UALGOS); + // example: CONST_MIN_FEE + } + + public static void createAccount() throws NoSuchAlgorithmException { + // example: ACCOUNT_GENERATE + Account acct = new Account(); + System.out.println("Address: " + acct.getAddress()); + System.out.println("Passphrase: " + acct.toMnemonic()); + // example: ACCOUNT_GENERATE + } + + public static void getAccountInfo(AlgodClient algodClient, Account acct) throws Exception { + // example: ALGOD_FETCH_ACCOUNT_INFO + Response acctInfoResp = algodClient + .AccountInformation(acct.getAddress()).execute(); + com.algorand.algosdk.v2.client.model.Account acctInfo = acctInfoResp.body(); + // print one of the fields in the account info response + System.out.printf("Current balance: %d", acctInfo.amount); + // example: ALGOD_FETCH_ACCOUNT_INFO + } + + public static AlgodClient createClient() { + // example: ALGOD_CREATE_CLIENT + String algodHost = "http://localhost"; + int algodPort = 4001; + String algodToken = "a".repeat(64); + AlgodClient algodClient = new AlgodClient(algodHost, algodPort, algodToken); + + // OR if the API provider requires a specific header key for the token + String tokenHeader = "X-API-Key"; + AlgodClient otherAlgodClient = new AlgodClient(algodHost, algodPort, algodToken, tokenHeader); + // example: ALGOD_CREATE_CLIENT + return algodClient; + } + +} \ No newline at end of file diff --git a/examples/src/main/java/com/algorand/examples/Participation.java b/examples/src/main/java/com/algorand/examples/Participation.java new file mode 100644 index 000000000..27f0d4ebe --- /dev/null +++ b/examples/src/main/java/com/algorand/examples/Participation.java @@ -0,0 +1,46 @@ +package com.algorand.examples; + +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.TransactionParametersResponse; + +public class Participation { + + public static void main(String[] args) throws Exception { + AlgodClient algodClient = ExampleUtils.getAlgodClient(); + + // example: TRANSACTION_KEYREG_ONLINE_CREATE + // get suggested parameters + Response rsp = algodClient.TransactionParams().execute(); + TransactionParametersResponse sp = rsp.body(); + + String address = "EW64GC6F24M7NDSC5R3ES4YUVE3ZXXNMARJHDCCCLIHZU6TBEOC7XRSBG4"; + + String votekey = "eXq34wzh2UIxCZaI1leALKyAvSz/+XOe0wqdHagM+bw="; + String skey = "X84ReKTmp+yfgmMCbbokVqeFFFrKQeFZKEXG89SXwm4="; + + Long numRounds = 100000l; // sets up keys for 100000 rounds + Long keyDilution = (long) Math.sqrt(numRounds); // dilution default is sqrt num rounds + + Transaction keyRegTxn = Transaction.KeyRegistrationTransactionBuilder().suggestedParams(sp) + .sender(address) + .selectionPublicKeyBase64(skey) + .participationPublicKeyBase64(votekey) + .voteFirst(sp.lastRound) + .voteLast(sp.lastRound + numRounds) + .voteKeyDilution(keyDilution) + .build(); + // ... sign and send to network + // example: TRANSACTION_KEYREG_ONLINE_CREATE + + // example: TRANSACTION_KEYREG_OFFLINE_CREATE + // create keyreg transaction to take this account offline + Transaction keyRegOfflineTxn = Transaction.KeyRegistrationTransactionBuilder().suggestedParams(sp) + .sender(address) + .build(); + // example: TRANSACTION_KEYREG_OFFLINE_CREATE + System.out.println(keyRegOfflineTxn); + } + +} From 20e1ed5e8be1ab391b0d490301129a2a9ec20b8e Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Fri, 24 Mar 2023 10:47:07 -0400 Subject: [PATCH 3/7] Fix: improve error message for mismatched args (#511) Co-authored-by: Michael Chuang --- .../java/com/algorand/algosdk/transaction/MethodCallParams.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java b/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java index ae435eaac..fc40d30f4 100644 --- a/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java +++ b/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java @@ -170,7 +170,7 @@ public List createTransactions() { methodABIts.add(argT.parsedType); } else throw new IllegalArgumentException( - "error: the type of method argument is a transaction-type, but no transaction-with-signer provided" + "error: the type of method (" + this.method.name +") argument " + (i+1) + " is a " + methodArg.getClass().getName() + ", but " + argT.type + " not provided" ); } From 6cdd43912daa68571baacdf38ac5a913f34600d2 Mon Sep 17 00:00:00 2001 From: Michael T Chuang Date: Thu, 27 Apr 2023 05:16:45 -0700 Subject: [PATCH 4/7] ci: update label github action to v3 (#531) --- .github/workflows/pr-type-category.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-type-category.yml b/.github/workflows/pr-type-category.yml index 0ae6369ca..5e8e0dbf1 100644 --- a/.github/workflows/pr-type-category.yml +++ b/.github/workflows/pr-type-category.yml @@ -10,7 +10,7 @@ jobs: name: Check PR Category and Type steps: - name: Checking for correct number of required github pr labels - uses: mheap/github-action-required-labels@v2 + uses: mheap/github-action-required-labels@v3 with: mode: exactly count: 1 From e37bbe29687d1562015f16a499be9b19f67dc423 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Thu, 27 Apr 2023 08:17:16 -0400 Subject: [PATCH 5/7] api: Regenerate Client Interfaces and implement cucumber tests. (#555) --- .../client/algod/GetBlockTimeStampOffset.java | 56 +++++++++++++++ .../algosdk/v2/client/algod/GetReady.java | 52 ++++++++++++++ .../algosdk/v2/client/algod/GetSyncRound.java | 55 +++++++++++++++ .../v2/client/algod/RawTransaction.java | 2 +- .../client/algod/SetBlockTimeStampOffset.java | 66 ++++++++++++++++++ .../algosdk/v2/client/algod/SetSyncRound.java | 64 +++++++++++++++++ .../v2/client/algod/SimulateTransaction.java | 67 ++++++++++++++++++ .../v2/client/algod/UnsetSyncRound.java | 54 +++++++++++++++ .../algosdk/v2/client/common/AlgodClient.java | 68 ++++++++++++++++++- .../algosdk/v2/client/common/Query.java | 4 ++ .../GetBlockTimeStampOffsetResponse.java | 30 ++++++++ .../v2/client/model/GetSyncRoundResponse.java | 30 ++++++++ .../algosdk/v2/client/model/KvDelta.java | 51 ++++++++++++++ .../v2/client/model/NodeStatusResponse.java | 56 +++++++++++++++ .../model/PendingTransactionResponse.java | 6 +- .../v2/client/model/SimulateRequest.java | 47 +++++++++++++ .../SimulateRequestTransactionGroup.java | 33 +++++++++ .../v2/client/model/SimulateResponse.java | 56 +++++++++++++++ .../model/SimulateTransactionGroupResult.java | 64 +++++++++++++++++ .../model/SimulateTransactionResult.java | 46 +++++++++++++ .../client/model/SimulationEvalOverrides.java | 47 +++++++++++++ .../com/algorand/algosdk/unit/AlgodPaths.java | 30 ++++++++ .../algorand/algosdk/unit/PathsShared.java | 14 +++- .../algosdk/unit/ResponsesShared.java | 6 ++ src/test/unit.tags | 7 +- 25 files changed, 1002 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/GetBlockTimeStampOffset.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/GetReady.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/GetSyncRound.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/SetBlockTimeStampOffset.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/SetSyncRound.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/SimulateTransaction.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/UnsetSyncRound.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/GetBlockTimeStampOffsetResponse.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/GetSyncRoundResponse.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/KvDelta.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/SimulateRequest.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/SimulateRequestTransactionGroup.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/SimulateResponse.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/SimulateTransactionGroupResult.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/SimulateTransactionResult.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/SimulationEvalOverrides.java diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetBlockTimeStampOffset.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetBlockTimeStampOffset.java new file mode 100644 index 000000000..8febe9a85 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetBlockTimeStampOffset.java @@ -0,0 +1,56 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.GetBlockTimeStampOffsetResponse; + + +/** + * Gets the current timestamp offset. + * /v2/devmode/blocks/offset + */ +public class GetBlockTimeStampOffset extends Query { + + public GetBlockTimeStampOffset(Client client) { + super(client, new HttpMethod("get")); + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(GetBlockTimeStampOffsetResponse.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(GetBlockTimeStampOffsetResponse.class); + return resp; + } + + protected QueryData getRequestString() { + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("devmode")); + addPathSegment(String.valueOf("blocks")); + addPathSegment(String.valueOf("offset")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetReady.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetReady.java new file mode 100644 index 000000000..2d87aa3f4 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetReady.java @@ -0,0 +1,52 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; + + +/** + * Returns OK if healthy and fully caught up. + * /ready + */ +public class GetReady extends Query { + + public GetReady(Client client) { + super(client, new HttpMethod("get")); + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(String.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(String.class); + return resp; + } + + protected QueryData getRequestString() { + addPathSegment(String.valueOf("ready")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetSyncRound.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetSyncRound.java new file mode 100644 index 000000000..4d37fd10c --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetSyncRound.java @@ -0,0 +1,55 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.GetSyncRoundResponse; + + +/** + * Gets the minimum sync round for the ledger. + * /v2/ledger/sync + */ +public class GetSyncRound extends Query { + + public GetSyncRound(Client client) { + super(client, new HttpMethod("get")); + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(GetSyncRoundResponse.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(GetSyncRoundResponse.class); + return resp; + } + + protected QueryData getRequestString() { + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("ledger")); + addPathSegment(String.valueOf("sync")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/RawTransaction.java b/src/main/java/com/algorand/algosdk/v2/client/algod/RawTransaction.java index 1b80c3394..91ccc0bdf 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/algod/RawTransaction.java +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/RawTransaction.java @@ -9,7 +9,7 @@ /** - * Broadcasts a raw transaction to the network. + * Broadcasts a raw transaction or transaction group to the network. * /v2/transactions */ public class RawTransaction extends Query { diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/SetBlockTimeStampOffset.java b/src/main/java/com/algorand/algosdk/v2/client/algod/SetBlockTimeStampOffset.java new file mode 100644 index 000000000..72d88b841 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/SetBlockTimeStampOffset.java @@ -0,0 +1,66 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; + + +/** + * Sets the timestamp offset (seconds) for blocks in dev mode. Providing an offset + * of 0 will unset this value and try to use the real clock for the timestamp. + * /v2/devmode/blocks/offset/{offset} + */ +public class SetBlockTimeStampOffset extends Query { + + private Long offset; + + /** + * @param offset The timestamp offset for blocks in dev mode. + */ + public SetBlockTimeStampOffset(Client client, Long offset) { + super(client, new HttpMethod("post")); + this.offset = offset; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(String.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(String.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.offset == null) { + throw new RuntimeException("offset is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("devmode")); + addPathSegment(String.valueOf("blocks")); + addPathSegment(String.valueOf("offset")); + addPathSegment(String.valueOf(offset)); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/SetSyncRound.java b/src/main/java/com/algorand/algosdk/v2/client/algod/SetSyncRound.java new file mode 100644 index 000000000..f3b5cf94d --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/SetSyncRound.java @@ -0,0 +1,64 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; + + +/** + * Sets the minimum sync round on the ledger. + * /v2/ledger/sync/{round} + */ +public class SetSyncRound extends Query { + + private Long round; + + /** + * @param round The round for which the deltas are desired. + */ + public SetSyncRound(Client client, Long round) { + super(client, new HttpMethod("post")); + this.round = round; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(String.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(String.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.round == null) { + throw new RuntimeException("round is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("ledger")); + addPathSegment(String.valueOf("sync")); + addPathSegment(String.valueOf(round)); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/SimulateTransaction.java b/src/main/java/com/algorand/algosdk/v2/client/algod/SimulateTransaction.java new file mode 100644 index 000000000..277e6e9b9 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/SimulateTransaction.java @@ -0,0 +1,67 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.SimulateRequest; +import com.algorand.algosdk.v2.client.model.SimulateResponse; + + +/** + * Simulates a raw transaction or transaction group as it would be evaluated on the + * network. The simulation will use blockchain state from the latest committed + * round. + * /v2/transactions/simulate + */ +public class SimulateTransaction extends Query { + + public SimulateTransaction(Client client) { + super(client, new HttpMethod("post")); + addQuery("format", "msgpack"); + } + + /** + * The transactions to simulate, along with any other inputs. + */ + public SimulateTransaction request(SimulateRequest request) { + addToBody(request); + return this; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(SimulateResponse.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(SimulateResponse.class); + return resp; + } + + protected QueryData getRequestString() { + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("transactions")); + addPathSegment(String.valueOf("simulate")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/UnsetSyncRound.java b/src/main/java/com/algorand/algosdk/v2/client/algod/UnsetSyncRound.java new file mode 100644 index 000000000..38285612a --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/UnsetSyncRound.java @@ -0,0 +1,54 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; + + +/** + * Unset the ledger sync round. + * /v2/ledger/sync + */ +public class UnsetSyncRound extends Query { + + public UnsetSyncRound(Client client) { + super(client, new HttpMethod("delete")); + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(String.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(String.class); + return resp; + } + + protected QueryData getRequestString() { + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("ledger")); + addPathSegment(String.valueOf("sync")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java index 6211aefa2..84fd4fd29 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java @@ -1,6 +1,7 @@ package com.algorand.algosdk.v2.client.common; import com.algorand.algosdk.v2.client.algod.HealthCheck; +import com.algorand.algosdk.v2.client.algod.GetReady; import com.algorand.algosdk.v2.client.algod.Metrics; import com.algorand.algosdk.v2.client.algod.GetGenesis; import com.algorand.algosdk.v2.client.algod.SwaggerJSON; @@ -16,6 +17,7 @@ import com.algorand.algosdk.v2.client.algod.GetStatus; import com.algorand.algosdk.v2.client.algod.WaitForBlock; import com.algorand.algosdk.v2.client.algod.RawTransaction; +import com.algorand.algosdk.v2.client.algod.SimulateTransaction; import com.algorand.algosdk.v2.client.algod.TransactionParams; import com.algorand.algosdk.v2.client.algod.GetPendingTransactions; import com.algorand.algosdk.v2.client.algod.PendingTransactionInformation; @@ -25,9 +27,14 @@ import com.algorand.algosdk.v2.client.algod.GetApplicationBoxes; import com.algorand.algosdk.v2.client.algod.GetApplicationBoxByName; import com.algorand.algosdk.v2.client.algod.GetAssetByID; +import com.algorand.algosdk.v2.client.algod.UnsetSyncRound; +import com.algorand.algosdk.v2.client.algod.GetSyncRound; +import com.algorand.algosdk.v2.client.algod.SetSyncRound; import com.algorand.algosdk.v2.client.algod.TealCompile; import com.algorand.algosdk.v2.client.algod.TealDisassemble; import com.algorand.algosdk.v2.client.algod.TealDryrun; +import com.algorand.algosdk.v2.client.algod.GetBlockTimeStampOffset; +import com.algorand.algosdk.v2.client.algod.SetBlockTimeStampOffset; import com.algorand.algosdk.crypto.Address; public class AlgodClient extends Client { @@ -61,6 +68,14 @@ public HealthCheck HealthCheck() { return new HealthCheck((Client) this); } + /** + * Returns OK if healthy and fully caught up. + * /ready + */ + public GetReady GetReady() { + return new GetReady((Client) this); + } + /** * Return metrics about algod functioning. * /metrics @@ -187,13 +202,23 @@ public WaitForBlock WaitForBlock(Long round) { } /** - * Broadcasts a raw transaction to the network. + * Broadcasts a raw transaction or transaction group to the network. * /v2/transactions */ public RawTransaction RawTransaction() { return new RawTransaction((Client) this); } + /** + * Simulates a raw transaction or transaction group as it would be evaluated on the + * network. The simulation will use blockchain state from the latest committed + * round. + * /v2/transactions/simulate + */ + public SimulateTransaction SimulateTransaction() { + return new SimulateTransaction((Client) this); + } + /** * Get parameters for constructing a new transaction * /v2/transactions/params @@ -282,6 +307,30 @@ public GetAssetByID GetAssetByID(Long assetId) { return new GetAssetByID((Client) this, assetId); } + /** + * Unset the ledger sync round. + * /v2/ledger/sync + */ + public UnsetSyncRound UnsetSyncRound() { + return new UnsetSyncRound((Client) this); + } + + /** + * Gets the minimum sync round for the ledger. + * /v2/ledger/sync + */ + public GetSyncRound GetSyncRound() { + return new GetSyncRound((Client) this); + } + + /** + * Sets the minimum sync round on the ledger. + * /v2/ledger/sync/{round} + */ + public SetSyncRound SetSyncRound(Long round) { + return new SetSyncRound((Client) this, round); + } + /** * Given TEAL source code in plain text, return base64 encoded program bytes and * base32 SHA512_256 hash of program bytes (Address style). This endpoint is only @@ -312,4 +361,21 @@ public TealDryrun TealDryrun() { return new TealDryrun((Client) this); } + /** + * Gets the current timestamp offset. + * /v2/devmode/blocks/offset + */ + public GetBlockTimeStampOffset GetBlockTimeStampOffset() { + return new GetBlockTimeStampOffset((Client) this); + } + + /** + * Sets the timestamp offset (seconds) for blocks in dev mode. Providing an offset + * of 0 will unset this value and try to use the real clock for the timestamp. + * /v2/devmode/blocks/offset/{offset} + */ + public SetBlockTimeStampOffset SetBlockTimeStampOffset(Long offset) { + return new SetBlockTimeStampOffset((Client) this, offset); + } + } diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/Query.java b/src/main/java/com/algorand/algosdk/v2/client/common/Query.java index 20901e05f..fb0e6f0e6 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/Query.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/Query.java @@ -36,6 +36,10 @@ public String getRequestUrl() { return getRequestUrl(client.port, client.host); } + public String getRequestMethod() { + return httpMethod.method(); + } + public String getRequestUrl(int port, String host) { // Make sure the path is generated by calling getRequestString (at most) once. if (qd.pathSegments.size() == 0) { diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/GetBlockTimeStampOffsetResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/GetBlockTimeStampOffsetResponse.java new file mode 100644 index 000000000..8b3fa11b6 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/GetBlockTimeStampOffsetResponse.java @@ -0,0 +1,30 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Response containing the timestamp offset in seconds + */ +public class GetBlockTimeStampOffsetResponse extends PathResponse { + + /** + * Timestamp offset in seconds. + */ + @JsonProperty("offset") + public Long offset; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + GetBlockTimeStampOffsetResponse other = (GetBlockTimeStampOffsetResponse) o; + if (!Objects.deepEquals(this.offset, other.offset)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/GetSyncRoundResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/GetSyncRoundResponse.java new file mode 100644 index 000000000..c44f4da70 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/GetSyncRoundResponse.java @@ -0,0 +1,30 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Response containing the ledger's minimum sync round + */ +public class GetSyncRoundResponse extends PathResponse { + + /** + * The minimum sync round for the ledger. + */ + @JsonProperty("round") + public Long round; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + GetSyncRoundResponse other = (GetSyncRoundResponse) o; + if (!Objects.deepEquals(this.round, other.round)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/KvDelta.java b/src/main/java/com/algorand/algosdk/v2/client/model/KvDelta.java new file mode 100644 index 000000000..963bdf1bb --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/KvDelta.java @@ -0,0 +1,51 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A single Delta containing the key, the previous value and the current value for + * a single round. + */ +public class KvDelta extends PathResponse { + + /** + * The key, base64 encoded. + */ + @JsonProperty("key") + public void key(String base64Encoded) { + this.key = Encoder.decodeFromBase64(base64Encoded); + } + public String key() { + return Encoder.encodeToBase64(this.key); + } + public byte[] key; + + /** + * The new value of the KV store entry, base64 encoded. + */ + @JsonProperty("value") + public void value(String base64Encoded) { + this.value = Encoder.decodeFromBase64(base64Encoded); + } + public String value() { + return Encoder.encodeToBase64(this.value); + } + public byte[] value; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + KvDelta other = (KvDelta) o; + if (!Objects.deepEquals(this.key, other.key)) return false; + if (!Objects.deepEquals(this.value, other.value)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/NodeStatusResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/NodeStatusResponse.java index 220edb6a1..a68a723d2 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/NodeStatusResponse.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/NodeStatusResponse.java @@ -123,6 +123,54 @@ public class NodeStatusResponse extends PathResponse { @JsonProperty("time-since-last-round") public Long timeSinceLastRound; + /** + * Upgrade delay + */ + @JsonProperty("upgrade-delay") + public Long upgradeDelay; + + /** + * Next protocol round + */ + @JsonProperty("upgrade-next-protocol-vote-before") + public Long upgradeNextProtocolVoteBefore; + + /** + * No votes cast for consensus upgrade + */ + @JsonProperty("upgrade-no-votes") + public Long upgradeNoVotes; + + /** + * This node's upgrade vote + */ + @JsonProperty("upgrade-node-vote") + public Boolean upgradeNodeVote; + + /** + * Total voting rounds for current upgrade + */ + @JsonProperty("upgrade-vote-rounds") + public Long upgradeVoteRounds; + + /** + * Total votes cast for consensus upgrade + */ + @JsonProperty("upgrade-votes") + public Long upgradeVotes; + + /** + * Yes votes required for consensus upgrade + */ + @JsonProperty("upgrade-votes-required") + public Long upgradeVotesRequired; + + /** + * Yes votes cast for consensus upgrade + */ + @JsonProperty("upgrade-yes-votes") + public Long upgradeYesVotes; + @Override public boolean equals(Object o) { @@ -148,6 +196,14 @@ public boolean equals(Object o) { if (!Objects.deepEquals(this.nextVersionSupported, other.nextVersionSupported)) return false; if (!Objects.deepEquals(this.stoppedAtUnsupportedRound, other.stoppedAtUnsupportedRound)) return false; if (!Objects.deepEquals(this.timeSinceLastRound, other.timeSinceLastRound)) return false; + if (!Objects.deepEquals(this.upgradeDelay, other.upgradeDelay)) return false; + if (!Objects.deepEquals(this.upgradeNextProtocolVoteBefore, other.upgradeNextProtocolVoteBefore)) return false; + if (!Objects.deepEquals(this.upgradeNoVotes, other.upgradeNoVotes)) return false; + if (!Objects.deepEquals(this.upgradeNodeVote, other.upgradeNodeVote)) return false; + if (!Objects.deepEquals(this.upgradeVoteRounds, other.upgradeVoteRounds)) return false; + if (!Objects.deepEquals(this.upgradeVotes, other.upgradeVotes)) return false; + if (!Objects.deepEquals(this.upgradeVotesRequired, other.upgradeVotesRequired)) return false; + if (!Objects.deepEquals(this.upgradeYesVotes, other.upgradeYesVotes)) return false; return true; } diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/PendingTransactionResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/PendingTransactionResponse.java index df05d09aa..1228ca88b 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/PendingTransactionResponse.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/PendingTransactionResponse.java @@ -54,7 +54,7 @@ public class PendingTransactionResponse extends PathResponse { public Long confirmedRound; /** - * (gd) Global state key/value changes for the application being executed by this + * Global state key/value changes for the application being executed by this * transaction. */ @JsonProperty("global-state-delta") @@ -67,14 +67,14 @@ public class PendingTransactionResponse extends PathResponse { public List innerTxns = new ArrayList(); /** - * (ld) Local state key/value changes for the application being executed by this + * Local state key/value changes for the application being executed by this * transaction. */ @JsonProperty("local-state-delta") public List localStateDelta = new ArrayList(); /** - * (lg) Logs for the application being executed by this transaction. + * Logs for the application being executed by this transaction. */ @JsonProperty("logs") public List logs = new ArrayList(); diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/SimulateRequest.java b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateRequest.java new file mode 100644 index 000000000..d11447cc5 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateRequest.java @@ -0,0 +1,47 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Request type for simulation endpoint. + */ +public class SimulateRequest extends PathResponse { + + /** + * Allow transactions without signatures to be simulated as if they had correct + * signatures. + */ + @JsonProperty("allow-empty-signatures") + public Boolean allowEmptySignatures; + + /** + * Lifts limits on log opcode usage during simulation. + */ + @JsonProperty("allow-more-logging") + public Boolean allowMoreLogging; + + /** + * The transaction groups to simulate. + */ + @JsonProperty("txn-groups") + public List txnGroups = new ArrayList(); + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + SimulateRequest other = (SimulateRequest) o; + if (!Objects.deepEquals(this.allowEmptySignatures, other.allowEmptySignatures)) return false; + if (!Objects.deepEquals(this.allowMoreLogging, other.allowMoreLogging)) return false; + if (!Objects.deepEquals(this.txnGroups, other.txnGroups)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/SimulateRequestTransactionGroup.java b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateRequestTransactionGroup.java new file mode 100644 index 000000000..79ddf038d --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateRequestTransactionGroup.java @@ -0,0 +1,33 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.algorand.algosdk.transaction.SignedTransaction; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A transaction group to simulate. + */ +public class SimulateRequestTransactionGroup extends PathResponse { + + /** + * An atomic transaction group. + */ + @JsonProperty("txns") + public List txns = new ArrayList(); + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + SimulateRequestTransactionGroup other = (SimulateRequestTransactionGroup) o; + if (!Objects.deepEquals(this.txns, other.txns)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/SimulateResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateResponse.java new file mode 100644 index 000000000..213713d35 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateResponse.java @@ -0,0 +1,56 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Result of a transaction group simulation. + */ +public class SimulateResponse extends PathResponse { + + /** + * The set of parameters and limits override during simulation. If this set of + * parameters is present, then evaluation parameters may differ from standard + * evaluation in certain ways. + */ + @JsonProperty("eval-overrides") + public SimulationEvalOverrides evalOverrides; + + /** + * The round immediately preceding this simulation. State changes through this + * round were used to run this simulation. + */ + @JsonProperty("last-round") + public Long lastRound; + + /** + * A result object for each transaction group that was simulated. + */ + @JsonProperty("txn-groups") + public List txnGroups = new ArrayList(); + + /** + * The version of this response object. + */ + @JsonProperty("version") + public Long version; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + SimulateResponse other = (SimulateResponse) o; + if (!Objects.deepEquals(this.evalOverrides, other.evalOverrides)) return false; + if (!Objects.deepEquals(this.lastRound, other.lastRound)) return false; + if (!Objects.deepEquals(this.txnGroups, other.txnGroups)) return false; + if (!Objects.deepEquals(this.version, other.version)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/SimulateTransactionGroupResult.java b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateTransactionGroupResult.java new file mode 100644 index 000000000..ea124acd3 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateTransactionGroupResult.java @@ -0,0 +1,64 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Simulation result for an atomic transaction group + */ +public class SimulateTransactionGroupResult extends PathResponse { + + /** + * Total budget added during execution of app calls in the transaction group. + */ + @JsonProperty("app-budget-added") + public Long appBudgetAdded; + + /** + * Total budget consumed during execution of app calls in the transaction group. + */ + @JsonProperty("app-budget-consumed") + public Long appBudgetConsumed; + + /** + * If present, indicates which transaction in this group caused the failure. This + * array represents the path to the failing transaction. Indexes are zero based, + * the first element indicates the top-level transaction, and successive elements + * indicate deeper inner transactions. + */ + @JsonProperty("failed-at") + public List failedAt = new ArrayList(); + + /** + * If present, indicates that the transaction group failed and specifies why that + * happened + */ + @JsonProperty("failure-message") + public String failureMessage; + + /** + * Simulation result for individual transactions + */ + @JsonProperty("txn-results") + public List txnResults = new ArrayList(); + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + SimulateTransactionGroupResult other = (SimulateTransactionGroupResult) o; + if (!Objects.deepEquals(this.appBudgetAdded, other.appBudgetAdded)) return false; + if (!Objects.deepEquals(this.appBudgetConsumed, other.appBudgetConsumed)) return false; + if (!Objects.deepEquals(this.failedAt, other.failedAt)) return false; + if (!Objects.deepEquals(this.failureMessage, other.failureMessage)) return false; + if (!Objects.deepEquals(this.txnResults, other.txnResults)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/SimulateTransactionResult.java b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateTransactionResult.java new file mode 100644 index 000000000..783cb6435 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/SimulateTransactionResult.java @@ -0,0 +1,46 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Simulation result for an individual transaction + */ +public class SimulateTransactionResult extends PathResponse { + + /** + * Budget used during execution of an app call transaction. This value includes + * budged used by inner app calls spawned by this transaction. + */ + @JsonProperty("app-budget-consumed") + public Long appBudgetConsumed; + + /** + * Budget used during execution of a logic sig transaction. + */ + @JsonProperty("logic-sig-budget-consumed") + public Long logicSigBudgetConsumed; + + /** + * Details about a pending transaction. If the transaction was recently confirmed, + * includes confirmation details like the round and reward details. + */ + @JsonProperty("txn-result") + public PendingTransactionResponse txnResult; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + SimulateTransactionResult other = (SimulateTransactionResult) o; + if (!Objects.deepEquals(this.appBudgetConsumed, other.appBudgetConsumed)) return false; + if (!Objects.deepEquals(this.logicSigBudgetConsumed, other.logicSigBudgetConsumed)) return false; + if (!Objects.deepEquals(this.txnResult, other.txnResult)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/SimulationEvalOverrides.java b/src/main/java/com/algorand/algosdk/v2/client/model/SimulationEvalOverrides.java new file mode 100644 index 000000000..7737596b6 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/SimulationEvalOverrides.java @@ -0,0 +1,47 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The set of parameters and limits override during simulation. If this set of + * parameters is present, then evaluation parameters may differ from standard + * evaluation in certain ways. + */ +public class SimulationEvalOverrides extends PathResponse { + + /** + * If true, transactions without signatures are allowed and simulated as if they + * were properly signed. + */ + @JsonProperty("allow-empty-signatures") + public Boolean allowEmptySignatures; + + /** + * The maximum log calls one can make during simulation + */ + @JsonProperty("max-log-calls") + public java.math.BigInteger maxLogCalls; + + /** + * The maximum byte number to log during simulation + */ + @JsonProperty("max-log-size") + public java.math.BigInteger maxLogSize; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + SimulationEvalOverrides other = (SimulationEvalOverrides) o; + if (!Objects.deepEquals(this.allowEmptySignatures, other.allowEmptySignatures)) return false; + if (!Objects.deepEquals(this.maxLogCalls, other.maxLogCalls)) return false; + if (!Objects.deepEquals(this.maxLogSize, other.maxLogSize)) return false; + + return true; + } +} diff --git a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java index fb9c6ba07..d3cae8855 100644 --- a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java @@ -121,4 +121,34 @@ public void getStateProof(Long round) { public void getBlockHash(Long round) { ps.q = algodClient.GetBlockHash(round); } + + @When("we make a GetSyncRound call") + public void getSyncRound() { + ps.q = algodClient.GetSyncRound(); + } + + @When("we make a SetSyncRound call against round {long}") + public void setSyncRound(Long round) { + ps.q = algodClient.SetSyncRound(round); + } + + @When("we make a UnsetSyncRound call") + public void unetSyncRound() { + ps.q = algodClient.UnsetSyncRound(); + } + + @When("we make a Ready call") + public void getReady() { + ps.q = algodClient.GetReady(); + } + + @When("we make a GetBlockTimeStampOffset call") + public void getBlockTimestampOffset() { + ps.q = algodClient.GetBlockTimeStampOffset(); + } + + @When("we make a SetBlockTimeStampOffset call against offset {long}") + public void setBlockTimestampOffset(Long round) { + ps.q = algodClient.SetBlockTimeStampOffset(round); + } } diff --git a/src/test/java/com/algorand/algosdk/unit/PathsShared.java b/src/test/java/com/algorand/algosdk/unit/PathsShared.java index d4da1e887..389e72652 100644 --- a/src/test/java/com/algorand/algosdk/unit/PathsShared.java +++ b/src/test/java/com/algorand/algosdk/unit/PathsShared.java @@ -6,14 +6,22 @@ import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; +import static org.assertj.core.api.Assertions.assertThat; + public class PathsShared { public Query q; @Given("mock server recording request paths") - public void mock_server_recording_request_paths() { } + public void mockServerNoop() { } @Then("expect the path used to be {string}") - public void expect_the_path_used_to_be(String string) { - TestingUtils.verifyPathUrls(q.getRequestUrl(), string); + public void expectPathToBe(String path) { + TestingUtils.verifyPathUrls(q.getRequestUrl(), path); + } + + @Then("expect the request to be {string} {string}") + public void expectMethodAndPathToBe(String method, String path) { + assertThat(q.getRequestMethod()).isEqualTo(method); + TestingUtils.verifyPathUrls(q.getRequestUrl(), path); } } diff --git a/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java b/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java index 2565d51cd..40ce9e9bb 100644 --- a/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java +++ b/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java @@ -194,6 +194,12 @@ public void we_make_any_call_to(String client, String endpoint) throws Exception case "GetBlockHash": response = algod.GetBlockHash(1234L).execute(); break; + case "GetBlockTimeStampOffset": + response = algod.GetBlockTimeStampOffset().execute(); + break; + case "GetSyncRound": + response = algod.GetSyncRound().execute(); + break; default: Assertions.fail("Unsupported algod endpoint: " + endpoint); } diff --git a/src/test/unit.tags b/src/test/unit.tags index b38b77d38..720e9743e 100644 --- a/src/test/unit.tags +++ b/src/test/unit.tags @@ -15,18 +15,23 @@ @unit.indexer.rekey @unit.offline @unit.program_sanity_check +@unit.ready @unit.rekey @unit.responses @unit.responses.231 +@unit.responses.blocksummary @unit.responses.messagepack @unit.responses.messagepack.231 +@unit.responses.timestamp +@unit.responses.sync @unit.responses.unlimited_assets -@unit.responses.blocksummary @unit.sourcemap @unit.stateproof.paths @unit.stateproof.responses @unit.stateproof.responses.msgp +@unit.sync @unit.tealsign +@unit.timestamp @unit.transactions @unit.transactions.keyreg @unit.transactions.payment From 6efd6d59ab495f11432e513d9ab0df5c5807a56c Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 4 May 2023 10:54:23 -0400 Subject: [PATCH 6/7] DevOps: Add CODEOWNERS to restrict workflow editing (#559) --- .github/workflows/pr-type-category.yml | 4 +++- CODEOWNERS | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 CODEOWNERS diff --git a/.github/workflows/pr-type-category.yml b/.github/workflows/pr-type-category.yml index 5e8e0dbf1..e03505595 100644 --- a/.github/workflows/pr-type-category.yml +++ b/.github/workflows/pr-type-category.yml @@ -17,8 +17,10 @@ jobs: labels: "New Feature, Enhancement, Bug-Fix, Not-Yet-Enabled, Skip-Release-Notes" - name: "Checking for PR Category in PR title. Should be like ': '." + env: + PR_TITLE: ${{ github.event.pull_request.title }} run: | - if [[ ! "${{ github.event.pull_request.title }}" =~ ^.{2,}\:.{2,} ]]; then + if [[ ! "$PR_TITLE" =~ ^.{2,}\:.{2,} ]]; then echo "## PR Category is missing from PR title. Please add it like ': '." >> GITHUB_STEP_SUMMARY exit 1 fi diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..aa26c82a4 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +.github/ @algorand/dev +.circleci/ @algorand/dev From bb13e02ad73467613798a160c1fd564a3594ebd2 Mon Sep 17 00:00:00 2001 From: Barbara Poon Date: Mon, 8 May 2023 15:11:35 -0400 Subject: [PATCH 7/7] bump to 2.1.0 --- CHANGELOG.md | 19 +++++++++++++++++++ README.md | 2 +- pom.xml | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 989dc52f9..ae86c0cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# 2.1.0 + +## What's Changed +Supports new devmode block timestamp offset endpoints. + +### Bugfixes +* Bug-Fix: update label github action to v3 by @michaeltchuang in https://github.com/algorand/java-algorand-sdk/pull/531 +### Enhancements +* algod REST API: Add test for /v2/teal/disassemble by @michaeldiamant in https://github.com/algorand/java-algorand-sdk/pull/433 +* Documentation: Adds examples to be pulled into docs by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/506 +* Fix: improve error message for mismatched args by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/511 +* api: Regenerate Client Interfaces and implement cucumber tests. by @winder in https://github.com/algorand/java-algorand-sdk/pull/555 +* DevOps: Add CODEOWNERS to restrict workflow editing by @onetechnical in https://github.com/algorand/java-algorand-sdk/pull/559 + +## New Contributors +* @michaeltchuang made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/531 + +**Full Changelog**: https://github.com/algorand/java-algorand-sdk/compare/2.0.0...2.1.0 + # 2.0.0 ## What's Changed diff --git a/README.md b/README.md index b7e4909d2..b25484d73 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Maven: com.algorand algosdk - 2.0.0 + 2.1.0 ``` diff --git a/pom.xml b/pom.xml index 1b9036764..ec8d71f2b 100755 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.algorand algosdk - 2.0.0 + 2.1.0 jar ${project.groupId}:${project.artifactId}