Add endpoint `GET /api/v1/transactions` (#388)
### Summary Tracking issue: https://github.com/wormhole-foundation/wormhole-explorer/issues/385 This pull request implements a new endpoint, `GET /api/v1/transactions`, which will be consumed by the wormhole explorer UI. The endpoint returns a paginated list of transactions, in which each element contains a brief overview of the transaction (ID, txHash, status, etc.). It exposes offset-based pagination via the parameters `page` and `pageSize`. Also, results can be obtained for a specific address by using the `address` query parameter. The response model looks like this: ```json { "transactions": [ { "id": "1/5ec18c34b47c63d17ab43b07b9b2319ea5ee2d163bce2e467000174e238c8e7f/12965", "timestamp": "2023-06-08T19:30:19Z", "txHash": "a302c4ab2d6b9a6003951d2e91f8fdbb83cfa20f6ffb588b95ef0290aab37066", "originChain": 1, "status": "ongoing" }, { "id": "22/0000000000000000000000000000000000000000000000000000000000000001/18308", "timestamp": "2023-06-08T19:17:14Z", "txHash": "00000000000000000000000000000000000000000000000000000000000047e7", "originChain": 22, "destinationAddress": "0x00000000000000000000000067e8a40816a983fbe3294aaebd0cc2391815b86b", "destinationChain": 5, "tokenAmount": "0.12", "usdAmount": "0.12012", "symbol": "USDC", "status": "completed" }, ... ] } ``` ### Limitations of the current implementation 1. Doesn't return the total number of results (this may result in a performance issue when we filter by address) 2. Can only filter by receiver address (we don't have sender information in the database yet)
This commit is contained in:
parent
d0436fa46d
commit
a0475ab17e
175
api/docs/docs.go
175
api/docs/docs.go
|
@ -1,5 +1,4 @@
|
||||||
// Code generated by swaggo/swag. DO NOT EDIT.
|
// Code generated by swaggo/swag. DO NOT EDIT
|
||||||
|
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import "github.com/swaggo/swag"
|
import "github.com/swaggo/swag"
|
||||||
|
@ -575,12 +574,19 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"allOf": [
|
||||||
"properties": {
|
{
|
||||||
"status": {
|
"type": "object"
|
||||||
"type": "string"
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -893,12 +899,19 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"allOf": [
|
||||||
"properties": {
|
{
|
||||||
"ready": {
|
"type": "object"
|
||||||
"type": "string"
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ready": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -1027,6 +1040,43 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/transactions/": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns transactions. Output is paginated.",
|
||||||
|
"tags": [
|
||||||
|
"Wormscan"
|
||||||
|
],
|
||||||
|
"operationId": "list-transactions",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Page number. Starts at 0.",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of elements per page.",
|
||||||
|
"name": "pageSize",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/transactions.ListTransactionsResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/vaas/": {
|
"/api/v1/vaas/": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns all VAAs. Output is paginated and can also be be sorted.",
|
"description": "Returns all VAAs. Output is paginated and can also be be sorted.",
|
||||||
|
@ -1575,15 +1625,22 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"allOf": [
|
||||||
"properties": {
|
{
|
||||||
"vaaBytes": {
|
"type": "object"
|
||||||
"type": "array",
|
},
|
||||||
"items": {
|
{
|
||||||
"type": "integer"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"vaaBytes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -1629,15 +1686,22 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"allOf": [
|
||||||
"properties": {
|
{
|
||||||
"vaaBytes": {
|
"type": "object"
|
||||||
"type": "array",
|
},
|
||||||
"items": {
|
{
|
||||||
"type": "integer"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"vaaBytes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -1676,6 +1740,41 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"github_com_wormhole-foundation_wormhole-explorer_api_routes_wormscan_transactions.TransactionOverview": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"destinationAddress": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"destinationChain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"originChain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"$ref": "#/definitions/transactions.TxStatus"
|
||||||
|
},
|
||||||
|
"symbol": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tokenAmount": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"txHash": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"usdAmount": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"governor.AvailableNotionalItemResponse": {
|
"governor.AvailableNotionalItemResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2427,6 +2526,17 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"transactions.ListTransactionsResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"transactions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/github_com_wormhole-foundation_wormhole-explorer_api_routes_wormscan_transactions.TransactionOverview"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"transactions.ScorecardsResponse": {
|
"transactions.ScorecardsResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2522,6 +2632,17 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"transactions.TxStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ongoing",
|
||||||
|
"completed"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"TxStatusOngoing",
|
||||||
|
"TxStatusCompleted"
|
||||||
|
]
|
||||||
|
},
|
||||||
"vaa.ChainID": {
|
"vaa.ChainID": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
@ -2667,8 +2788,6 @@ var SwaggerInfo = &swag.Spec{
|
||||||
Description: "Wormhole Guardian API\nThis is the API for the Wormhole Guardian and Explorer.\nThe API has two namespaces: wormscan and guardian.\nwormscan is the namespace for the explorer and the new endpoints. The prefix is /api/v1.\nguardian is the legacy namespace backguard compatible with guardian node API. The prefix is /v1.\nThis API is public and does not require authentication although some endpoints are rate limited.\nCheck each endpoint documentation for more information.",
|
Description: "Wormhole Guardian API\nThis is the API for the Wormhole Guardian and Explorer.\nThe API has two namespaces: wormscan and guardian.\nwormscan is the namespace for the explorer and the new endpoints. The prefix is /api/v1.\nguardian is the legacy namespace backguard compatible with guardian node API. The prefix is /v1.\nThis API is public and does not require authentication although some endpoints are rate limited.\nCheck each endpoint documentation for more information.",
|
||||||
InfoInstanceName: "swagger",
|
InfoInstanceName: "swagger",
|
||||||
SwaggerTemplate: docTemplate,
|
SwaggerTemplate: docTemplate,
|
||||||
LeftDelim: "{{",
|
|
||||||
RightDelim: "}}",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -567,12 +567,19 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"allOf": [
|
||||||
"properties": {
|
{
|
||||||
"status": {
|
"type": "object"
|
||||||
"type": "string"
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -885,12 +892,19 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"allOf": [
|
||||||
"properties": {
|
{
|
||||||
"ready": {
|
"type": "object"
|
||||||
"type": "string"
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ready": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -1019,6 +1033,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/transactions/": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns transactions. Output is paginated.",
|
||||||
|
"tags": [
|
||||||
|
"Wormscan"
|
||||||
|
],
|
||||||
|
"operationId": "list-transactions",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Page number. Starts at 0.",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Number of elements per page.",
|
||||||
|
"name": "pageSize",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/transactions.ListTransactionsResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/vaas/": {
|
"/api/v1/vaas/": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns all VAAs. Output is paginated and can also be be sorted.",
|
"description": "Returns all VAAs. Output is paginated and can also be be sorted.",
|
||||||
|
@ -1567,15 +1618,22 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"allOf": [
|
||||||
"properties": {
|
{
|
||||||
"vaaBytes": {
|
"type": "object"
|
||||||
"type": "array",
|
},
|
||||||
"items": {
|
{
|
||||||
"type": "integer"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"vaaBytes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -1621,15 +1679,22 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"allOf": [
|
||||||
"properties": {
|
{
|
||||||
"vaaBytes": {
|
"type": "object"
|
||||||
"type": "array",
|
},
|
||||||
"items": {
|
{
|
||||||
"type": "integer"
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"vaaBytes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
@ -1668,6 +1733,41 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"github_com_wormhole-foundation_wormhole-explorer_api_routes_wormscan_transactions.TransactionOverview": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"destinationAddress": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"destinationChain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"originChain": {
|
||||||
|
"$ref": "#/definitions/vaa.ChainID"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"$ref": "#/definitions/transactions.TxStatus"
|
||||||
|
},
|
||||||
|
"symbol": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tokenAmount": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"txHash": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"usdAmount": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"governor.AvailableNotionalItemResponse": {
|
"governor.AvailableNotionalItemResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2419,6 +2519,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"transactions.ListTransactionsResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"transactions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/github_com_wormhole-foundation_wormhole-explorer_api_routes_wormscan_transactions.TransactionOverview"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"transactions.ScorecardsResponse": {
|
"transactions.ScorecardsResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -2514,6 +2625,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"transactions.TxStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"ongoing",
|
||||||
|
"completed"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"TxStatusOngoing",
|
||||||
|
"TxStatusCompleted"
|
||||||
|
]
|
||||||
|
},
|
||||||
"vaa.ChainID": {
|
"vaa.ChainID": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
|
@ -16,6 +16,29 @@ definitions:
|
||||||
index:
|
index:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
github_com_wormhole-foundation_wormhole-explorer_api_routes_wormscan_transactions.TransactionOverview:
|
||||||
|
properties:
|
||||||
|
destinationAddress:
|
||||||
|
type: string
|
||||||
|
destinationChain:
|
||||||
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
originChain:
|
||||||
|
$ref: '#/definitions/vaa.ChainID'
|
||||||
|
status:
|
||||||
|
$ref: '#/definitions/transactions.TxStatus'
|
||||||
|
symbol:
|
||||||
|
type: string
|
||||||
|
timestamp:
|
||||||
|
type: string
|
||||||
|
tokenAmount:
|
||||||
|
type: string
|
||||||
|
txHash:
|
||||||
|
type: string
|
||||||
|
usdAmount:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
governor.AvailableNotionalItemResponse:
|
governor.AvailableNotionalItemResponse:
|
||||||
properties:
|
properties:
|
||||||
bigTransactionSize:
|
bigTransactionSize:
|
||||||
|
@ -502,6 +525,13 @@ definitions:
|
||||||
volume:
|
volume:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
transactions.ListTransactionsResponse:
|
||||||
|
properties:
|
||||||
|
transactions:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/github_com_wormhole-foundation_wormhole-explorer_api_routes_wormscan_transactions.TransactionOverview'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
transactions.ScorecardsResponse:
|
transactions.ScorecardsResponse:
|
||||||
properties:
|
properties:
|
||||||
24h_messages:
|
24h_messages:
|
||||||
|
@ -568,6 +598,14 @@ definitions:
|
||||||
volume:
|
volume:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
transactions.TxStatus:
|
||||||
|
enum:
|
||||||
|
- ongoing
|
||||||
|
- completed
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- TxStatusOngoing
|
||||||
|
- TxStatusCompleted
|
||||||
vaa.ChainID:
|
vaa.ChainID:
|
||||||
enum:
|
enum:
|
||||||
- 0
|
- 0
|
||||||
|
@ -1068,10 +1106,12 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
properties:
|
allOf:
|
||||||
status:
|
- type: object
|
||||||
type: string
|
- properties:
|
||||||
type: object
|
status:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
"500":
|
"500":
|
||||||
|
@ -1283,10 +1323,12 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
properties:
|
allOf:
|
||||||
ready:
|
- type: object
|
||||||
type: string
|
- properties:
|
||||||
type: object
|
ready:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
"500":
|
"500":
|
||||||
|
@ -1381,6 +1423,30 @@ paths:
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
tags:
|
tags:
|
||||||
- Wormscan
|
- Wormscan
|
||||||
|
/api/v1/transactions/:
|
||||||
|
get:
|
||||||
|
description: Returns transactions. Output is paginated.
|
||||||
|
operationId: list-transactions
|
||||||
|
parameters:
|
||||||
|
- description: Page number. Starts at 0.
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: Number of elements per page.
|
||||||
|
in: query
|
||||||
|
name: pageSize
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/transactions.ListTransactionsResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
tags:
|
||||||
|
- Wormscan
|
||||||
/api/v1/vaas/:
|
/api/v1/vaas/:
|
||||||
get:
|
get:
|
||||||
description: Returns all VAAs. Output is paginated and can also be be sorted.
|
description: Returns all VAAs. Output is paginated and can also be be sorted.
|
||||||
|
@ -1758,12 +1824,14 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
properties:
|
allOf:
|
||||||
vaaBytes:
|
- type: object
|
||||||
items:
|
- properties:
|
||||||
type: integer
|
vaaBytes:
|
||||||
type: array
|
items:
|
||||||
type: object
|
type: integer
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
"500":
|
"500":
|
||||||
|
@ -1794,12 +1862,14 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
properties:
|
allOf:
|
||||||
vaaBytes:
|
- type: object
|
||||||
items:
|
- properties:
|
||||||
type: integer
|
vaaBytes:
|
||||||
type: array
|
items:
|
||||||
type: object
|
type: integer
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
"500":
|
"500":
|
||||||
|
|
|
@ -13,7 +13,9 @@ import (
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/internal/tvl"
|
"github.com/wormhole-foundation/wormhole-explorer/api/internal/tvl"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/api/types"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
@ -138,6 +140,12 @@ union(tables: [summarized, raw])
|
||||||
|> top(columns: ["_value"], n: 7)
|
|> top(columns: ["_value"], n: 7)
|
||||||
`
|
`
|
||||||
|
|
||||||
|
type repositoryCollections struct {
|
||||||
|
vaas *mongo.Collection
|
||||||
|
parsedVaa *mongo.Collection
|
||||||
|
globalTransactions *mongo.Collection
|
||||||
|
}
|
||||||
|
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
tvl *tvl.Tvl
|
tvl *tvl.Tvl
|
||||||
influxCli influxdb2.Client
|
influxCli influxdb2.Client
|
||||||
|
@ -146,10 +154,8 @@ type Repository struct {
|
||||||
bucket30DaysRetention string
|
bucket30DaysRetention string
|
||||||
bucket24HoursRetention string
|
bucket24HoursRetention string
|
||||||
db *mongo.Database
|
db *mongo.Database
|
||||||
collections struct {
|
collections repositoryCollections
|
||||||
globalTransactions *mongo.Collection
|
logger *zap.Logger
|
||||||
}
|
|
||||||
logger *zap.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepository(
|
func NewRepository(
|
||||||
|
@ -169,8 +175,12 @@ func NewRepository(
|
||||||
bucket30DaysRetention: bucket30DaysRetention,
|
bucket30DaysRetention: bucket30DaysRetention,
|
||||||
bucketInfiniteRetention: bucketInfiniteRetention,
|
bucketInfiniteRetention: bucketInfiniteRetention,
|
||||||
db: db,
|
db: db,
|
||||||
collections: struct{ globalTransactions *mongo.Collection }{globalTransactions: db.Collection("globalTransactions")},
|
collections: repositoryCollections{
|
||||||
logger: logger,
|
vaas: db.Collection("vaas"),
|
||||||
|
parsedVaa: db.Collection("parsedVaa"),
|
||||||
|
globalTransactions: db.Collection("globalTransactions"),
|
||||||
|
},
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &r
|
return &r
|
||||||
|
@ -684,3 +694,254 @@ func (r *Repository) findGlobalTransactionByID(ctx context.Context, q *GlobalTra
|
||||||
|
|
||||||
return &globalTranstaction, nil
|
return &globalTranstaction, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionOverview models a brief overview of a transactions (ID, txHash, status, etc.)
|
||||||
|
type TransactionOverview struct {
|
||||||
|
ID string `bson:"_id"`
|
||||||
|
EmitterChain sdk.ChainID `bson:"emitterChain"`
|
||||||
|
TxHash string `bson:"txHash"`
|
||||||
|
Timestamp time.Time `bson:"timestamp"`
|
||||||
|
ToAddress string `bson:"toAddress"`
|
||||||
|
ToChain sdk.ChainID `bson:"toChain"`
|
||||||
|
Symbol string `bson:"symbol"`
|
||||||
|
UsdAmount string `bson:"usdAmount"`
|
||||||
|
TokenAmount string `bson:"tokenAmount"`
|
||||||
|
GlobalTransations []GlobalTransactionDoc `bson:"globalTransactions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTransactionsInput is used as the output for the function `ListTransactions`
|
||||||
|
type ListTransactonsOutput struct {
|
||||||
|
Transactions []TransactionOverview
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTransactions returns a sorted list of transactions.
|
||||||
|
//
|
||||||
|
// Pagination is implemented using a keyset cursor pattern, based on the (timestamp, ID) pair.
|
||||||
|
func (r *Repository) ListTransactions(
|
||||||
|
ctx context.Context,
|
||||||
|
pagination *pagination.Pagination,
|
||||||
|
) (*ListTransactonsOutput, error) {
|
||||||
|
|
||||||
|
// Build the aggregation pipeline
|
||||||
|
var pipeline mongo.Pipeline
|
||||||
|
{
|
||||||
|
// Specify sorting criteria
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$sort", bson.D{
|
||||||
|
bson.E{"timestamp", -1},
|
||||||
|
bson.E{"_id", -1},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `transferPrices` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "transferPrices"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "transferPrices"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `vaaIdTxHash` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "vaaIdTxHash"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "vaaIdTxHash"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `parsedVaa` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "parsedVaa"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "parsedVaa"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `globalTransactions` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "globalTransactions"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "globalTransactions"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// add nested fields
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$addFields", bson.D{
|
||||||
|
{"txHash", bson.M{"$arrayElemAt": []interface{}{"$vaaIdTxHash.txHash", 0}}},
|
||||||
|
{"toAddress", bson.M{"$arrayElemAt": []interface{}{"$parsedVaa.result.toAddress", 0}}},
|
||||||
|
{"toChain", bson.M{"$arrayElemAt": []interface{}{"$parsedVaa.result.toChain", 0}}},
|
||||||
|
{"symbol", bson.M{"$arrayElemAt": []interface{}{"$transferPrices.symbol", 0}}},
|
||||||
|
{"usdAmount", bson.M{"$arrayElemAt": []interface{}{"$transferPrices.usdAmount", 0}}},
|
||||||
|
{"tokenAmount", bson.M{"$arrayElemAt": []interface{}{"$transferPrices.tokenAmount", 0}}},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Unset unused fields
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$unset", []interface{}{"transferPrices", "vaaTxIdHash", "parsedVaa"}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Skip initial results
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$skip", pagination.Skip},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Limit size of results
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$limit", pagination.Limit},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the aggregation pipeline
|
||||||
|
cur, err := r.collections.vaas.Aggregate(ctx, pipeline)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("failed execute aggregation pipeline", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read results from cursor
|
||||||
|
var documents []TransactionOverview
|
||||||
|
err = cur.All(ctx, &documents)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("failed to decode cursor", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build result and return
|
||||||
|
response := ListTransactonsOutput{
|
||||||
|
Transactions: documents,
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTransactionsByAddress returns a sorted list of transactions for a given address.
|
||||||
|
//
|
||||||
|
// Pagination is implemented using a keyset cursor pattern, based on the (timestamp, ID) pair.
|
||||||
|
func (r *Repository) ListTransactionsByAddress(
|
||||||
|
ctx context.Context,
|
||||||
|
address *types.Address,
|
||||||
|
pagination *pagination.Pagination,
|
||||||
|
) (*ListTransactonsOutput, error) {
|
||||||
|
|
||||||
|
// Build the aggregation pipeline
|
||||||
|
var pipeline mongo.Pipeline
|
||||||
|
{
|
||||||
|
// filter by address
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$match", bson.D{{"result.toAddress", bson.M{"$eq": "0x" + address.Hex()}}}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// specify sorting criteria
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$sort", bson.D{bson.E{"indexedAt", -1}}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `transferPrices` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "transferPrices"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "transferPrices"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `vaas` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "vaas"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "vaas"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `vaaIdTxHash` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "vaaIdTxHash"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "vaaIdTxHash"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `parsedVaa` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "parsedVaa"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "parsedVaa"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// left outer join on the `globalTransactions` collection
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$lookup", bson.D{
|
||||||
|
{"from", "globalTransactions"},
|
||||||
|
{"localField", "_id"},
|
||||||
|
{"foreignField", "_id"},
|
||||||
|
{"as", "globalTransactions"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// add nested fields
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$addFields", bson.D{
|
||||||
|
{"txHash", bson.M{"$arrayElemAt": []interface{}{"$vaaIdTxHash.txHash", 0}}},
|
||||||
|
{"timestamp", bson.M{"$arrayElemAt": []interface{}{"$vaas.timestamp", 0}}},
|
||||||
|
{"toAddress", bson.M{"$arrayElemAt": []interface{}{"$parsedVaa.result.toAddress", 0}}},
|
||||||
|
{"toChain", bson.M{"$arrayElemAt": []interface{}{"$parsedVaa.result.toChain", 0}}},
|
||||||
|
{"symbol", bson.M{"$arrayElemAt": []interface{}{"$transferPrices.symbol", 0}}},
|
||||||
|
{"usdAmount", bson.M{"$arrayElemAt": []interface{}{"$transferPrices.usdAmount", 0}}},
|
||||||
|
{"tokenAmount", bson.M{"$arrayElemAt": []interface{}{"$transferPrices.tokenAmount", 0}}},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Unset unused fields
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$unset", []interface{}{"transferPrices", "vaas", "vaaTxIdHash", "parsedVaa"}},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Skip initial results
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$skip", pagination.Skip},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Limit size of results
|
||||||
|
pipeline = append(pipeline, bson.D{
|
||||||
|
{"$limit", pagination.Limit},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the aggregation pipeline
|
||||||
|
cur, err := r.collections.parsedVaa.Aggregate(ctx, pipeline)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("failed execute aggregation pipeline", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read results from cursor
|
||||||
|
var documents []TransactionOverview
|
||||||
|
err = cur.All(ctx, &documents)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("failed to decode cursor", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build result and return
|
||||||
|
response := ListTransactonsOutput{
|
||||||
|
Transactions: documents,
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/api/internal/pagination"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/types"
|
"github.com/wormhole-foundation/wormhole-explorer/api/types"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
@ -73,3 +74,20 @@ func (s *Service) GetTokenByChainAndAddress(ctx context.Context, chainID vaa.Cha
|
||||||
Decimals: tokenMetadata.Decimals,
|
Decimals: tokenMetadata.Decimals,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListTransactions(
|
||||||
|
ctx context.Context,
|
||||||
|
pagination *pagination.Pagination,
|
||||||
|
) (*ListTransactonsOutput, error) {
|
||||||
|
|
||||||
|
return s.repo.ListTransactions(ctx, pagination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListTransactionsByAddress(
|
||||||
|
ctx context.Context,
|
||||||
|
address *types.Address,
|
||||||
|
pagination *pagination.Pagination,
|
||||||
|
) (*ListTransactonsOutput, error) {
|
||||||
|
|
||||||
|
return s.repo.ListTransactionsByAddress(ctx, address, pagination)
|
||||||
|
}
|
||||||
|
|
|
@ -175,7 +175,33 @@ func ExtractObservationHash(c *fiber.Ctx, l *zap.Logger) (string, error) {
|
||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractAddress(c *fiber.Ctx, l *zap.Logger) (*types.Address, error) {
|
// ExtractAddressFromQueryParams parses the `address` parameter from the query string.
|
||||||
|
//
|
||||||
|
// If the parameter doesn't exist, the function returns a nil address without errors.
|
||||||
|
func ExtractAddressFromQueryParams(c *fiber.Ctx, l *zap.Logger) (*types.Address, error) {
|
||||||
|
|
||||||
|
val := c.Query("address")
|
||||||
|
|
||||||
|
if val == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse the address
|
||||||
|
addr, err := types.StringToAddress(val, true /*acceptSolanaFormat*/)
|
||||||
|
if err != nil {
|
||||||
|
requestID := fmt.Sprintf("%v", c.Locals("requestid"))
|
||||||
|
l.Error("failed to decode address",
|
||||||
|
zap.Error(err),
|
||||||
|
zap.String("requestID", requestID),
|
||||||
|
)
|
||||||
|
return nil, response.NewInvalidParamError(c, "MALFORMED ADDR", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractAddressFromPath parses the `id` parameter from the route path.
|
||||||
|
func ExtractAddressFromPath(c *fiber.Ctx, l *zap.Logger) (*types.Address, error) {
|
||||||
|
|
||||||
val := c.Params("id")
|
val := c.Params("id")
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ func NewController(srv *address.Service, logger *zap.Logger) *Controller {
|
||||||
// @Router /api/v1/address/{address} [get]
|
// @Router /api/v1/address/{address} [get]
|
||||||
func (c *Controller) FindById(ctx *fiber.Ctx) error {
|
func (c *Controller) FindById(ctx *fiber.Ctx) error {
|
||||||
|
|
||||||
address, err := middleware.ExtractAddress(ctx, c.logger)
|
address, err := middleware.ExtractAddressFromPath(ctx, c.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ func RegisterRoutes(
|
||||||
// accounts resource
|
// accounts resource
|
||||||
api.Get("/address/:id", addressCtrl.FindById)
|
api.Get("/address/:id", addressCtrl.FindById)
|
||||||
|
|
||||||
// analytics
|
// analytics, transactions, custom endpoints
|
||||||
api.Get("/global-tx/:chain/:emitter/:sequence", transactionCtrl.FindGlobalTransactionByID)
|
api.Get("/global-tx/:chain/:emitter/:sequence", transactionCtrl.FindGlobalTransactionByID)
|
||||||
api.Get("/last-txs", transactionCtrl.GetLastTransactions)
|
api.Get("/last-txs", transactionCtrl.GetLastTransactions)
|
||||||
api.Get("/scorecards", transactionCtrl.GetScorecards)
|
api.Get("/scorecards", transactionCtrl.GetScorecards)
|
||||||
|
@ -70,6 +70,7 @@ func RegisterRoutes(
|
||||||
api.Get("/top-assets-by-volume", transactionCtrl.GetTopAssets)
|
api.Get("/top-assets-by-volume", transactionCtrl.GetTopAssets)
|
||||||
api.Get("/top-chain-pairs-by-num-transfers", transactionCtrl.GetTopChainPairs)
|
api.Get("/top-chain-pairs-by-num-transfers", transactionCtrl.GetTopChainPairs)
|
||||||
api.Get("token/:chain/:token_address", transactionCtrl.GetTokenByChainAndAddress)
|
api.Get("token/:chain/:token_address", transactionCtrl.GetTokenByChainAndAddress)
|
||||||
|
api.Get("/transactions", transactionCtrl.ListTransactions)
|
||||||
|
|
||||||
// vaas resource
|
// vaas resource
|
||||||
vaas := api.Group("/vaas")
|
vaas := api.Group("/vaas")
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/transactions"
|
"github.com/wormhole-foundation/wormhole-explorer/api/handlers/transactions"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/api/middleware"
|
"github.com/wormhole-foundation/wormhole-explorer/api/middleware"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -347,3 +348,81 @@ func (c *Controller) GetTokenByChainAndAddress(ctx *fiber.Ctx) error {
|
||||||
|
|
||||||
return ctx.JSON(token)
|
return ctx.JSON(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListTransactions godoc
|
||||||
|
// @Description Returns transactions. Output is paginated.
|
||||||
|
// @Tags Wormscan
|
||||||
|
// @ID list-transactions
|
||||||
|
// @Param page query integer false "Page number. Starts at 0."
|
||||||
|
// @Param pageSize query integer false "Number of elements per page."
|
||||||
|
// @Success 200 {object} ListTransactionsResponse
|
||||||
|
// @Failure 400
|
||||||
|
// @Failure 500
|
||||||
|
// @Router /api/v1/transactions/ [get]
|
||||||
|
func (c *Controller) ListTransactions(ctx *fiber.Ctx) error {
|
||||||
|
|
||||||
|
// Extract query parameters
|
||||||
|
pagination, err := middleware.ExtractPagination(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
address, err := middleware.ExtractAddressFromQueryParams(ctx, c.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query transactions from the database
|
||||||
|
var queryResult *transactions.ListTransactonsOutput
|
||||||
|
if address != nil {
|
||||||
|
queryResult, err = c.srv.ListTransactionsByAddress(ctx.Context(), address, pagination)
|
||||||
|
} else {
|
||||||
|
queryResult, err = c.srv.ListTransactions(ctx.Context(), pagination)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert query results into the response model
|
||||||
|
response := ListTransactionsResponse{
|
||||||
|
Transactions: make([]TransactionOverview, 0, len(queryResult.Transactions)),
|
||||||
|
}
|
||||||
|
for i := range queryResult.Transactions {
|
||||||
|
|
||||||
|
tx := TransactionOverview{
|
||||||
|
ID: queryResult.Transactions[i].ID,
|
||||||
|
OriginChain: queryResult.Transactions[i].EmitterChain,
|
||||||
|
Timestamp: queryResult.Transactions[i].Timestamp,
|
||||||
|
DestinationAddress: queryResult.Transactions[i].ToAddress,
|
||||||
|
DestinationChain: queryResult.Transactions[i].ToChain,
|
||||||
|
Symbol: queryResult.Transactions[i].Symbol,
|
||||||
|
TokenAmount: queryResult.Transactions[i].TokenAmount,
|
||||||
|
UsdAmount: queryResult.Transactions[i].UsdAmount,
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Solana VAAs, the txHash that we get from the gossip network is not the real transacion hash,
|
||||||
|
// so we have to overwrite it with the real txHash.
|
||||||
|
if queryResult.Transactions[i].EmitterChain == sdk.ChainIDSolana &&
|
||||||
|
len(queryResult.Transactions[i].GlobalTransations) == 1 &&
|
||||||
|
queryResult.Transactions[i].GlobalTransations[0].OriginTx != nil {
|
||||||
|
|
||||||
|
tx.TxHash = queryResult.Transactions[i].GlobalTransations[0].OriginTx.TxHash
|
||||||
|
|
||||||
|
} else {
|
||||||
|
tx.TxHash = queryResult.Transactions[i].TxHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the status based on the outcome of the redeem transaction.
|
||||||
|
if len(queryResult.Transactions[i].GlobalTransations) == 1 &&
|
||||||
|
queryResult.Transactions[i].GlobalTransations[0].DestinationTx != nil &&
|
||||||
|
queryResult.Transactions[i].GlobalTransations[0].DestinationTx.Status == domain.DstTxStatusConfirmed {
|
||||||
|
|
||||||
|
tx.Status = TxStatusCompleted
|
||||||
|
} else {
|
||||||
|
tx.Status = TxStatusOngoing
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Transactions = append(response.Transactions, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.JSON(response)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package transactions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TxStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TxStatusOngoing TxStatus = "ongoing"
|
||||||
|
TxStatusCompleted TxStatus = "completed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransactionOverview is a brief description of a transaction (e.g. ID, txHash, status, etc.).
|
||||||
|
type TransactionOverview struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
TxHash string `json:"txHash,omitempty"`
|
||||||
|
OriginChain sdk.ChainID `json:"originChain"`
|
||||||
|
DestinationAddress string `json:"destinationAddress,omitempty"`
|
||||||
|
DestinationChain sdk.ChainID `json:"destinationChain,omitempty"`
|
||||||
|
TokenAmount string `json:"tokenAmount,omitempty"`
|
||||||
|
UsdAmount string `json:"usdAmount,omitempty"`
|
||||||
|
Symbol string `json:"symbol,omitempty"`
|
||||||
|
Status TxStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTransactionsResponse is the "200 OK" response model for `GET /api/v1/transactions`.
|
||||||
|
type ListTransactionsResponse struct {
|
||||||
|
Transactions []TransactionOverview `json:"transactions"`
|
||||||
|
}
|
|
@ -24,3 +24,9 @@ const (
|
||||||
// SourceTxStatusConfirmed indicates that the transaciton has been processed successfully.
|
// SourceTxStatusConfirmed indicates that the transaciton has been processed successfully.
|
||||||
SourceTxStatusConfirmed SourceTxStatus = "confirmed"
|
SourceTxStatusConfirmed SourceTxStatus = "confirmed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DstTxStatusFailedToProcess = "failed"
|
||||||
|
DstTxStatusConfirmed = "completed"
|
||||||
|
DstTxStatusUnkonwn = "unknown"
|
||||||
|
)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/avast/retry-go"
|
"github.com/avast/retry-go"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/aptos"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/aptos"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
||||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
@ -223,9 +224,9 @@ func (w *AptosWatcher) processTransaction(ctx context.Context, tx aptos.Transact
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
status := TxStatusFailedToProcess
|
status := domain.DstTxStatusFailedToProcess
|
||||||
if txResult.Success {
|
if txResult.Success {
|
||||||
status = TxStatusConfirmed
|
status = domain.DstTxStatusConfirmed
|
||||||
}
|
}
|
||||||
updatedAt := time.Now()
|
updatedAt := time.Now()
|
||||||
globalTx := storage.TransactionUpdate{
|
globalTx := storage.TransactionUpdate{
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/config"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/config"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/support"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/support"
|
||||||
|
@ -53,11 +54,11 @@ type EvmTransaction struct {
|
||||||
func getTxStatus(status string) string {
|
func getTxStatus(status string) string {
|
||||||
switch status {
|
switch status {
|
||||||
case TxStatusSuccess:
|
case TxStatusSuccess:
|
||||||
return TxStatusConfirmed
|
return domain.DstTxStatusConfirmed
|
||||||
case TxStatusFailReverted:
|
case TxStatusFailReverted:
|
||||||
return TxStatusFailedToProcess
|
return domain.DstTxStatusFailedToProcess
|
||||||
default:
|
default:
|
||||||
return TxStatusUnkonwn
|
return domain.DstTxStatusUnkonwn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
solana_types "github.com/gagliardetto/solana-go"
|
solana_types "github.com/gagliardetto/solana-go"
|
||||||
"github.com/gagliardetto/solana-go/rpc"
|
"github.com/gagliardetto/solana-go/rpc"
|
||||||
"github.com/near/borsh-go"
|
"github.com/near/borsh-go"
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/solana"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/solana"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
||||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
@ -378,7 +379,7 @@ func (w *SolanaWatcher) getAccountAddress(inst solana_types.CompiledInstruction,
|
||||||
|
|
||||||
func (w *SolanaWatcher) getStatus(txRpc *rpc.TransactionWithMeta) string {
|
func (w *SolanaWatcher) getStatus(txRpc *rpc.TransactionWithMeta) string {
|
||||||
if txRpc.Meta != nil && txRpc.Meta.Err != nil {
|
if txRpc.Meta != nil && txRpc.Meta.Err != nil {
|
||||||
return TxStatusFailedToProcess
|
return domain.DstTxStatusFailedToProcess
|
||||||
}
|
}
|
||||||
return TxStatusConfirmed
|
return domain.DstTxStatusConfirmed
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/terra"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/internal/terra"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
||||||
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
"github.com/wormhole-foundation/wormhole/sdk/vaa"
|
||||||
|
@ -287,9 +288,9 @@ func filterTransactionMethod(method string) bool {
|
||||||
|
|
||||||
func getStatus(tx terra.Tx) string {
|
func getStatus(tx terra.Tx) string {
|
||||||
if tx.Code == 0 {
|
if tx.Code == 0 {
|
||||||
return TxStatusConfirmed
|
return domain.DstTxStatusConfirmed
|
||||||
}
|
}
|
||||||
return TxStatusFailedToProcess
|
return domain.DstTxStatusFailedToProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TerraWatcher) Close() {
|
func (w *TerraWatcher) Close() {
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package watcher
|
|
||||||
|
|
||||||
const (
|
|
||||||
TxStatusFailedToProcess = "failed"
|
|
||||||
TxStatusConfirmed = "completed"
|
|
||||||
TxStatusUnkonwn = "unknown"
|
|
||||||
)
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -38,27 +39,27 @@ func updateGlobalTransaction(ctx context.Context, tx storage.TransactionUpdate,
|
||||||
// checkTxShouldBeUpdated checks if the transaction should be updated.
|
// checkTxShouldBeUpdated checks if the transaction should be updated.
|
||||||
func checkTxShouldBeUpdated(ctx context.Context, tx storage.TransactionUpdate, getGlobalTransactionByIDFunc FuncGetGlobalTransactionById) (bool, error) {
|
func checkTxShouldBeUpdated(ctx context.Context, tx storage.TransactionUpdate, getGlobalTransactionByIDFunc FuncGetGlobalTransactionById) (bool, error) {
|
||||||
switch tx.Destination.Status {
|
switch tx.Destination.Status {
|
||||||
case TxStatusConfirmed:
|
case domain.DstTxStatusConfirmed:
|
||||||
return true, nil
|
return true, nil
|
||||||
case TxStatusFailedToProcess:
|
case domain.DstTxStatusFailedToProcess:
|
||||||
// check if the transaction exists from the same vaa ID.
|
// check if the transaction exists from the same vaa ID.
|
||||||
oldTx, err := getGlobalTransactionByIDFunc(ctx, tx.ID)
|
oldTx, err := getGlobalTransactionByIDFunc(ctx, tx.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
// if the transaction was already confirmed, then no update it.
|
// if the transaction was already confirmed, then no update it.
|
||||||
if oldTx.Destination.Status == TxStatusConfirmed {
|
if oldTx.Destination.Status == domain.DstTxStatusConfirmed {
|
||||||
return false, ErrTxfailedCannotBeUpdated
|
return false, ErrTxfailedCannotBeUpdated
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
case TxStatusUnkonwn:
|
case domain.DstTxStatusUnkonwn:
|
||||||
// check if the transaction exists from the same vaa ID.
|
// check if the transaction exists from the same vaa ID.
|
||||||
oldTx, err := getGlobalTransactionByIDFunc(ctx, tx.ID)
|
oldTx, err := getGlobalTransactionByIDFunc(ctx, tx.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
// if the transaction was already confirmed or failed to process, then no update it.
|
// if the transaction was already confirmed or failed to process, then no update it.
|
||||||
if oldTx.Destination.Status == TxStatusConfirmed || oldTx.Destination.Status == TxStatusFailedToProcess {
|
if oldTx.Destination.Status == domain.DstTxStatusConfirmed || oldTx.Destination.Status == domain.DstTxStatusFailedToProcess {
|
||||||
return false, ErrTxUnknowCannotBeUpdated
|
return false, ErrTxUnknowCannotBeUpdated
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
||||||
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
"github.com/wormhole-foundation/wormhole-explorer/contract-watcher/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status completed and does not exist transaction with the same vaa ID",
|
name: "tx with status completed and does not exist transaction with the same vaa ID",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusConfirmed,
|
Status: domain.DstTxStatusConfirmed,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{}, storage.ErrDocNotFound
|
return storage.TransactionUpdate{}, storage.ErrDocNotFound
|
||||||
|
@ -33,12 +34,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status completed and already exists a transaction with the same vaa ID with status completed",
|
name: "tx with status completed and already exists a transaction with the same vaa ID with status completed",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusConfirmed,
|
Status: domain.DstTxStatusConfirmed,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusConfirmed,
|
Status: domain.DstTxStatusConfirmed,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: true,
|
expectedUpdate: true,
|
||||||
|
@ -48,12 +49,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status completed and already exist a transaction with the same vaa ID with status failed",
|
name: "tx with status completed and already exist a transaction with the same vaa ID with status failed",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusConfirmed,
|
Status: domain.DstTxStatusConfirmed,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusFailedToProcess,
|
Status: domain.DstTxStatusFailedToProcess,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: true,
|
expectedUpdate: true,
|
||||||
|
@ -63,12 +64,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status completed and already exist a transaction with the same vaa ID with status unknown",
|
name: "tx with status completed and already exist a transaction with the same vaa ID with status unknown",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusConfirmed,
|
Status: domain.DstTxStatusConfirmed,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusUnkonwn,
|
Status: domain.DstTxStatusUnkonwn,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: true,
|
expectedUpdate: true,
|
||||||
|
@ -78,7 +79,7 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status failed and does not exist transaction with the same vaa ID",
|
name: "tx with status failed and does not exist transaction with the same vaa ID",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusFailedToProcess,
|
Status: domain.DstTxStatusFailedToProcess,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{}, storage.ErrDocNotFound
|
return storage.TransactionUpdate{}, storage.ErrDocNotFound
|
||||||
|
@ -90,12 +91,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status failed and already exists a transaction with the same vaa ID with status completed",
|
name: "tx with status failed and already exists a transaction with the same vaa ID with status completed",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusFailedToProcess,
|
Status: domain.DstTxStatusFailedToProcess,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusConfirmed,
|
Status: domain.DstTxStatusConfirmed,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: false,
|
expectedUpdate: false,
|
||||||
|
@ -105,12 +106,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status failed and already exist a transaction with the same vaa ID with status failed",
|
name: "tx with status failed and already exist a transaction with the same vaa ID with status failed",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusFailedToProcess,
|
Status: domain.DstTxStatusFailedToProcess,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusFailedToProcess,
|
Status: domain.DstTxStatusFailedToProcess,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: true,
|
expectedUpdate: true,
|
||||||
|
@ -120,12 +121,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status failed and already exist a transaction with the same vaa ID with status unknown",
|
name: "tx with status failed and already exist a transaction with the same vaa ID with status unknown",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusFailedToProcess,
|
Status: domain.DstTxStatusFailedToProcess,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusUnkonwn,
|
Status: domain.DstTxStatusUnkonwn,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: true,
|
expectedUpdate: true,
|
||||||
|
@ -135,7 +136,7 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status unknown and does not exist transaction with the same vaa ID",
|
name: "tx with status unknown and does not exist transaction with the same vaa ID",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusUnkonwn,
|
Status: domain.DstTxStatusUnkonwn,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{}, storage.ErrDocNotFound
|
return storage.TransactionUpdate{}, storage.ErrDocNotFound
|
||||||
|
@ -147,12 +148,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status unknown and already exists a transaction with the same vaa ID with status completed",
|
name: "tx with status unknown and already exists a transaction with the same vaa ID with status completed",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusUnkonwn,
|
Status: domain.DstTxStatusUnkonwn,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusConfirmed,
|
Status: domain.DstTxStatusConfirmed,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: false,
|
expectedUpdate: false,
|
||||||
|
@ -162,12 +163,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status unknown and already exist a transaction with the same vaa ID with status failed",
|
name: "tx with status unknown and already exist a transaction with the same vaa ID with status failed",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusUnkonwn,
|
Status: domain.DstTxStatusUnkonwn,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusFailedToProcess,
|
Status: domain.DstTxStatusFailedToProcess,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: false,
|
expectedUpdate: false,
|
||||||
|
@ -177,12 +178,12 @@ func TestCheckTxShouldBeUpdated(t *testing.T) {
|
||||||
name: "tx with status unknown and already exist a transaction with the same vaa ID with status unknown",
|
name: "tx with status unknown and already exist a transaction with the same vaa ID with status unknown",
|
||||||
inputTx: storage.TransactionUpdate{
|
inputTx: storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusUnkonwn,
|
Status: domain.DstTxStatusUnkonwn,
|
||||||
}},
|
}},
|
||||||
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
inputGetGlobalTransactionByIDFunc: func(ctx context.Context, id string) (storage.TransactionUpdate, error) {
|
||||||
return storage.TransactionUpdate{
|
return storage.TransactionUpdate{
|
||||||
Destination: storage.DestinationTx{
|
Destination: storage.DestinationTx{
|
||||||
Status: TxStatusUnkonwn,
|
Status: domain.DstTxStatusUnkonwn,
|
||||||
}}, nil
|
}}, nil
|
||||||
},
|
},
|
||||||
expectedUpdate: true,
|
expectedUpdate: true,
|
||||||
|
|
Loading…
Reference in New Issue