mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-23 11:27:51 +00:00
Cleaned up responses from /addresses/* endpoints in that some return text/plain instead of application/json. Removed need for class-local copy of ApiErrorFactory in AddressesResource - using getInstance() instead. Some work still needs to be done on annotating API errors. API error examples in API UI rendered incorrectly - swagger-ui issue? Removed repository-accessing code from api.models.* Added /assets/order/{orderId} for fetching info on specific asset order. NOTE: AssetRepository.getOrdersTrades() now returns trades where order is initiating or target. (Previously was initiating order only). qora.assets.Order.orphan() updated to reflect above change. block-explorer.html fixed to use new API output.
452 lines
16 KiB
Java
452 lines
16 KiB
Java
package api;
|
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
import io.swagger.v3.oas.annotations.Parameter;
|
|
import io.swagger.v3.oas.annotations.extensions.Extension;
|
|
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
|
import io.swagger.v3.oas.annotations.media.Content;
|
|
import io.swagger.v3.oas.annotations.media.Schema;
|
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.Base64;
|
|
import java.util.List;
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.ws.rs.GET;
|
|
import javax.ws.rs.Path;
|
|
import javax.ws.rs.PathParam;
|
|
import javax.ws.rs.Produces;
|
|
import javax.ws.rs.core.Context;
|
|
import javax.ws.rs.core.MediaType;
|
|
|
|
import data.account.AccountBalanceData;
|
|
import data.account.AccountData;
|
|
import qora.account.Account;
|
|
import qora.assets.Asset;
|
|
import qora.crypto.Crypto;
|
|
import repository.DataException;
|
|
import repository.Repository;
|
|
import repository.RepositoryManager;
|
|
import transform.Transformer;
|
|
|
|
@Path("addresses")
|
|
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="/Api/AddressesResource")
|
|
}
|
|
)
|
|
@Tag(name = "Addresses")
|
|
public class AddressesResource {
|
|
|
|
@Context
|
|
HttpServletRequest request;
|
|
|
|
@GET
|
|
@Path("/lastreference/{address}")
|
|
@Operation(
|
|
summary = "Fetch reference for next transaction to be created by address",
|
|
description = "Returns the base64-encoded signature of the last confirmed transaction created by address, failing that: the first incoming transaction to address. Returns \"false\" if there is no transactions.",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET lastreference:address"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the base64-encoded transaction signature or \"false\"",
|
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public String getLastReference(@Parameter(ref = "address") @PathParam("address") String address) {
|
|
if (!Crypto.isValidAddress(address))
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
|
|
|
byte[] lastReference = null;
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
Account account = new Account(repository, address);
|
|
lastReference = account.getLastReference();
|
|
} catch (ApiException e) {
|
|
throw e;
|
|
} catch (DataException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
|
}
|
|
|
|
if(lastReference == null || lastReference.length == 0) {
|
|
return "false";
|
|
} else {
|
|
return Base64.getEncoder().encodeToString(lastReference);
|
|
}
|
|
}
|
|
|
|
@GET
|
|
@Path("/lastreference/{address}/unconfirmed")
|
|
@Operation(
|
|
summary = "Fetch reference for next transaction to be created by address, considering unconfirmed transactions",
|
|
description = "Returns the base64-encoded signature of the last confirmed/unconfirmed transaction created by address, failing that: the first incoming transaction. Returns \\\"false\\\" if there is no transactions.",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET lastreference:address:unconfirmed"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the base64-encoded transaction signature",
|
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public String getLastReferenceUnconfirmed(@PathParam("address") String address) {
|
|
if (!Crypto.isValidAddress(address))
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
|
|
|
byte[] lastReference = null;
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
Account account = new Account(repository, address);
|
|
lastReference = account.getUnconfirmedLastReference();
|
|
} catch (ApiException e) {
|
|
throw e;
|
|
} catch (DataException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
|
}
|
|
|
|
if(lastReference == null || lastReference.length == 0) {
|
|
return "false";
|
|
} else {
|
|
return Base64.getEncoder().encodeToString(lastReference);
|
|
}
|
|
}
|
|
|
|
@GET
|
|
@Path("/validate/{address}")
|
|
@Operation(
|
|
summary = "Validates the given address",
|
|
description = "Returns true/false.",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET validate:address"),
|
|
@ExtensionProperty(name="summary.key", value="operation:summary"),
|
|
@ExtensionProperty(name="description.key", value="operation:description"),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean")),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public boolean validate(@PathParam("address") String address) {
|
|
return Crypto.isValidAddress(address);
|
|
}
|
|
|
|
@GET
|
|
@Path("/generatingbalance/{address}")
|
|
@Operation(
|
|
summary = "Return the generating balance of the given address",
|
|
description = "Returns the effective balance of the given address, used in Proof-of-Stake calculationgs when generating a new block.",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET generatingbalance:address"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the generating balance",
|
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public BigDecimal getGeneratingBalanceOfAddress(@PathParam("address") String address) {
|
|
if (!Crypto.isValidAddress(address))
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
|
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
Account account = new Account(repository, address);
|
|
return account.getGeneratingBalance();
|
|
} catch (ApiException e) {
|
|
throw e;
|
|
} catch (DataException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
|
}
|
|
}
|
|
|
|
@GET
|
|
@Path("/balance/{address}")
|
|
@Operation(
|
|
summary = "Returns the confirmed balance of the given address",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET balance:address"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the balance",
|
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public BigDecimal getGeneratingBalance(@PathParam("address") String address) {
|
|
if (!Crypto.isValidAddress(address))
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
|
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
Account account = new Account(repository, address);
|
|
return account.getConfirmedBalance(Asset.QORA);
|
|
} catch (ApiException e) {
|
|
throw e;
|
|
} catch (DataException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
|
}
|
|
}
|
|
|
|
@GET
|
|
@Path("/assetbalance/{assetid}/{address}")
|
|
@Operation(
|
|
summary = "Asset-specific balance request",
|
|
description = "Returns the confirmed balance of the given address for the given asset key.",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET assetbalance:assetid:address"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\", \"INVALID_ASSET_ID\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the balance",
|
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public BigDecimal getAssetBalance(@PathParam("assetid") long assetid, @PathParam("address") String address) {
|
|
if (!Crypto.isValidAddress(address))
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
|
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
Account account = new Account(repository, address);
|
|
return account.getConfirmedBalance(assetid);
|
|
} catch (ApiException e) {
|
|
throw e;
|
|
} catch (DataException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
|
}
|
|
}
|
|
|
|
@GET
|
|
@Path("/assets/{address}")
|
|
@Operation(
|
|
summary = "All assets owned by this address",
|
|
description = "Returns the list of assets for this address, with balances.",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET assets:address"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the list of assets",
|
|
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AccountBalanceData.class))),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public List<AccountBalanceData> getAssets(@PathParam("address") String address) {
|
|
if (!Crypto.isValidAddress(address))
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
|
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
return repository.getAccountRepository().getAllBalances(address);
|
|
} catch (ApiException e) {
|
|
throw e;
|
|
} catch (DataException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
|
}
|
|
}
|
|
|
|
@GET
|
|
@Path("/balance/{address}/{confirmations}")
|
|
@Operation(
|
|
summary = "Calculates the balance of the given address for the given confirmations",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET balance:address:confirmations"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the balance",
|
|
content = @Content(schema = @Schema(implementation = String.class)),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public String getGeneratingBalance(@PathParam("address") String address, @PathParam("confirmations") int confirmations) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
@GET
|
|
@Path("/publickey/{address}")
|
|
@Operation(
|
|
summary = "Get public key of address",
|
|
description = "Returns the base64-encoded account public key of the given address, or \"false\" if address not known or has no public key.",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET publickey:address"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the public key",
|
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public String getPublicKey(@PathParam("address") String address) {
|
|
if (!Crypto.isValidAddress(address))
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
|
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
|
|
|
if (accountData == null)
|
|
return "false";
|
|
|
|
byte[] publicKey = accountData.getPublicKey();
|
|
if (publicKey == null)
|
|
return "false";
|
|
|
|
return Base64.getEncoder().encodeToString(publicKey);
|
|
} catch (ApiException e) {
|
|
throw e;
|
|
} catch (DataException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
|
}
|
|
}
|
|
|
|
@GET
|
|
@Path("/convert/{publickey}")
|
|
@Operation(
|
|
summary = "Convert public key into address",
|
|
description = "Returns account address based on supplied public key. Expects base64-encoded, 32-byte public key.",
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="path", value="GET publickey:address"),
|
|
@ExtensionProperty(name="description.key", value="operation:description")
|
|
}),
|
|
@Extension(properties = {
|
|
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
})
|
|
},
|
|
responses = {
|
|
@ApiResponse(
|
|
description = "the address",
|
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
|
|
extensions = {
|
|
@Extension(name = "translation", properties = {
|
|
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
})
|
|
}
|
|
)
|
|
}
|
|
)
|
|
public String fromPublicKey(@PathParam("publickey") String publicKey) {
|
|
// Decode public key
|
|
byte[] publicKeyBytes;
|
|
try {
|
|
publicKeyBytes = Base64.getDecoder().decode(publicKey);
|
|
} catch (NumberFormatException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY, e);
|
|
}
|
|
|
|
// Correct size for public key?
|
|
if (publicKeyBytes.length != Transformer.PUBLIC_KEY_LENGTH)
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY);
|
|
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
return Crypto.toAddress(publicKeyBytes);
|
|
} catch (ApiException e) {
|
|
throw e;
|
|
} catch (DataException e) {
|
|
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
|
}
|
|
}
|
|
|
|
}
|