Skip to content

Commit

Permalink
Merge pull request #111 from wavesplatform/eth_support
Browse files Browse the repository at this point in the history
Add ethereum transaction support
  • Loading branch information
devyatkinalexandr authored May 26, 2022
2 parents cf0254a + bdf51bc commit 3ed7254
Show file tree
Hide file tree
Showing 22 changed files with 1,321 additions and 31 deletions.
151 changes: 146 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,32 @@ Supports node interaction, offline transaction signing and creating addresses an
## Using WavesJ in your project
Use the codes below to add WavesJ as a dependency for your project.

##### Requirements:
- JDK 1.8 or above

##### Maven:
```
<dependency>
<groupId>com.wavesplatform</groupId>
<artifactId>wavesj</artifactId>
<version>1.2.3</version>
<version>1.2.5</version>
</dependency>
```

##### Gradle:
```
compile group: 'com.wavesplatform', name: 'wavesj', version: '1.2.3'
compile group: 'com.wavesplatform', name: 'wavesj', version: '1.2.5'
```

##### SBT:
```
libraryDependencies += "com.wavesplatform" % "wavesj" % "1.2.3"
libraryDependencies += "com.wavesplatform" % "wavesj" % "1.2.5"
```

[This library's page at Maven Central](https://mvnrepository.com/artifact/com.wavesplatform/wavesj)

## Basic Usage
Create an account from a private key ('T' for testnet):
### Getting started
Create an account from a private key ('T' for testnet) from random seed phrase:
```java
String seed = Crypto.getRandomSeedPhrase();
PrivateKey privateKey = PrivateKey.fromSeed(seed);
Expand Down Expand Up @@ -59,3 +62,141 @@ Base64String script = node
.script();
node.broadcast(new SetScriptTransaction(publicKey, script).addProof(privateKey));
```

### Reading transaction info
[Same transaction from REST API](https://nodes-stagenet.wavesnodes.com/transactions/info/CWuFY42te67sLmc5gwt4NxwHmFjVfJdHkKuLyshTwEct)

```java
Id ethTxId = new Id("CWuFY42te67sLmc5gwt4NxwHmFjVfJdHkKuLyshTwEct");
EthereumTransactionInfo ethInvokeTxInfo = node.getTransactionInfo(ethTxId, EthereumTransactionInfo.class);

EthereumTransaction ethInvokeTx = ethInvokeTxInfo.tx();
EthereumTransaction.Invocation payload = (EthereumTransaction.Invocation) ethInvokeTx.payload();

System.out.println("is ethereum invoke transaction: " + ethInvokeTxInfo.isInvokeTransaction());

System.out.println("type: " + ethInvokeTx.type());
System.out.println("id: " + ethInvokeTx.id().encoded());
System.out.println("fee: " + ethInvokeTx.fee().value());
System.out.println("feeAssetId: " + ethInvokeTx.fee().assetId().encoded());
System.out.println("timestamp: " + ethInvokeTx.timestamp());
System.out.println("version: " + ethInvokeTx.version());
System.out.println("chainId: " + ethInvokeTx.chainId());
System.out.println("bytes: " + ethInvokeTxInfo.getBytes());
System.out.println("sender: " + ethInvokeTx.sender().address().encoded());
System.out.println("senderPublicKey: " + ethInvokeTx.sender().encoded());
System.out.println("height: " + ethInvokeTxInfo.height());
System.out.println("applicationStatus: " + ethInvokeTxInfo.applicationStatus());
System.out.println("payload dApp: " + payload.dApp().encoded());
System.out.println("payload call function: " + payload.function().name());
List<Arg> args = payload.function().args();
System.out.println("payload call function arguments type: " + args.get(0).type());
System.out.println("payload call function arguments value: " + ((StringArg) args.get(0)).value());
DataEntry dataEntry = ethInvokeTxInfo.getStateChanges().data().get(0);
System.out.println("state changes data key: " + dataEntry.key());
System.out.println("state changes data type: " + dataEntry.type().name());
System.out.println("state changes data value: " + ((StringEntry) dataEntry).value());
```

### Broadcasting transactions
#### Creating accounts (see Getting started for more info about account creation)
```java
PrivateKey alice = createAccountWithBalance(10_00000000);
PrivateKey bob = createAccountWithBalance(10_00000000);
```
#### Broadcasting exchange transaction
```java
AssetId assetId = node.waitForTransaction(node.broadcast(
IssueTransaction.builder("Asset", 1000, 2).getSignedWith(alice)).id(),
IssueTransactionInfo.class).tx().assetId();

Amount amount = Amount.of(1);
Amount price = Amount.of(100, assetId);
long matcherFee = 300000;
Order buy = Order.builder(OrderType.BUY, amount, price, alice.publicKey()).getSignedWith(alice);
Order sell = Order.builder(OrderType.SELL, amount, price, alice.publicKey()).getSignedWith(bob);

ExchangeTransaction tx = ExchangeTransaction
.builder(buy, sell, amount.value(), price.value(), matcherFee, matcherFee).getSignedWith(alice);
node.waitForTransaction(node.broadcast(tx).id());

TransactionInfo commonInfo = node.getTransactionInfo(tx.id());
ExchangeTransactionInfo txInfo = node.getTransactionInfo(tx.id(), ExchangeTransactionInfo.class);
```

### Working with dApp
#### Creating accounts (see Getting started for more info about account creation)
```java
PrivateKey alice = createAccountWithBalance(10_00000000);
PrivateKey bob = createAccountWithBalance(10_00000000);
```
#### Broadcasting issue transaction
```java
AssetId assetId = node.waitForTransaction(node.broadcast(
IssueTransaction.builder("Asset", 1000, 2).getSignedWith(alice)).id(),
IssueTransactionInfo.class).tx().assetId();
```

#### Compiling and broadcasting RIDE script
```java
Base64String script = node.compileScript(
"{-# STDLIB_VERSION 5 #-}\n" +
"{-# CONTENT_TYPE DAPP #-}\n" +
"{-# SCRIPT_TYPE ACCOUNT #-}\n" +
"@Callable(inv)\n" +
"func call(bv: ByteVector, b: Boolean, int: Int, str: String, list: List[Int]) = {\n" +
" let asset = Issue(\"Asset\", \"\", 1, 0, true)\n" +
" let assetId = asset.calculateAssetId()\n" +
" let lease = Lease(inv.caller, 7)\n" +
" let leaseId = lease.calculateLeaseId()\n" +
" [\n" +
" BinaryEntry(\"bin\", assetId),\n" +
" BooleanEntry(\"bool\", true),\n" +
" IntegerEntry(\"int\", 100500),\n" +
" StringEntry(\"assetId\", assetId.toBase58String()),\n" +
" StringEntry(\"leaseId\", leaseId.toBase58String()),\n" +
" StringEntry(\"del\", \"\"),\n" +
" DeleteEntry(\"del\"),\n" +
" asset,\n" +
" SponsorFee(assetId, 1),\n" +
" Reissue(assetId, 4, false),\n" +
" Burn(assetId, 3),\n" +
" ScriptTransfer(inv.caller, 2, assetId),\n" +
" lease,\n" +
" LeaseCancel(lease.calculateLeaseId())\n" +
" ]\n" +
"}").script();
node.waitForTransaction(node.broadcast(
SetScriptTransaction.builder(script).getSignedWith(bob)).id());
```

#### Calling dApp
```java
InvokeScriptTransaction tx = InvokeScriptTransaction
.builder(bob.address(), Function.as("call",
BinaryArg.as(alice.address().bytes()),
BooleanArg.as(true),
IntegerArg.as(100500),
StringArg.as(alice.address().toString()),
ListArg.as(IntegerArg.as(100500))
)).payments(
Amount.of(1, assetId),
Amount.of(2, assetId),
Amount.of(3, assetId),
Amount.of(4, assetId),
Amount.of(5, assetId),
Amount.of(6, assetId),
Amount.of(7, assetId),
Amount.of(8, assetId),
Amount.of(9, assetId),
Amount.of(10, assetId)
).extraFee(1_00000000)
.getSignedWith(alice);
node.waitForTransaction(node.broadcast(tx).id());
```

#### Receiving invoke script transaction info
```java
TransactionInfo commonInfo = node.getTransactionInfo(tx.id());
InvokeScriptTransactionInfo txInfo = node.getTransactionInfo(tx.id(), InvokeScriptTransactionInfo.class);
```
61 changes: 37 additions & 24 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.wavesplatform</groupId>
<artifactId>wavesj</artifactId>
<version>1.2.4</version>
<version>1.2.5</version>
<packaging>jar</packaging>

<properties>
Expand Down Expand Up @@ -116,27 +116,27 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<configuration>
<!-- Prevent `gpg` from using pinentry programs -->
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
</configuration>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- <plugin>-->
<!-- <groupId>org.apache.maven.plugins</groupId>-->
<!-- <artifactId>maven-gpg-plugin</artifactId>-->
<!-- <version>1.6</version>-->
<!-- <configuration>-->
<!-- &lt;!&ndash; Prevent `gpg` from using pinentry programs &ndash;&gt;-->
<!-- <gpgArguments>-->
<!-- <arg>&#45;&#45;pinentry-mode</arg>-->
<!-- <arg>loopback</arg>-->
<!-- </gpgArguments>-->
<!-- </configuration>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <id>sign-artifacts</id>-->
<!-- <phase>verify</phase>-->
<!-- <goals>-->
<!-- <goal>sign</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- </plugin>-->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
Expand Down Expand Up @@ -169,7 +169,7 @@
<dependency>
<groupId>com.wavesplatform</groupId>
<artifactId>waves-transactions</artifactId>
<version>1.0.9</version>
<version>1.1.0</version>
</dependency>

<!-- http client -->
Expand All @@ -186,6 +186,13 @@
<version>1.2.3</version>
</dependency>

<!-- util -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>

<!-- testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand All @@ -202,7 +209,7 @@
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.15.3</version>
<version>1.16.3</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -211,5 +218,11 @@
<version>1.15.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
24 changes: 24 additions & 0 deletions src/main/java/com/wavesplatform/wavesj/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ public class Node {
private final URI uri;
private final WavesJMapper mapper;

public Node(URI uri, HttpClient httpClient) throws IOException, NodeException {
this.uri = uri;
this.client = httpClient;
this.mapper = new WavesJMapper();
this.chainId = getAddresses().get(0).chainId();
WavesConfig.chainId(this.chainId);
}

public Node(String url, HttpClient httpClient) throws URISyntaxException, IOException, NodeException {
this(new URI(url), httpClient);
}

public Node(Profile profile, HttpClient httpClient) throws IOException, NodeException {
this(profile.uri(), httpClient);
}

public Node(URI uri) throws IOException, NodeException {
this.uri = uri;
this.client = HttpClients
Expand Down Expand Up @@ -559,6 +575,13 @@ public ScriptInfo compileScript(String source, boolean enableCompaction) throws
TypeRef.SCRIPT_INFO);
}

public String ethToWavesAsset(String asset) throws NodeException, IOException {
return asType(
get("/eth/assets").addParameter("id", asset),
TypeRef.ASSETS_DETAILS
).get(0).assetId().encoded();
}

//===============
// WAITINGS
//===============
Expand Down Expand Up @@ -684,6 +707,7 @@ protected RequestBuilder post(String path) {
}

protected HttpResponse exec(HttpUriRequest request) throws IOException, NodeException {
HttpUriRequest rq = RequestBuilder.get(Profile.STAGENET.uri().resolve("/addresses")).build();
HttpResponse r = client.execute(request);
if (r.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
throw mapper.readValue(r.getEntity().getContent(), NodeException.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.wavesplatform.wavesj.info;

import com.wavesplatform.transactions.EthereumTransaction;
import com.wavesplatform.wavesj.ApplicationStatus;
import com.wavesplatform.wavesj.StateChanges;

public class EthereumTransactionInfo extends TransactionInfo {

private final StateChanges stateChanges;
private final String bytes;

public EthereumTransactionInfo(EthereumTransaction tx, ApplicationStatus applicationStatus, int height,
StateChanges stateChanges, String bytes) {
super(tx, applicationStatus, height);
this.stateChanges = stateChanges;
this.bytes = bytes;
}

public EthereumTransaction tx() {
return (EthereumTransaction) super.tx();
}

public Boolean isTransferTransaction() {
return tx().payload() instanceof EthereumTransaction.Transfer;
}

public Boolean isInvokeTransaction() {
return tx().payload() instanceof EthereumTransaction.Invocation;
}

public StateChanges getStateChanges() {
return stateChanges;
}

public String getBytes() {
return bytes;
}

@Override
public String toString() {
return "EthereumTransaction{} " + super.toString();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wavesplatform.wavesj.json.deser;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
Expand Down Expand Up @@ -63,7 +64,19 @@ else if (tx instanceof InvokeScriptTransaction)
codec.treeToValue(json.get("stateChanges"), StateChanges.class));
else if (tx instanceof UpdateAssetInfoTransaction)
return new UpdateAssetInfoTransactionInfo((UpdateAssetInfoTransaction) tx, status, height);
else if (tx instanceof EthereumTransaction)
return new EthereumTransactionInfo(
(EthereumTransaction) tx,
status,
height,
stateChangesFromJson(codec, json),
json.get("bytes").asText()
);
else
throw new IOException("Can't parse transaction info: " + json.toString());
}

private StateChanges stateChangesFromJson(ObjectCodec codec, JsonNode json) throws JsonProcessingException {
return json.get("payload") != null ? codec.treeToValue(json.get("payload").get("stateChanges"), StateChanges.class) : null;
}
}
Loading

0 comments on commit 3ed7254

Please sign in to comment.