diff --git a/Dockerfile b/Dockerfile index 4bd0df8f..b734a57e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,9 @@ RUN go mod download COPY internal internal COPY cmd cmd COPY pkg pkg +RUN mkdir -p /tmp/openapi +COPY api/openapi.json /tmp/openapi/openapi.json +COPY api/openapi.yml /tmp/openapi/openapi.yml RUN apt-get update && \ apt-get install -y libsecp256k1-0 libsodium23 @@ -20,4 +23,6 @@ RUN mkdir -p /app/lib RUN wget -O /app/lib/libemulator.so https://github.com/ton-blockchain/ton/releases/download/v2024.08/libemulator-linux-x86_64.so ENV LD_LIBRARY_PATH=/app/lib/ COPY --from=gobuild /tmp/opentonapi /usr/bin/ +COPY --from=gobuild /tmp/openapi /app/openapi +WORKDIR /app CMD ["/usr/bin/opentonapi"] diff --git a/api/openapi.json b/api/openapi.json index d55d05e0..a534bfb8 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -414,18 +414,6 @@ }, "description": "a list of account ids" }, - "Backup": { - "content": { - "application/octet-stream": { - "schema": { - "format": "binary", - "type": "string" - } - } - }, - "description": "Information for saving backup", - "required": true - }, "BatchBoc": { "content": { "application/json": { @@ -442,6 +430,12 @@ "boc": { "format": "cell", "type": "string" + }, + "meta": { + "additionalProperties": { + "type": "string" + }, + "type": "object" } }, "type": "object" @@ -702,6 +696,10 @@ "properties": { "error": { "type": "string" + }, + "error_code": { + "format": "int64", + "type": "integer" } }, "required": [ @@ -743,6 +741,12 @@ "example": {}, "type": "object" }, + "extra_balance": { + "items": { + "$ref": "#/components/schemas/ExtraCurrency" + }, + "type": "array" + }, "get_methods": { "example": [ "get_item_data" @@ -1065,6 +1069,9 @@ "ElectionsRecoverStake": { "$ref": "#/components/schemas/ElectionsRecoverStakeAction" }, + "ExtraCurrencyTransfer": { + "$ref": "#/components/schemas/ExtraCurrencyTransferAction" + }, "InscriptionMint": { "$ref": "#/components/schemas/InscriptionMintAction" }, @@ -1129,6 +1136,7 @@ "type": { "enum": [ "TonTransfer", + "ExtraCurrencyTransfer", "JettonTransfer", "JettonBurn", "JettonMint", @@ -2284,6 +2292,35 @@ ], "type": "object" }, + "45": { + "description": "precompiled contracts", + "properties": { + "contracts": { + "items": { + "properties": { + "code_hash": { + "format": "address", + "type": "string" + }, + "gas_usage": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "code_hash", + "gas_usage" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "contracts" + ], + "type": "object" + }, "5": { "properties": { "blackhole_addr": { @@ -3092,6 +3129,28 @@ ], "type": "object" }, + "EcPreview": { + "properties": { + "decimals": { + "example": 5, + "type": "integer" + }, + "image": { + "example": "https://cache.tonapi.io/images/extra.jpg", + "type": "string" + }, + "symbol": { + "example": "FMS", + "type": "string" + } + }, + "required": [ + "symbol", + "decimals", + "image" + ], + "type": "object" + }, "ElectionsDepositStakeAction": { "properties": { "amount": { @@ -3208,6 +3267,67 @@ ], "type": "object" }, + "ExtraCurrency": { + "properties": { + "amount": { + "example": "1000000000", + "type": "string", + "x-js-format": "bigint" + }, + "decimals": { + "example": 5, + "type": "integer" + }, + "id": { + "example": 239, + "format": "int32", + "type": "integer" + }, + "name": { + "example": "FMS", + "type": "string" + } + }, + "required": [ + "id", + "amount", + "decimals" + ], + "type": "object" + }, + "ExtraCurrencyTransferAction": { + "properties": { + "amount": { + "description": "amount in quanta of tokens", + "example": "1000000000", + "type": "string", + "x-js-format": "bigint" + }, + "comment": { + "example": "Hi! This is your salary. \nFrom accounting with love.", + "type": "string" + }, + "currency": { + "$ref": "#/components/schemas/EcPreview" + }, + "encrypted_comment": { + "$ref": "#/components/schemas/EncryptedComment" + }, + "recipient": { + "$ref": "#/components/schemas/AccountAddress" + }, + "sender": { + "$ref": "#/components/schemas/AccountAddress" + } + }, + "required": [ + "sender", + "recipient", + "amount", + "currency" + ], + "type": "object" + }, "FoundAccounts": { "properties": { "addresses": { @@ -3710,6 +3830,10 @@ "example": true, "type": "boolean" }, + "preview": { + "example": "https://cache.tonapi.io/images/jetton.jpg", + "type": "string" + }, "score": { "format": "int32", "type": "integer" @@ -3728,7 +3852,8 @@ "total_supply", "metadata", "verification", - "holders_count" + "holders_count", + "preview" ], "type": "object" }, @@ -3762,7 +3887,8 @@ "type": "string" }, "image": { - "example": "https://cache.tonapi.io/images/jetton.jpg", + "description": "this field currently returns a cached image URL (e.g., \"https://cache.tonapi.io/images/jetton.jpg\"). In the future, this will be replaced with the original URL from the metadata. The cached image is already available in the `preview` field of `JettonInfo` and will remain there.", + "example": "https://bitcoincash-example.github.io/website/logo.png", "type": "string" }, "name": { @@ -4158,6 +4284,12 @@ "format": "int64", "type": "integer", "x-js-format": "bigint" + }, + "value_extra": { + "items": { + "$ref": "#/components/schemas/ExtraCurrency" + }, + "type": "array" } }, "required": [ @@ -7249,6 +7381,16 @@ }, "type": "array" } + }, + { + "in": "query", + "name": "fix_order", + "required": false, + "schema": { + "default": true, + "description": "A temporary fix to switch to a scheme with direct ordering of arguments. \nIf equal to false, then the method takes arguments in direct order,\ne.g. for get_nft_content(int index, cell individual_content) we pass a list of arguments [index, individual_content].\nIf equal to true, then the method takes arguments in reverse order, e.g. [individual_content, index].", + "type": "boolean" + } } ], "responses": { @@ -9810,6 +9952,51 @@ ] } }, + "/v2/openapi.json": { + "get": { + "description": "Get the openapi.json file", + "operationId": "getOpenapiJson", + "responses": { + "200": { + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "tags": [ + "Openapi" + ] + } + }, + "/v2/openapi.yml": { + "get": { + "description": "Get the openapi.yml file", + "operationId": "getOpenapiYml", + "responses": { + "200": { + "content": { + "application/x-yaml": { + "schema": { + "format": "binary", + "type": "string" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Error" + } + }, + "tags": [ + "Openapi" + ] + } + }, "/v2/pubkeys/{public_key}/wallets": { "get": { "description": "Get wallets by public key", @@ -10440,76 +10627,6 @@ ] } }, - "/v2/wallet/backup": { - "get": { - "description": "Get backup info", - "operationId": "getWalletBackup", - "parameters": [ - { - "in": "header", - "name": "X-TonConnect-Auth", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "properties": { - "dump": { - "type": "string" - } - }, - "required": [ - "dump" - ], - "type": "object" - } - } - }, - "description": "get wallet dump" - }, - "default": { - "$ref": "#/components/responses/Error" - } - }, - "tags": [ - "Wallet" - ] - }, - "put": { - "description": "Set backup info", - "operationId": "setWalletBackup", - "parameters": [ - { - "in": "header", - "name": "X-TonConnect-Auth", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/Backup" - }, - "responses": { - "200": { - "description": "success" - }, - "default": { - "$ref": "#/components/responses/Error" - } - }, - "tags": [ - "Wallet" - ] - } - }, "/v2/wallet/emulate": { "post": { "description": "Emulate sending message to blockchain", diff --git a/api/openapi.yml b/api/openapi.yml index 4753c0b6..38d7b51a 100644 --- a/api/openapi.yml +++ b/api/openapi.yml @@ -86,6 +86,34 @@ tags: url: https://docs.tonconsole.com/tonapi/rest-api/utilities paths: + /v2/openapi.json: + get: + description: Get the openapi.json file + operationId: getOpenapiJson + tags: + - Openapi + responses: + '200': + content: + application/json: + schema: { } # Free-form JSON value + 'default': + $ref: '#/components/responses/Error' + /v2/openapi.yml: + get: + description: Get the openapi.yml file + operationId: getOpenapiYml + tags: + - Openapi + responses: + '200': + content: + application/x-yaml: + schema: + type: string + format: binary + 'default': + $ref: '#/components/responses/Error' /v2/status: get: description: Status @@ -396,6 +424,17 @@ paths: items: type: string example: [ "0:9a33970f617bcd71acf2cd28357c067aa31859c02820d8f01d74c88063a8f4d8" ] + - name: fix_order + in: query + required: false + schema: + type: boolean + description: |- + A temporary fix to switch to a scheme with direct ordering of arguments. + If equal to false, then the method takes arguments in direct order, + e.g. for get_nft_content(int index, cell individual_content) we pass a list of arguments [index, individual_content]. + If equal to true, then the method takes arguments in reverse order, e.g. [individual_content, index]. + default: true responses: '200': description: method execution result @@ -1931,51 +1970,6 @@ paths: $ref: '#/components/schemas/AccountInfoByStateInit' 'default': $ref: '#/components/responses/Error' - - /v2/wallet/backup: - get: - description: Get backup info - operationId: getWalletBackup - tags: - - Wallet - parameters: - - in: header - name: X-TonConnect-Auth - schema: - type: string - required: true - responses: - '200': - description: get wallet dump - content: - application/json: - schema: - type: object - required: - - dump - properties: - dump: - type: string - 'default': - $ref: '#/components/responses/Error' - put: - description: Set backup info - operationId: setWalletBackup - tags: - - Wallet - parameters: - - in: header - name: X-TonConnect-Auth - schema: - type: string - required: true - requestBody: - $ref: "#/components/requestBodies/Backup" - responses: - '200': - description: success - 'default': - $ref: '#/components/responses/Error' /v2/wallet/auth/proof: post: description: Account verification and token issuance @@ -3190,6 +3184,10 @@ components: items: type: string format: cell + meta: + type: object + additionalProperties: + type: string EmulationBoc: description: bag-of-cells serialized to base64/hex and additional parameters to configure emulation required: true @@ -3337,14 +3335,6 @@ components: state_init: type: string format: cell-base64 - Backup: - description: "Information for saving backup" - required: true - content: - application/octet-stream: - schema: - type: string - format: binary TonConnectStateInit: description: "Data that is expected" required: true @@ -3765,6 +3755,10 @@ components: format: int64 x-js-format: bigint example: 60000000 + value_extra: + type: array + items: + $ref: '#/components/schemas/ExtraCurrency' fwd_fee: type: integer format: int64 @@ -4642,6 +4636,10 @@ components: format: int64 example: 123456789 x-js-format: bigint + extra_balance: + type: array + items: + $ref: '#/components/schemas/ExtraCurrency' currencies_balance: description: "{'USD': 1, 'IDR': 1000}" type: object @@ -5288,6 +5286,27 @@ components: example: 0:0000000000000000000000000000000000000000000000000000000000000000 suspended_until: type: integer + + "45": + type: object + description: precompiled contracts + required: + - contracts + properties: + contracts: + type: array + items: + type: object + required: + - code_hash + - gas_usage + properties: + code_hash: + type: string + format: address + gas_usage: + type: integer + format: int64 "71": type: object description: Bridge parameters for wrapping TON in other networks. @@ -5760,6 +5779,7 @@ components: example: "TonTransfer" enum: - TonTransfer + - ExtraCurrencyTransfer - JettonTransfer - JettonBurn - JettonMint @@ -5788,6 +5808,8 @@ components: - failed TonTransfer: $ref: '#/components/schemas/TonTransferAction' + ExtraCurrencyTransfer: + $ref: '#/components/schemas/ExtraCurrencyTransferAction' ContractDeploy: $ref: '#/components/schemas/ContractDeployAction' JettonTransfer: @@ -5858,6 +5880,46 @@ components: $ref: '#/components/schemas/EncryptedComment' refund: $ref: '#/components/schemas/Refund' + EcPreview: + type: object + required: + - symbol + - decimals + - image + properties: + symbol: + type: string + example: FMS + decimals: + type: integer + example: 5 + image: + type: string + example: https://cache.tonapi.io/images/extra.jpg + ExtraCurrencyTransferAction: + type: object + required: + - sender + - recipient + - amount + - currency + properties: + sender: + $ref: '#/components/schemas/AccountAddress' + recipient: + $ref: '#/components/schemas/AccountAddress' + amount: + type: string + x-js-format: bigint + example: "1000000000" + description: amount in quanta of tokens + comment: + type: string + example: "Hi! This is your salary. \nFrom accounting with love." + encrypted_comment: + $ref: '#/components/schemas/EncryptedComment' + currency: + $ref: '#/components/schemas/EcPreview' SmartContractAction: type: object required: @@ -6882,7 +6944,8 @@ components: example: "9" image: type: string - example: "https://cache.tonapi.io/images/jetton.jpg" + example: "https://bitcoincash-example.github.io/website/logo.png" + description: this field currently returns a cached image URL (e.g., "https://cache.tonapi.io/images/jetton.jpg"). In the future, this will be replaced with the original URL from the metadata. The cached image is already available in the `preview` field of `JettonInfo` and will remain there. description: type: string example: Wrapped Toncoin @@ -6954,6 +7017,7 @@ components: - metadata - verification - holders_count + - preview properties: mintable: type: boolean @@ -6966,6 +7030,9 @@ components: $ref: '#/components/schemas/AccountAddress' metadata: $ref: '#/components/schemas/JettonMetadata' + preview: + type: string + example: "https://cache.tonapi.io/images/jetton.jpg" verification: $ref: '#/components/schemas/JettonVerificationType' holders_count: @@ -7411,6 +7478,27 @@ components: type: integer format: int64 example: 1668436763 + ExtraCurrency: + type: object + required: + - id + - amount + - decimals + properties: + id: + type: integer + example: 239 + format: int32 + amount: + type: string + x-js-format: bigint + example: "1000000000" + name: + type: string + example: FMS + decimals: + type: integer + example: 5 responses: Error: @@ -7424,3 +7512,6 @@ components: properties: error: type: string + error_code: + type: integer + format: int64 \ No newline at end of file diff --git a/api/rt.yml b/api/rt.yml index 42b38157..6d0d9357 100644 --- a/api/rt.yml +++ b/api/rt.yml @@ -104,6 +104,29 @@ paths: example: { } 'default': $ref: '#/components/responses/Error' + /webhooks/{webhook_id}/logs: + get: + description: "Get logs of failed attempts to deliver notifications to the webhook" + operationId: getFailureLogs + parameters: + - $ref: '#/components/parameters/tokenQuery' + - $ref: '#/components/parameters/webhookId' + - in: query + name: offset + required: false + schema: + type: integer + default: 0 + minimum: 0 + responses: + '200': + description: "list of webhooks" + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookLogs' + 'default': + $ref: '#/components/responses/Error' /usage-stats: get: operationId: getUsageStats @@ -173,6 +196,23 @@ paths: $ref: '#/components/schemas/WebhookAccountTxSubscriptions' 'default': $ref: '#/components/responses/Error' + /webhooks/{webhook_id}/msg-opcode/subscriptions: + get: + operationId: webhookNewContractsSubscriptions + parameters: + - $ref: '#/components/parameters/tokenQuery' + - $ref: '#/components/parameters/webhookId' + - $ref: '#/components/parameters/offsetQuery' + - $ref: '#/components/parameters/limitQuery' + responses: + '200': + description: "subscriptions" + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookMsgOpcodeSubscriptions' + 'default': + $ref: '#/components/responses/Error' /webhooks/{webhook_id}/subscribe-new-contracts: post: description: "receive a notification when a new contract is deployed to the blockchain" @@ -297,11 +337,9 @@ components: schema: type: string example: NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODQ3... - required: true tokenQuery: in: query name: token - required: true schema: type: string example: NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODQ3... @@ -410,6 +448,30 @@ components: webhooks_failed: type: integer format: int64 + WebhookLogs: + type: object + required: + - logs + properties: + next_offset: + type: integer + format: int64 + logs: + type: array + items: + type: object + required: + - timestamp + - message + - event_type + properties: + message: + type: string + event_type: + type: string + timestamp: + type: string + format: date-time WebhookList: type: object required: @@ -429,6 +491,8 @@ components: - status_failed_attempts - subscribed_accounts - subscribed_to_mempool + - subscribed_to_new_contracts + - subscribed_msg_opcodes properties: id: type: integer @@ -439,8 +503,12 @@ components: type: string subscribed_accounts: type: integer + subscribed_msg_opcodes: + type: integer subscribed_to_mempool: type: boolean + subscribed_to_new_contracts: + type: boolean status: type: string enum: @@ -481,8 +549,31 @@ components: failed_attempts: type: integer format: int64 - - + WebhookMsgOpcodeSubscriptions: + type: object + required: + - subscriptions + properties: + subscriptions: + type: array + items: + type: object + required: + - opcode + - status + properties: + opcode: + type: string + status: + type: string + enum: + - active + - disabled + disabled_at: + type: string + format: date-time + disabled_reason: + type: string responses: Error: description: Some error during request processing diff --git a/go.mod b/go.mod index 38d22c97..b14c109c 100644 --- a/go.mod +++ b/go.mod @@ -21,11 +21,10 @@ require ( github.com/prometheus/client_golang v1.14.0 github.com/puzpuzpuz/xsync/v2 v2.4.0 github.com/shopspring/decimal v1.3.1 - github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 github.com/sourcegraph/conc v0.3.0 github.com/stretchr/testify v1.9.0 github.com/tonkeeper/scam_backoffice_rules v0.0.0-20241106130559-c44de2d4177b - github.com/tonkeeper/tongo v1.10.3 + github.com/tonkeeper/tongo v1.13.0 go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/metric v1.24.0 go.opentelemetry.io/otel/trace v1.24.0 diff --git a/go.sum b/go.sum index b31a1445..b3bde2c1 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,6 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 h1:B1PEwpArrNp4dkQrfxh/abbBAOZBVp0ds+fBEOUOqOc= -github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -272,8 +270,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tonkeeper/scam_backoffice_rules v0.0.0-20241106130559-c44de2d4177b h1:udp2XHUF2gba2mrHbPcEjmeedRoXvpldO+3mXDiKJ2A= github.com/tonkeeper/scam_backoffice_rules v0.0.0-20241106130559-c44de2d4177b/go.mod h1:SqZXYO9vbID8ku+xnnaKXeNGmehxigODGrk5V1KqbRA= -github.com/tonkeeper/tongo v1.10.3 h1:T1xGxikAfVRDLd5SUWDOCKZurlUDRoChoXzFvy8AGsw= -github.com/tonkeeper/tongo v1.10.3/go.mod h1:MjgIgAytFarjCoVjMLjYEtpZNN1f2G/pnZhKjr28cWs= +github.com/tonkeeper/tongo v1.13.0 h1:LesxO+HFrLSkhDYMXLl+zlabZpVTnaMNHUqwkIhzl0c= +github.com/tonkeeper/tongo v1.13.0/go.mod h1:MjgIgAytFarjCoVjMLjYEtpZNN1f2G/pnZhKjr28cWs= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= diff --git a/pkg/addressbook/addressbook.go b/pkg/addressbook/addressbook.go index 998ddf2b..176b366b 100644 --- a/pkg/addressbook/addressbook.go +++ b/pkg/addressbook/addressbook.go @@ -1,14 +1,8 @@ package addressbook import ( - "context" "encoding/json" "fmt" - "github.com/tonkeeper/opentonapi/pkg/core" - imgGenerator "github.com/tonkeeper/opentonapi/pkg/image" - "github.com/tonkeeper/tongo/abi" - "github.com/tonkeeper/tongo/tlb" - "github.com/tonkeeper/tongo/ton" "io" "math/rand" "net/http" @@ -18,8 +12,13 @@ import ( "sync" "time" + "github.com/tonkeeper/opentonapi/pkg/core" + imgGenerator "github.com/tonkeeper/opentonapi/pkg/image" + "github.com/tonkeeper/tongo/abi" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" + "github.com/shopspring/decimal" - "github.com/shurcooL/graphql" "github.com/tonkeeper/tongo" "go.uber.org/zap" "golang.org/x/exp/maps" @@ -29,7 +28,9 @@ import ( "github.com/tonkeeper/opentonapi/pkg/oas" ) -// KnownAddress represents additional manually crafted information about a particular account in the blockchain. +var NormalizeReg = regexp.MustCompile("[^\\p{L}\\p{N}]") + +// KnownAddress represents additional manually crafted information about a particular account in the blockchain type KnownAddress struct { IsScam bool `json:"is_scam,omitempty"` RequireMemo bool `json:"require_memo,omitempty"` @@ -38,6 +39,7 @@ type KnownAddress struct { Image string `json:"image,omitempty"` } +// AttachedAccountType defines different types of accounts (e.g., manual, NFT) type AttachedAccountType string const ( @@ -55,6 +57,7 @@ type AttachedAccount struct { Name string `json:"name"` Preview string `json:"preview"` Wallet ton.AccountID `json:"wallet"` + Slug string `json:"-"` Symbol string `json:"-"` Type AttachedAccountType `json:"-"` Weight int32 `json:"-"` @@ -62,7 +65,7 @@ type AttachedAccount struct { Normalized string `json:"-"` } -// KnownJetton represents additional manually crafted information about a particular jetton in the blockchain. +// KnownJetton represents additional manually crafted information about a particular jetton in the blockchain type KnownJetton struct { Name string `json:"name"` Verification core.TrustType `json:"verification"` @@ -77,7 +80,7 @@ type KnownJetton struct { Coingecko string `json:"coingecko,omitempty"` } -// KnownCollection represents additional manually crafted information about a particular NFT collection in the blockchain. +// KnownCollection represents additional manually crafted information about a particular NFT collection in the blockchain type KnownCollection struct { Address string `json:"address"` Approvers []oas.NftApprovedByItem @@ -104,7 +107,7 @@ func WithAdditionalAddressesSource(a addresser) Option { } } -// Book holds information about known accounts, jettons, NFT collections manually crafted by the tonkeeper team and the community. +// Book holds information about known accounts, jettons, NFT collections manually crafted by the tonkeeper team and the community type Book struct { addressers []addresser @@ -116,12 +119,14 @@ type Book struct { walletsResolved cache.Cache[tongo.AccountID, bool] } +// TFPoolInfo holds information about a token pool type TFPoolInfo struct { Name string `json:"name"` GroupName string `json:"groupName"` Address string `json:"address"` } +// GetAddressInfoByAddress fetches address info if available func (b *Book) GetAddressInfoByAddress(a tongo.AccountID) (KnownAddress, bool) { for i := range b.addressers { if a1, ok := b.addressers[i].GetAddress(a); ok { @@ -131,8 +136,9 @@ func (b *Book) GetAddressInfoByAddress(a tongo.AccountID) (KnownAddress, bool) { return KnownAddress{}, false } +// SearchAttachedAccountsByPrefix searches for accounts by prefix func (b *Book) SearchAttachedAccountsByPrefix(prefix string) []AttachedAccount { - prefix = strings.ToLower(normalizeReg.ReplaceAllString(prefix, "")) + prefix = strings.ToLower(NormalizeReg.ReplaceAllString(prefix, "")) var accounts []AttachedAccount for i := range b.addressers { foundAccounts := b.addressers[i].SearchAttachedAccounts(prefix) @@ -142,12 +148,13 @@ func (b *Book) SearchAttachedAccountsByPrefix(prefix string) []AttachedAccount { } tonDomainPrefix := prefix + "ton" tgDomainPrefix := prefix + "tme" - + // Adjust weight for full matches for i := range accounts { if accounts[i].Normalized == prefix || accounts[i].Normalized == tonDomainPrefix || accounts[i].Normalized == tgDomainPrefix { - accounts[i].Weight *= 100 // full match + accounts[i].Weight *= 100 } } + // Sort and limit the result sort.Slice(accounts, func(i, j int) bool { if accounts[i].Weight == accounts[j].Weight { return len(accounts[i].Name) < len(accounts[j].Name) @@ -160,6 +167,7 @@ func (b *Book) SearchAttachedAccountsByPrefix(prefix string) []AttachedAccount { return accounts } +// GetTFPoolInfo retrieves token pool info for an account func (b *Book) GetTFPoolInfo(a tongo.AccountID) (TFPoolInfo, bool) { b.mu.RLock() defer b.mu.RUnlock() @@ -167,6 +175,7 @@ func (b *Book) GetTFPoolInfo(a tongo.AccountID) (TFPoolInfo, bool) { return info, ok } +// GetKnownCollections returns all known collections func (b *Book) GetKnownCollections() map[tongo.AccountID]KnownCollection { b.mu.RLock() defer b.mu.RUnlock() @@ -178,6 +187,7 @@ func (b *Book) GetKnownCollections() map[tongo.AccountID]KnownCollection { return collections } +// GetCollectionInfoByAddress retrieves collection info for a specific address func (b *Book) GetCollectionInfoByAddress(a tongo.AccountID) (KnownCollection, bool) { b.mu.RLock() defer b.mu.RUnlock() @@ -185,12 +195,14 @@ func (b *Book) GetCollectionInfoByAddress(a tongo.AccountID) (KnownCollection, b return c, ok } +// GetKnownJettons returns all known jettons func (b *Book) GetKnownJettons() map[tongo.AccountID]KnownJetton { b.mu.RLock() defer b.mu.RUnlock() return maps.Clone(b.jettons) } +// GetJettonInfoByAddress fetches jetton info for a specific address func (b *Book) GetJettonInfoByAddress(a tongo.AccountID) (KnownJetton, bool) { b.mu.RLock() defer b.mu.RUnlock() @@ -204,13 +216,14 @@ func (b *Book) GetJettonInfoByAddress(a tongo.AccountID) (KnownJetton, bool) { return j, ok } +// TFPools returns a list of all token pools func (b *Book) TFPools() []tongo.AccountID { b.mu.RLock() defer b.mu.RUnlock() return maps.Keys(b.tfPools) } -// IsWallet returns true if the given address is a wallet. +// IsWallet checks if the address is a wallet func (b *Book) IsWallet(addr tongo.AccountID) (bool, error) { if wallet, ok := b.walletsResolved.Get(addr); ok { return wallet, nil @@ -220,7 +233,7 @@ func (b *Book) IsWallet(addr tongo.AccountID) (bool, error) { return false, fmt.Errorf("failed to figure out if %v is a wallet %w", addr, err) } if status == tlb.AccountNone || status == tlb.AccountUninit { - b.walletsResolved.Set(addr, true, cache.WithExpiration(time.Minute)) //shorter period for non-existing accounts + b.walletsResolved.Set(addr, true, cache.WithExpiration(time.Minute)) // Cache short for non-existing accounts return true, nil } isWallet := false @@ -234,45 +247,49 @@ func (b *Book) IsWallet(addr tongo.AccountID) (bool, error) { return isWallet, nil } -type manyalAddresser struct { +type manualAddresser struct { mu sync.RWMutex addresses map[tongo.AccountID]KnownAddress sorted []AttachedAccount } -func (m *manyalAddresser) GetAddress(a tongo.AccountID) (KnownAddress, bool) { +// GetAddress fetches known address by account +func (m *manualAddresser) GetAddress(a tongo.AccountID) (KnownAddress, bool) { m.mu.RLock() defer m.mu.RUnlock() addr, ok := m.addresses[a] return addr, ok } -func (m *manyalAddresser) SearchAttachedAccounts(prefix string) []AttachedAccount { +// SearchAttachedAccounts searches for accounts by prefix +func (m *manualAddresser) SearchAttachedAccounts(prefix string) []AttachedAccount { m.mu.RLock() sortedList := m.sorted m.mu.RUnlock() - startIdx, endIdx := findIndexes(sortedList, prefix) + + if len(sortedList) == 0 { + return []AttachedAccount{} + } + // Normalize the prefix for comparison + prefix = strings.ToLower(NormalizeReg.ReplaceAllString(prefix, "")) + startIdx, endIdx := FindIndexes(sortedList, prefix) if startIdx == -1 || endIdx == -1 { - return nil + return []AttachedAccount{} } + // Return the found accounts as a slice foundAccounts := make([]AttachedAccount, endIdx-startIdx+1) copy(foundAccounts, sortedList[startIdx:endIdx+1]) return foundAccounts } -var normalizeReg = regexp.MustCompile("[^\\p{L}\\p{N}]") - -func (m *manyalAddresser) refreshAddresses(addressPath string) error { +// refreshAddresses updates the list of known addresses +func (m *manualAddresser) refreshAddresses(addressPath string) error { addresses, err := downloadJson[KnownAddress](addressPath) if err != nil { return err } newAddresses := make(map[tongo.AccountID]KnownAddress, len(addresses)) - newSorted := []AttachedAccount{ - {Name: "The Locker", Wallet: ton.MustParseAccountID("EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2"), Normalized: "locker", Weight: 1000}, - {Name: "The Locker", Wallet: ton.MustParseAccountID("EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2"), Normalized: "thelocker", Weight: 10000}, - {Name: "Ton Foundation", Wallet: ton.MustParseAccountID("EQCLyZHP4Xe8fpchQz76O-_RmUhaVc_9BAoGyJrwJrcbz2eZ"), Normalized: "foundation", Weight: 1000}, - } + var newSorted []AttachedAccount for _, item := range addresses { account, err := tongo.ParseAddress(item.Address) if err != nil { @@ -284,31 +301,52 @@ func (m *manyalAddresser) refreshAddresses(addressPath string) error { if item.Image != "" { preview = imgGenerator.DefaultGenerator.GenerateImageUrl(item.Image, 200, 200) } - newSorted = append(newSorted, AttachedAccount{ - Name: item.Name, - Preview: preview, - Wallet: account.ID, - Type: ManualAccountType, - Weight: 1000, - Popular: 1, - Normalized: strings.ToLower(normalizeReg.ReplaceAllString(item.Name, "")), - }) + names := GenerateNameVariants(item.Name) + for idx, name := range names { + // Assign initial weight, give extra weight to the first name for priority + weight := int32(1000) + if idx == 0 { + weight *= 10 // Boost weight for the first name + } + newSorted = append(newSorted, AttachedAccount{ + Name: item.Name, + Preview: preview, + Wallet: account.ID, + Type: ManualAccountType, + Weight: weight, + Popular: 1, + Normalized: strings.ToLower(NormalizeReg.ReplaceAllString(name, "")), + }) + } } sort.Slice(newSorted, func(i, j int) bool { return newSorted[i].Normalized < newSorted[j].Normalized }) + m.mu.Lock() - defer m.mu.Unlock() m.addresses = newAddresses m.sorted = newSorted + m.mu.Unlock() + return nil } -func findIndexes(sortedList []AttachedAccount, prefix string) (int, int) { - low := 0 - high := len(sortedList) - 1 +func GenerateNameVariants(name string) []string { + words := strings.Fields(name) // Split the name into words + var variants []string + // Generate up to 3 variants by rotating the words + for i := 0; i < len(words) && i < 3; i++ { + variant := append(words[i:], words[:i]...) // Rotate the words + variants = append(variants, strings.Join(variant, " ")) + } + return variants +} + +func FindIndexes(sortedList []AttachedAccount, prefix string) (int, int) { + low, high := 0, len(sortedList)-1 startIdx := -1 - for low <= high { // Find the starting index + // Find starting index for the prefix + for low <= high { med := (low + high) / 2 if strings.HasPrefix(sortedList[med].Normalized, prefix) { startIdx = med @@ -319,15 +357,13 @@ func findIndexes(sortedList []AttachedAccount, prefix string) (int, int) { high = med - 1 } } - - if startIdx == -1 { // Prefix not found + if startIdx == -1 { // No prefix match return -1, -1 } - - low = startIdx - high = len(sortedList) - 1 + low, high = startIdx, len(sortedList)-1 endIdx := -1 - for low <= high { // Find the ending index + // Find ending index for the prefix + for low <= high { med := (low + high) / 2 if strings.HasPrefix(sortedList[med].Normalized, prefix) { endIdx = med @@ -340,14 +376,16 @@ func findIndexes(sortedList []AttachedAccount, prefix string) (int, int) { return startIdx, endIdx } +// NewAddressBook initializes a Book and starts background refreshers tasks func NewAddressBook(logger *zap.Logger, addressPath, jettonPath, collectionPath string, storage accountsStatesSource, opts ...Option) *Book { - var manual = &manyalAddresser{ + var manual = &manualAddresser{ addresses: make(map[tongo.AccountID]KnownAddress), } options := Options{addressers: []addresser{manual}} for _, opt := range opts { opt(&options) } + collections := make(map[tongo.AccountID]KnownCollection) jettons := make(map[tongo.AccountID]KnownJetton) tfPools := make(map[tongo.AccountID]TFPoolInfo) @@ -360,17 +398,18 @@ func NewAddressBook(logger *zap.Logger, addressPath, jettonPath, collectionPath addressers: options.addressers, walletsResolved: cache.NewLRUCache[tongo.AccountID, bool](1_000_000, "is_wallet"), } - - go refresher("gg whitelist", time.Hour, 5*time.Minute, logger, book.getGGWhitelist) - go refresher("addresses", time.Minute*15, 5*time.Minute, logger, func() error { return manual.refreshAddresses(addressPath) }) - go refresher("jettons", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshJettons(jettonPath) }) - go refresher("collections", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshCollections(collectionPath) }) - book.refreshTfPools(logger) //hardcoded so don't need to be refreshed + // Start background refreshers + go Refresher("gg whitelist", time.Hour, 5*time.Minute, logger, book.getGGWhitelist) + go Refresher("addresses", time.Minute*15, 5*time.Minute, logger, func() error { return manual.refreshAddresses(addressPath) }) + go Refresher("jettons", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshJettons(jettonPath) }) + go Refresher("collections", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshCollections(collectionPath) }) + book.refreshTfPools(logger) // Refresh tfPools once on initialization as it doesn't need periodic updates return book } -func refresher(name string, interval, errorInterval time.Duration, logger *zap.Logger, f func() error) { +// Refresher periodically calls the provided function at the specified interval +func Refresher(name string, interval, errorInterval time.Duration, logger *zap.Logger, f func() error) { for { err := f() if err != nil { @@ -378,10 +417,12 @@ func refresher(name string, interval, errorInterval time.Duration, logger *zap.L time.Sleep(errorInterval + time.Duration(rand.Intn(10))*time.Second) continue } + // Wait for the next interval before refreshing again time.Sleep(interval + time.Duration(rand.Intn(10))*time.Second) } } +// refreshJettons fetches and updates the jetton data from the provided URL func (b *Book) refreshJettons(jettonPath string) error { jettons, err := downloadJson[KnownJetton](jettonPath) if err != nil { @@ -389,6 +430,7 @@ func (b *Book) refreshJettons(jettonPath string) error { } b.mu.Lock() defer b.mu.Unlock() + // Update jettons map with the fetched data for _, item := range jettons { account, err := tongo.ParseAddress(item.Address) if err != nil { @@ -400,6 +442,7 @@ func (b *Book) refreshJettons(jettonPath string) error { return nil } +// refreshCollections fetches and updates collection data from the provided URL func (b *Book) refreshCollections(collectionPath string) error { collections, err := downloadJson[KnownCollection](collectionPath) if err != nil { @@ -407,21 +450,22 @@ func (b *Book) refreshCollections(collectionPath string) error { } b.mu.Lock() defer b.mu.Unlock() + // Update collections map with the fetched data for _, item := range collections { - // TODO: remove items that were previously added but aren't present in the current list. + // TODO: remove items that were previously added but aren't present in the current list account, err := tongo.ParseAddress(item.Address) if err != nil { continue } currentCollection, ok := b.collections[account.ID] if !ok { - // this is a new item, so we only add tonkeeper as approver. + // Add new collection with Tonkeeper as the approver currentCollection.Address = account.ID.ToRaw() currentCollection.Approvers = []oas.NftApprovedByItem{oas.NftApprovedByItemTonkeeper} b.collections[account.ID] = currentCollection continue } - // this is an existing item, so we merge approvers and remove duplicates adding tonkeeper. + // Merge approvers and ensure Tonkeeper is included if !slices.Contains(currentCollection.Approvers, oas.NftApprovedByItemTonkeeper) { currentCollection.Approvers = append(item.Approvers, oas.NftApprovedByItemTonkeeper) b.collections[account.ID] = currentCollection @@ -430,10 +474,11 @@ func (b *Book) refreshCollections(collectionPath string) error { return nil } +// refreshTfPools fetches and updates the TF pool data func (b *Book) refreshTfPools(logger *zap.Logger) { b.mu.Lock() defer b.mu.Unlock() - + // Fetch and update the TF pools for _, pool := range getPools(logger) { account, err := tongo.ParseAddress(pool.Address) if err != nil { @@ -445,6 +490,7 @@ func (b *Book) refreshTfPools(logger *zap.Logger) { } } +// downloadJson is a utility function that downloads and unmarshals JSON data from a URL into a slice of the specified type func downloadJson[T any](url string) ([]T, error) { response, err := http.Get(url) if err != nil { @@ -465,6 +511,7 @@ func downloadJson[T any](url string) ([]T, error) { return data, nil } +// getGGWhitelist fetches the whitelist from the GetGems API and updates the collections func (b *Book) getGGWhitelist() error { addresses, err := fetchGetGemsVerifiedCollections() if err != nil { @@ -472,14 +519,17 @@ func (b *Book) getGGWhitelist() error { } b.mu.Lock() defer b.mu.Unlock() + // Update collections with new addresses from the whitelist for _, account := range addresses { collection, ok := b.collections[account] if !ok { + // Add new collection with Getgems as the approver collection.Address = account.ToRaw() collection.Approvers = []oas.NftApprovedByItem{oas.NftApprovedByItemGetgems} b.collections[account] = collection continue } + // Ensure Getgems is included as an approver if !slices.Contains(collection.Approvers, oas.NftApprovedByItemGetgems) { collection.Approvers = append(collection.Approvers, oas.NftApprovedByItemGetgems) b.collections[account] = collection @@ -488,20 +538,15 @@ func (b *Book) getGGWhitelist() error { return nil } +// fetchGetGemsVerifiedCollections fetches verified collections from GetGems API func fetchGetGemsVerifiedCollections() ([]tongo.AccountID, error) { - client := graphql.NewClient("https://api.getgems.io/graphql", nil) - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - var q struct { - GetAddressesOfVerifiedCollections []graphql.String `graphql:"getAddressesOfVerifiedCollections"` - } - err := client.Query(ctx, &q, nil) + res, err := downloadJson[string]("https://api.getgems.io/public/api/verified-collections") if err != nil { return nil, err } - accountIDs := make([]tongo.AccountID, 0, len(q.GetAddressesOfVerifiedCollections)) - for _, collection := range q.GetAddressesOfVerifiedCollections { - account, err := tongo.ParseAddress(string(collection)) + accountIDs := make([]tongo.AccountID, 0, len(res)) + for _, collection := range res { + account, err := tongo.ParseAddress(collection) if err != nil { return nil, err } diff --git a/pkg/addressbook/addressbook_test.go b/pkg/addressbook/addressbook_test.go index 0d4d6361..61634db5 100644 --- a/pkg/addressbook/addressbook_test.go +++ b/pkg/addressbook/addressbook_test.go @@ -1,12 +1,153 @@ package addressbook import ( + "strings" "testing" + "time" "github.com/stretchr/testify/require" + "github.com/tonkeeper/opentonapi/pkg/config" + "github.com/tonkeeper/opentonapi/pkg/litestorage" + "github.com/tonkeeper/tongo/liteapi" "github.com/tonkeeper/tongo/ton" + "go.uber.org/zap" ) +func TestNormalizeString(t *testing.T) { + type testCase struct { + name string + input string + output string + } + tests := []testCase{ + { + name: "simple English sentence", + input: "The day that will always", + output: "thedaythatwillalways", + }, + { + name: "mixed case English words", + input: "TON Panda Baby", + output: "tonpandababy", + }, + { + name: "English with emoji at the end", + input: "apple 👎", + output: "apple", + }, + { + name: "emoji at the start", + input: "👎banana", + output: "banana", + }, + { + name: "uppercase Cyrillic", + input: "ХАЗЯЕВА", + output: "хазяева", + }, + { + name: "string with numbers and dots", + input: "11313.ton", + output: "11313ton", + }, + { + name: "Cyrillic sentence with spaces", + input: "Пока что думаю ", + output: "покачтодумаю", + }, + { + name: "English with multiple emojis", + input: "💎 TON Earth 🌍 Collectibles 🧭", + output: "tonearthcollectibles", + }, + { + name: "Cyrillic with numbers", + input: "Привет122", + output: "привет122", + }, + { + name: "string with numbers and domain-like structure", + input: "11313.t.me", + output: "11313tme", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + convertedData := strings.ToLower(NormalizeReg.ReplaceAllString(test.input, "")) + require.Equal(t, test.output, convertedData) + }) + } +} + +func TestSearchAttachedAccountsByPrefix(t *testing.T) { + logger, _ := zap.NewDevelopment() + client, err := liteapi.NewClient(liteapi.FromEnvsOrMainnet()) + require.NoError(t, err, "Failed to create lite API client") + + liteStorage, err := litestorage.NewLiteStorage(logger, client) + require.NoError(t, err, "Failed to create lite storage") + + book := NewAddressBook(logger, config.AddressPath, config.JettonPath, config.CollectionPath, liteStorage) + + // Waiting background processes + time.Sleep(time.Second) + + tests := []struct { + name string + request string + expected *AttachedAccount + }{ + { + name: "Exact match with full name", + request: "TON Believers Fund", + expected: &AttachedAccount{ + Name: "TON Believers Fund", + Wallet: ton.MustParseAccountID("0:ed1691307050047117b998b561d8de82d31fbf84910ced6eb5fc92e7485ef8a7"), + }, + }, + { + name: "Partial match", + request: "believers fund", + expected: &AttachedAccount{ + Name: "TON Believers Fund", + Wallet: ton.MustParseAccountID("0:ed1691307050047117b998b561d8de82d31fbf84910ced6eb5fc92e7485ef8a7"), + }, + }, + { + name: "Single word partial match", + request: "fund", + expected: &AttachedAccount{ + Name: "TON Believers Fund", + Wallet: ton.MustParseAccountID("0:ed1691307050047117b998b561d8de82d31fbf84910ced6eb5fc92e7485ef8a7"), + }, + }, + { + name: "No match", + request: "random string", + expected: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + accounts := book.SearchAttachedAccountsByPrefix(test.request) + if test.expected == nil { + require.Empty(t, accounts, "Expected no results, but got: %v", len(accounts)) + return + } + // Check if the expected account is in the result + var found bool + for _, account := range accounts { + if account.Wallet == test.expected.Wallet { + found = true + break + } + } + require.True(t, found, "Expected account not found in the results: %v", test.expected) + }) + } +} + func TestFetchGetGemsVerifiedCollections(t *testing.T) { accountIDs, err := fetchGetGemsVerifiedCollections() require.Nil(t, err) diff --git a/pkg/api/account_converters.go b/pkg/api/account_converters.go index ecf8da84..656c2883 100644 --- a/pkg/api/account_converters.go +++ b/pkg/api/account_converters.go @@ -3,6 +3,8 @@ package api import ( "fmt" imgGenerator "github.com/tonkeeper/opentonapi/pkg/image" + "github.com/tonkeeper/opentonapi/pkg/references" + "math/big" "sort" "github.com/tonkeeper/tongo/abi" @@ -59,7 +61,8 @@ func convertToRawAccount(account *core.Account) (oas.BlockchainRawAccount, error if account.ExtraBalances != nil { balances := make(map[string]string, len(account.ExtraBalances)) for key, value := range account.ExtraBalances { - balances[fmt.Sprintf("%v", key)] = fmt.Sprintf("%v", value) + v := big.Int(value) + balances[fmt.Sprintf("%v", key)] = fmt.Sprintf("%v", v.String()) } rawAccount.ExtraBalance = oas.NewOptBlockchainRawAccountExtraBalance(balances) } @@ -72,6 +75,24 @@ func convertToRawAccount(account *core.Account) (oas.BlockchainRawAccount, error return rawAccount, nil } +func convertExtraCurrencies(extraBalances core.ExtraCurrencies) []oas.ExtraCurrency { + res := make([]oas.ExtraCurrency, 0, len(extraBalances)) + for k, v := range extraBalances { + amount := big.Int(v) + meta := references.GetExtraCurrencyMeta(k) + cur := oas.ExtraCurrency{ + ID: k, + Amount: amount.String(), + Decimals: meta.Decimals, + } + if meta.Name != "" { + cur.Name.SetTo(meta.Name) + } + res = append(res, cur) + } + return res +} + func convertToAccount(account *core.Account, ab *addressbook.KnownAddress, state chainState) oas.Account { acc := oas.Account{ Address: account.AccountAddress.ToRaw(), diff --git a/pkg/api/account_handlers.go b/pkg/api/account_handlers.go index 58ebb5c1..bce4ac8d 100644 --- a/pkg/api/account_handlers.go +++ b/pkg/api/account_handlers.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "golang.org/x/exp/slices" "net/http" "sort" "strings" @@ -71,6 +72,9 @@ func (h *Handler) GetAccount(ctx context.Context, params oas.GetAccountParams) ( } else { res = convertToAccount(rawAccount, nil, h.state) } + if rawAccount.ExtraBalances != nil { + res.ExtraBalance = convertExtraCurrencies(rawAccount.ExtraBalances) + } return &res, nil } @@ -113,6 +117,9 @@ func (h *Handler) GetAccounts(ctx context.Context, request oas.OptGetAccountsReq } else { res = convertToAccount(account, nil, h.state) } + if account.ExtraBalances != nil { + res.ExtraBalance = convertExtraCurrencies(account.ExtraBalances) + } results[account.AccountAddress] = res } // if we don't find an account, we return it with "nonexist" status @@ -200,6 +207,10 @@ func (h *Handler) ExecGetMethodForBlockchainAccount(ctx context.Context, params } return nil, toError(http.StatusInternalServerError, err) } + // TODO: remove parameter after user migration + if params.FixOrder.IsSet() && params.FixOrder.Value == true && len(params.Args) > 1 { + slices.Reverse(params.Args) + } key, err := getMethodCacheKey(account.ID, params.MethodName, contract.LastTransactionLt, params.Args) if err != nil { return nil, toError(http.StatusInternalServerError, err) @@ -208,12 +219,12 @@ func (h *Handler) ExecGetMethodForBlockchainAccount(ctx context.Context, params return result, nil } stack := make([]tlb.VmStackValue, 0, len(params.Args)) - for _, p := range params.Args { - r, err := stringToTVMStackRecord(p) + for i := len(params.Args) - 1; i >= 0; i-- { + r, err := stringToTVMStackRecord(params.Args[i]) if err != nil { - return nil, toError(http.StatusBadRequest, fmt.Errorf("can't parse arg '%v' as any TVMStackValue", p)) + return nil, toError(http.StatusBadRequest, fmt.Errorf("can't parse arg '%v' as any TVMStackValue", params.Args[i])) } - stack = append(stack, r) + stack = append(stack, r) // we need to put the arguments on the stack in reverse order } // RunSmcMethodByID fetches the contract from the storage on its own, // and it can happen that the contract has been changed and has another lt, diff --git a/pkg/api/blockchain_converters.go b/pkg/api/blockchain_converters.go index 9c8f276d..8e79d6f1 100644 --- a/pkg/api/blockchain_converters.go +++ b/pkg/api/blockchain_converters.go @@ -269,6 +269,9 @@ func convertMessage(m core.Message, book addressBook) oas.Message { value, _ := json.Marshal(m.DecodedBody.Value) msg.DecodedBody = g.ChangeJsonKeys(value, g.CamelToSnake) } + if m.ValueExtra != nil { + msg.ValueExtra = convertExtraCurrencies(m.ValueExtra) + } return msg } @@ -532,6 +535,17 @@ func convertConfig(logger *zap.Logger, cfg tlb.ConfigParams) (*oas.BlockchainCon config.R44.Accounts = append(config.R44.Accounts, accountID.String()) } config.R44.SetSuspendedUntil(int(blockchainConfig.ConfigParam44.SuspendedAddressList.SuspendedUntil)) + if p45 := blockchainConfig.ConfigParam45; p45 != nil { + var param45 oas.BlockchainConfig45 + for _, item := range p45.PrecompiledContractsConfig.List.Items() { + param45.Contracts = append(param45.Contracts, oas.BlockchainConfig45ContractsItem{ + CodeHash: item.Key.Hex(), + GasUsage: int64(item.Value.GasUsage), + }) + } + config.R45 = oas.NewOptBlockchainConfig45(param45) + } + if p71 := blockchainConfig.ConfigParam71; p71 != nil { param71 := oas.BlockchainConfig71{ OracleBridgeParams: convertOracleBridgeParams(p71.OracleBridgeParams), diff --git a/pkg/api/blockchain_handlers.go b/pkg/api/blockchain_handlers.go index 449e06aa..7f86bc49 100644 --- a/pkg/api/blockchain_handlers.go +++ b/pkg/api/blockchain_handlers.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "sort" "golang.org/x/exp/maps" @@ -195,6 +196,9 @@ func (h *Handler) GetBlockchainMasterchainTransactions(ctx context.Context, para result.Transactions = append(result.Transactions, convertTransaction(*tx, nil, h.addressBook)) } } + sort.Slice(result.Transactions, func(i, j int) bool { + return result.Transactions[i].Lt < result.Transactions[j].Lt + }) return &result, nil } @@ -216,6 +220,9 @@ func (h *Handler) GetBlockchainBlockTransactions(ctx context.Context, params oas for _, tx := range transactions { res.Transactions = append(res.Transactions, convertTransaction(*tx, nil, h.addressBook)) } + sort.Slice(res.Transactions, func(i, j int) bool { + return res.Transactions[i].Lt < res.Transactions[j].Lt + }) return &res, nil } diff --git a/pkg/api/converters.go b/pkg/api/converters.go index d26f4f4a..e0eca7b6 100644 --- a/pkg/api/converters.go +++ b/pkg/api/converters.go @@ -3,14 +3,17 @@ package api import ( "context" "encoding/json" + "errors" "fmt" - imgGenerator "github.com/tonkeeper/opentonapi/pkg/image" "math/big" "reflect" "strconv" "strings" "unicode" + imgGenerator "github.com/tonkeeper/opentonapi/pkg/image" + "github.com/tonkeeper/opentonapi/pkg/references" + "github.com/go-faster/jx" "github.com/tonkeeper/tongo" "github.com/tonkeeper/tongo/boc" @@ -22,15 +25,47 @@ import ( walletPkg "github.com/tonkeeper/opentonapi/pkg/wallet" ) -func toError(code int, err error) *oas.ErrorStatusCode { - if strings.HasPrefix(err.Error(), "failed to connect to") || strings.Contains(err.Error(), "host=") { - return &oas.ErrorStatusCode{StatusCode: code, Response: oas.Error{Error: "unknown error"}} +// ErrorWithExtendedCode helps to pass additional information about an error. +type ErrorWithExtendedCode struct { + Code int + Message string + ExtendedCode references.ExtendedCode +} + +func (e ErrorWithExtendedCode) Error() string { + return e.Message +} + +// censor removes sensitive information from the error message. +func censor(msg string) string { + if strings.HasPrefix(msg, "failed to connect to") || strings.Contains(msg, "host=") { + return "unknown error" + } + return msg +} + +func extendedCode(code references.ExtendedCode) oas.OptInt64 { + if code == 0 { + return oas.OptInt64{} + } + return oas.NewOptInt64(int64(code)) +} + +func toError(defaultCode int, err error) *oas.ErrorStatusCode { + var e ErrorWithExtendedCode + if errors.As(err, &e) { + return &oas.ErrorStatusCode{ + StatusCode: e.Code, + Response: oas.Error{ + Error: censor(e.Message), + ErrorCode: extendedCode(e.ExtendedCode), + }, + } } if s, ok := status.FromError(err); ok { - return &oas.ErrorStatusCode{StatusCode: code, Response: oas.Error{Error: s.Message()}} + return &oas.ErrorStatusCode{StatusCode: defaultCode, Response: oas.Error{Error: censor(s.Message())}} } - msg := err.Error() - return &oas.ErrorStatusCode{StatusCode: code, Response: oas.Error{Error: msg}} + return &oas.ErrorStatusCode{StatusCode: defaultCode, Response: oas.Error{Error: censor(err.Error())}} } func anyToJSONRawMap(a any) map[string]jx.Raw { //todo: переписать этот ужас @@ -233,15 +268,21 @@ func (h *Handler) convertMultisig(ctx context.Context, item core.Multisig) (*oas for _, account := range order.Signers { signers = append(signers, account.ToRaw()) } - messages, err := convertMultisigActionsToRawMessages(order.Actions) - if err != nil { - return nil, err + risk := walletPkg.Risk{ + TransferAllRemainingBalance: false, + Jettons: map[tongo.AccountID]big.Int{}, } - risk, err := walletPkg.ExtractRiskFromRawMessages(messages) - if err != nil { - return nil, err + for _, action := range order.Actions { + switch action.SumType { + case "SendMessage": + var err error + risk, err = walletPkg.ExtractRiskFromMessage(action.SendMessage.Field0.Message, risk, action.SendMessage.Field0.Mode) + if err != nil { + return nil, err + } + } } - oasRisk, err := h.convertRisk(ctx, *risk, item.AccountID) + oasRisk, err := h.convertRisk(ctx, risk, item.AccountID) if err != nil { return nil, err } diff --git a/pkg/api/dns_handlers.go b/pkg/api/dns_handlers.go index 006dd82d..f718bcef 100644 --- a/pkg/api/dns_handlers.go +++ b/pkg/api/dns_handlers.go @@ -83,8 +83,8 @@ func (h *Handler) AccountDnsBackResolve(ctx context.Context, params oas.AccountD } func (h *Handler) DnsResolve(ctx context.Context, params oas.DnsResolveParams) (*oas.DnsRecord, error) { - if len(params.DomainName) == 48 || len(params.DomainName) == 52 { - return nil, toError(http.StatusBadRequest, fmt.Errorf("domains with length 48 and 52 can't be resolved by security issues")) + if len(params.DomainName) == 48 || len(params.DomainName) == 52 || (len(params.DomainName) == 46 && params.DomainName[:2] == "0x") { + return nil, toError(http.StatusBadRequest, fmt.Errorf("domains with length 46, 48 and 52 can't be resolved by security issues")) } if len(params.DomainName) > 127 { return nil, toError(http.StatusBadRequest, fmt.Errorf("domain name is too long")) diff --git a/pkg/api/event_converters.go b/pkg/api/event_converters.go index a1cc847f..d531a0dd 100644 --- a/pkg/api/event_converters.go +++ b/pkg/api/event_converters.go @@ -152,6 +152,31 @@ func (h *Handler) convertActionTonTransfer(t *bath.TonTransferAction, acceptLang return action, simplePreview } +func (h *Handler) convertActionExtraCurrencyTransfer(t *bath.ExtraCurrencyTransferAction, acceptLanguage string, viewer *tongo.AccountID) (oas.OptExtraCurrencyTransferAction, oas.ActionSimplePreview) { + var action oas.OptExtraCurrencyTransferAction + amount := big.Int(t.Amount) + meta := references.GetExtraCurrencyMeta(t.CurrencyID) + action.SetTo(oas.ExtraCurrencyTransferAction{ + Amount: amount.String(), + Comment: g.Opt(t.Comment), + Recipient: convertAccountAddress(t.Recipient, h.addressBook), + Sender: convertAccountAddress(t.Sender, h.addressBook), + EncryptedComment: convertEncryptedComment(t.EncryptedComment), + Currency: oas.EcPreview{ + Symbol: meta.Symbol, + Decimals: meta.Decimals, + Image: meta.Image, + }, + }) + simplePreview := oas.ActionSimplePreview{ + Name: "Extra Currency Transfer", + Description: "", // TODO: add description + Accounts: distinctAccounts(viewer, h.addressBook, &t.Sender, &t.Recipient), + // TODO: add value + } + return action, simplePreview +} + func (h *Handler) convertActionNftTransfer(t *bath.NftTransferAction, acceptLanguage string, viewer *tongo.AccountID) (oas.OptNftItemTransferAction, oas.ActionSimplePreview) { var action oas.OptNftItemTransferAction action.SetTo(oas.NftItemTransferAction{ @@ -431,6 +456,8 @@ func (h *Handler) convertAction(ctx context.Context, viewer *tongo.AccountID, a switch a.Type { case bath.TonTransfer: action.TonTransfer, action.SimplePreview = h.convertActionTonTransfer(a.TonTransfer, acceptLanguage.Value, viewer) + case bath.ExtraCurrencyTransfer: + action.ExtraCurrencyTransfer, action.SimplePreview = h.convertActionExtraCurrencyTransfer(a.ExtraCurrencyTransfer, acceptLanguage.Value, viewer) case bath.NftItemTransfer: action.NftItemTransfer, action.SimplePreview = h.convertActionNftTransfer(a.NftItemTransfer, acceptLanguage.Value, viewer) case bath.JettonTransfer: @@ -775,7 +802,7 @@ func (h *Handler) toEvent(ctx context.Context, trace *core.Trace, result *bath.A } event.Actions[i] = convertedAction } - event.IsScam = h.spamFilter.CheckActions(event.Actions, nil) + event.IsScam = h.spamFilter.CheckActions(event.Actions, nil, trace.Account) previews := make(map[tongo.AccountID]oas.JettonPreview) for _, flow := range result.ValueFlow.Accounts { for jettonMaster := range flow.Jettons { @@ -856,7 +883,7 @@ func (h *Handler) toAccountEvent(ctx context.Context, account tongo.AccountID, t e.Actions = append(e.Actions, convertedAction) } if h.spamFilter != nil { - e.IsScam = h.spamFilter.CheckActions(e.Actions, &account) + e.IsScam = h.spamFilter.CheckActions(e.Actions, &account, trace.Account) } if len(e.Actions) == 0 { e.Actions = []oas.Action{ diff --git a/pkg/api/event_handlers.go b/pkg/api/event_handlers.go index b936df78..17afbe6c 100644 --- a/pkg/api/event_handlers.go +++ b/pkg/api/event_handlers.go @@ -73,6 +73,10 @@ func (h *Handler) SendBlockchainMessage(ctx context.Context, request *oas.SendBl if !request.Boc.IsSet() && len(request.Batch) == 0 { return toError(http.StatusBadRequest, fmt.Errorf("boc not found")) } + var meta map[string]string + if request.Meta.IsSet() { + meta = request.Meta.Value + } if request.Boc.IsSet() { m, err := decodeMessage(request.Boc.Value) if err != nil { @@ -86,6 +90,7 @@ func (h *Handler) SendBlockchainMessage(ctx context.Context, request *oas.SendBl MsgBoc: m.base64, Payload: m.payload, Details: h.ctxToDetails(ctx), + Meta: meta, } sendMessageCounter.Inc() if err := h.msgSender.SendMessage(ctx, msgCopy); err != nil { @@ -114,6 +119,7 @@ func (h *Handler) SendBlockchainMessage(ctx context.Context, request *oas.SendBl MsgBoc: m.base64, Payload: m.payload, Details: h.ctxToDetails(ctx), + Meta: meta, } copies = append(copies, msgCopy) } @@ -310,10 +316,11 @@ func (h *Handler) GetAccountEvent(ctx context.Context, params oas.GetAccountEven if err != nil { return nil, toError(http.StatusInternalServerError, err) } - result, err := bath.FindActions(ctx, trace, bath.ForAccount(account.ID), bath.WithInformationSource(h.storage)) + actions, err := bath.FindActions(ctx, trace, bath.ForAccount(account.ID), bath.WithInformationSource(h.storage)) if err != nil { return nil, toError(http.StatusInternalServerError, err) } + result := bath.EnrichWithIntentions(trace, actions) event, err := h.toAccountEvent(ctx, account.ID, trace, result, params.AcceptLanguage, params.SubjectOnly.Value) if err != nil { return nil, toError(http.StatusInternalServerError, err) @@ -374,10 +381,11 @@ func (h *Handler) EmulateMessageToAccountEvent(ctx context.Context, request *oas if err != nil { return nil, toError(http.StatusInternalServerError, err) } - result, err := bath.FindActions(ctx, trace, bath.WithInformationSource(h.storage)) + actions, err := bath.FindActions(ctx, trace, bath.WithInformationSource(h.storage)) if err != nil { return nil, toError(http.StatusInternalServerError, err) } + result := bath.EnrichWithIntentions(trace, actions) event, err := h.toAccountEvent(ctx, account.ID, trace, result, params.AcceptLanguage, false) if err != nil { return nil, toError(http.StatusInternalServerError, err) @@ -425,10 +433,11 @@ func (h *Handler) EmulateMessageToEvent(ctx context.Context, request *oas.Emulat return nil, toError(http.StatusInternalServerError, err) } } - result, err := bath.FindActions(ctx, trace, bath.WithInformationSource(h.storage)) + actions, err := bath.FindActions(ctx, trace, bath.WithInformationSource(h.storage)) if err != nil { return nil, toError(http.StatusInternalServerError, err) } + result := bath.EnrichWithIntentions(trace, actions) event, err := h.toEvent(ctx, trace, result, params.AcceptLanguage) if err != nil { return nil, toError(http.StatusInternalServerError, err) @@ -601,10 +610,11 @@ func (h *Handler) EmulateMessageToWallet(ctx context.Context, request *oas.Emula return nil, toError(http.StatusInternalServerError, err) } t := convertTrace(trace, h.addressBook) - result, err := bath.FindActions(ctx, trace, bath.ForAccount(*walletAddress), bath.WithInformationSource(h.storage)) + actions, err := bath.FindActions(ctx, trace, bath.ForAccount(*walletAddress), bath.WithInformationSource(h.storage)) if err != nil { return nil, toError(http.StatusInternalServerError, err) } + result := bath.EnrichWithIntentions(trace, actions) event, err := h.toAccountEvent(ctx, *walletAddress, trace, result, params.AcceptLanguage, true) if err != nil { return nil, toError(http.StatusInternalServerError, err) diff --git a/pkg/api/interfaces.go b/pkg/api/interfaces.go index 16b26a3a..3ba4b073 100644 --- a/pkg/api/interfaces.go +++ b/pkg/api/interfaces.go @@ -97,7 +97,7 @@ type storage interface { GetAccountState(ctx context.Context, a tongo.AccountID) (tlb.ShardAccount, error) GetLibraries(ctx context.Context, libraries []tongo.Bits256) (map[tongo.Bits256]*boc.Cell, error) - SearchAccountsByPubKey(pubKey ed25519.PublicKey) ([]tongo.AccountID, error) + SearchAccountsByPubKey(ctx context.Context, pubKey ed25519.PublicKey) ([]tongo.AccountID, error) // TrimmedConfigBase64 returns the current trimmed blockchain config in a base64 format. TrimmedConfigBase64() (string, error) @@ -183,7 +183,7 @@ type scoreSource interface { } type SpamFilter interface { - CheckActions(actions []oas.Action, viewer *ton.AccountID) bool + CheckActions(actions []oas.Action, viewer *ton.AccountID, initiator ton.AccountID) bool JettonTrust(address tongo.AccountID, symbol, name, image string) core.TrustType NftTrust(address tongo.AccountID, collection *ton.AccountID, description, image string) core.TrustType } diff --git a/pkg/api/jetton_converters.go b/pkg/api/jetton_converters.go index 863a52b8..d3d26963 100644 --- a/pkg/api/jetton_converters.go +++ b/pkg/api/jetton_converters.go @@ -22,7 +22,7 @@ func jettonPreview(master ton.AccountID, meta NormalizedMetadata, score int32) o Symbol: meta.Symbol, Verification: oas.JettonVerificationType(meta.Verification), Decimals: meta.Decimals, - Image: meta.Image, + Image: meta.PreviewImage, } if meta.CustomPayloadApiUri != "" { preview.CustomPayloadAPIURI = oas.NewOptString(meta.CustomPayloadApiUri) @@ -99,7 +99,7 @@ func (h *Handler) convertJettonHistory(ctx context.Context, account ton.AccountI } event.Actions = append(event.Actions, convertedAction) } - event.IsScam = h.spamFilter.CheckActions(event.Actions, &account) + event.IsScam = h.spamFilter.CheckActions(event.Actions, &account, trace.Account) if len(event.Actions) == 0 { continue } @@ -182,6 +182,7 @@ func (h *Handler) convertJettonInfo(ctx context.Context, master core.JettonMaste Verification: oas.JettonVerificationType(meta.Verification), HoldersCount: holders[master.Address], Admin: convertOptAccountAddress(master.Admin, h.addressBook), + Preview: meta.PreviewImage, } if score != 0 { info.Score = oas.NewOptInt32(score) diff --git a/pkg/api/jetton_handlers.go b/pkg/api/jetton_handlers.go index 2db807ae..d5d0fa81 100644 --- a/pkg/api/jetton_handlers.go +++ b/pkg/api/jetton_handlers.go @@ -91,6 +91,7 @@ func (h *Handler) GetJettonInfo(ctx context.Context, params oas.GetJettonInfoPar Verification: oas.JettonVerificationType(meta.Verification), HoldersCount: holdersCount[account.ID], Admin: convertOptAccountAddress(data.Admin, h.addressBook), + Preview: meta.PreviewImage, }, nil } diff --git a/pkg/api/multisig_handlers.go b/pkg/api/multisig_handlers.go index 655a6c13..87ace688 100644 --- a/pkg/api/multisig_handlers.go +++ b/pkg/api/multisig_handlers.go @@ -7,31 +7,9 @@ import ( "github.com/tonkeeper/opentonapi/pkg/core" "github.com/tonkeeper/opentonapi/pkg/oas" - "github.com/tonkeeper/tongo/abi" - "github.com/tonkeeper/tongo/boc" - "github.com/tonkeeper/tongo/tlb" "github.com/tonkeeper/tongo/ton" - tongoWallet "github.com/tonkeeper/tongo/wallet" ) -func convertMultisigActionsToRawMessages(actions []abi.MultisigSendMessageAction) ([]tongoWallet.RawMessage, error) { - var messages []tongoWallet.RawMessage - for _, action := range actions { - switch action.SumType { - case "SendMessage": - msg := boc.NewCell() - if err := tlb.Marshal(msg, action.SendMessage.Field0.Message); err != nil { - return nil, err - } - messages = append(messages, tongoWallet.RawMessage{ - Message: msg, - Mode: action.SendMessage.Field0.Mode, - }) - } - } - return messages, nil -} - func (h *Handler) GetMultisigAccount(ctx context.Context, params oas.GetMultisigAccountParams) (*oas.Multisig, error) { accountID, err := ton.ParseAccountID(params.AccountID) if err != nil { diff --git a/pkg/api/nft_converters.go b/pkg/api/nft_converters.go index 1927303a..7861fd81 100644 --- a/pkg/api/nft_converters.go +++ b/pkg/api/nft_converters.go @@ -46,6 +46,14 @@ func (h *Handler) convertNFT(ctx context.Context, item core.NftItem, book addres })) } var image, description string + if item.Metadata != nil { + if imageI, prs := item.Metadata["image"]; prs { + image, _ = imageI.(string) + } + if descriptionI, prs := item.Metadata["description"]; prs { + description, _ = descriptionI.(string) + } + } if item.CollectionAddress != nil { cInfo, _ := metaCache.getCollectionMeta(ctx, *item.CollectionAddress) if cc, prs := book.GetCollectionInfoByAddress(*item.CollectionAddress); prs { @@ -66,14 +74,6 @@ func (h *Handler) convertNFT(ctx context.Context, item core.NftItem, book addres nftItem.Metadata["buttons"] = buttons } } - if item.Metadata != nil { - if imageI, prs := item.Metadata["image"]; prs { - image, _ = imageI.(string) - } - if descriptionI, prs := item.Metadata["description"]; prs { - description, _ = descriptionI.(string) - } - } if len(nftItem.ApprovedBy) > 0 && nftItem.Verified { nftItem.Trust = oas.TrustType(core.TrustWhitelist) } else { @@ -163,7 +163,7 @@ func (h *Handler) convertNftHistory(ctx context.Context, account tongo.AccountID } event.Actions = append(event.Actions, convertedAction) } - event.IsScam = h.spamFilter.CheckActions(event.Actions, &account) + event.IsScam = h.spamFilter.CheckActions(event.Actions, &account, trace.Account) if len(event.Actions) > 0 { events = append(events, event) lastLT = trace.Lt diff --git a/pkg/api/normalized_metadata.go b/pkg/api/normalized_metadata.go index 87368e15..221d2d20 100644 --- a/pkg/api/normalized_metadata.go +++ b/pkg/api/normalized_metadata.go @@ -30,6 +30,7 @@ type NormalizedMetadata struct { Social []string Websites []string CustomPayloadApiUri string + PreviewImage string // path to the converted image } func NormalizeMetadata(meta tep64.Metadata, info *addressbook.KnownJetton, trust core.TrustType) NormalizedMetadata { @@ -41,7 +42,7 @@ func NormalizeMetadata(meta tep64.Metadata, info *addressbook.KnownJetton, trust if name == "" { name = "Unknown Token" } - image := references.Placeholder + var image string if meta.Image != "" { image = meta.Image } @@ -57,19 +58,21 @@ func NormalizeMetadata(meta tep64.Metadata, info *addressbook.KnownJetton, trust websites = info.Websites trust = core.TrustWhitelist } - - image = imgGenerator.DefaultGenerator.GenerateImageUrl(image, 200, 200) + previewImage := references.Placeholder + previewImage = rewriteIfNotEmpty(previewImage, image) + previewImage = imgGenerator.DefaultGenerator.GenerateImageUrl(previewImage, 200, 200) return NormalizedMetadata{ Name: name, Description: description, - Image: image, + Image: previewImage, // TODO: replace with `image` after migration to `previewImage` Symbol: symbol, Decimals: convertJettonDecimals(meta.Decimals), Verification: trust, Social: social, Websites: websites, CustomPayloadApiUri: meta.CustomPayloadAPIURL, + PreviewImage: previewImage, } } diff --git a/pkg/api/openapi.go b/pkg/api/openapi.go new file mode 100644 index 00000000..175e64b7 --- /dev/null +++ b/pkg/api/openapi.go @@ -0,0 +1,33 @@ +package api + +import ( + "bytes" + "context" + "github.com/go-faster/jx" + "github.com/tonkeeper/opentonapi/pkg/oas" + "net/http" + "os" +) + +func (h *Handler) GetOpenapiJson(ctx context.Context) (jx.Raw, error) { + file, err := os.ReadFile("openapi/openapi.json") + if err != nil { + return jx.Raw{}, toError(http.StatusInternalServerError, err) + } + d := jx.DecodeBytes(file) + result, err := d.Raw() + if err != nil { + return jx.Raw{}, toError(http.StatusInternalServerError, err) + } + return result, nil +} + +func (h *Handler) GetOpenapiYml(ctx context.Context) (oas.GetOpenapiYmlOK, error) { + file, err := os.ReadFile("openapi/openapi.yml") + if err != nil { + return oas.GetOpenapiYmlOK{}, toError(http.StatusInternalServerError, err) + } + return oas.GetOpenapiYmlOK{ + Data: bytes.NewReader(file), + }, nil +} diff --git a/pkg/api/staking_handlers.go b/pkg/api/staking_handlers.go index 8efee54d..52fc1a0b 100644 --- a/pkg/api/staking_handlers.go +++ b/pkg/api/staking_handlers.go @@ -268,10 +268,8 @@ func (h *Handler) GetStakingPoolHistory(ctx context.Context, params oas.GetStaki return nil, toError(http.StatusNotFound, err) } logAddress := tlb.MsgAddress{SumType: "AddrExtern"} - logAddress.AddrExtern = &struct { - Len tlb.Uint9 - ExternalAddress boc.BitString - }{Len: 256, ExternalAddress: g.Must(boc.BitStringFromFiftHex("0000000000000000000000000000000000000000000000000000000000000003"))} + addr := g.Must(boc.BitStringFromFiftHex("0000000000000000000000000000000000000000000000000000000000000003")) + logAddress.AddrExtern = &addr logs, err := h.storage.GetLogs(ctx, pool.ID, &logAddress, 100, 0) if err != nil { return nil, toError(http.StatusInternalServerError, err) diff --git a/pkg/api/wallet_handlers.go b/pkg/api/wallet_handlers.go index e155f2ae..96e085f1 100644 --- a/pkg/api/wallet_handlers.go +++ b/pkg/api/wallet_handlers.go @@ -2,20 +2,11 @@ package api import ( "context" - "crypto/hmac" - "crypto/sha256" - "encoding/base64" "encoding/hex" "errors" "fmt" - "io" - "net/http" - "os" - "time" - "github.com/tonkeeper/opentonapi/pkg/core" - - "github.com/tonkeeper/tongo/ton" + "net/http" "github.com/tonkeeper/opentonapi/pkg/oas" "github.com/tonkeeper/opentonapi/pkg/wallet" @@ -25,113 +16,12 @@ import ( tongoWallet "github.com/tonkeeper/tongo/wallet" ) -func (h *Handler) SetWalletBackup(ctx context.Context, request oas.SetWalletBackupReq, params oas.SetWalletBackupParams) error { - pubKey, verify, err := checkTonConnectToken(params.XTonConnectAuth, h.tonConnect.GetSecret()) - if err != nil { - return toError(http.StatusBadRequest, err) - } - if !verify { - return toError(http.StatusBadRequest, fmt.Errorf("failed verify")) - } - - walletBalance, err := getTotalBalances(ctx, h.storage, pubKey) - if err != nil { - return toError(http.StatusInternalServerError, err) - } - if walletBalance < int64(ton.OneTON) { - return toError(http.StatusBadRequest, fmt.Errorf("wallet must have more than 1 TON")) - } - - fileName := fmt.Sprintf("%x.dump", pubKey) - tempFileName := fileName + fmt.Sprintf(".temp%v", time.Now().Nanosecond()+time.Now().Second()) - file, err := os.Create(tempFileName) - if err != nil { - return toError(http.StatusInternalServerError, err) - } - defer file.Close() - _, err = io.Copy(file, io.LimitReader(request.Data, 640*1024)) //640K ought to be enough for anybody - if err != nil { - return toError(http.StatusInternalServerError, err) - } - file.Close() - err = os.Rename(tempFileName, fileName) - if err != nil { - return toError(http.StatusInternalServerError, err) - } - return nil -} - -func (h *Handler) GetWalletBackup(ctx context.Context, params oas.GetWalletBackupParams) (*oas.GetWalletBackupOK, error) { - pubKey, verify, err := checkTonConnectToken(params.XTonConnectAuth, h.tonConnect.GetSecret()) - if err != nil { - return nil, toError(http.StatusBadRequest, err) - } - if !verify { - return nil, toError(http.StatusBadRequest, fmt.Errorf("failed verify")) - } - - dump, err := os.ReadFile(fmt.Sprintf("%v.dump", hex.EncodeToString(pubKey))) - if err != nil { - return nil, toError(http.StatusInternalServerError, err) - } - - return &oas.GetWalletBackupOK{Dump: string(dump)}, nil -} - -func checkTonConnectToken(authToken, secret string) ([]byte, bool, error) { - decodedData, err := base64.URLEncoding.DecodeString(authToken) - if err != nil { - return nil, false, err - } - if len(decodedData) <= 32 { - return nil, false, fmt.Errorf("invalid payload length") - } - pubKey := decodedData[:32] - signature := decodedData[32:] - - hmacHash := hmac.New(sha256.New, []byte(secret)) - hmacHash.Write(pubKey) - computedSignature := hmacHash.Sum(nil) - if !hmac.Equal(signature, computedSignature) { - return nil, false, nil - } - - return pubKey, true, nil -} - -func getTotalBalances(ctx context.Context, storage storage, pubKey []byte) (int64, error) { - var balance int64 - versions := []tongoWallet.Version{ - tongoWallet.V1R1, tongoWallet.V1R2, tongoWallet.V1R3, - tongoWallet.V2R1, tongoWallet.V2R2, - tongoWallet.V3R1, tongoWallet.V3R2, - tongoWallet.V4R1, tongoWallet.V4R2, - tongoWallet.V5Beta, - } - var walletAddresses []tongo.AccountID - for _, version := range versions { - walletAddress, err := tongoWallet.GenerateWalletAddress(pubKey, version, nil, 0, nil) - if err != nil { - continue - } - walletAddresses = append(walletAddresses, walletAddress) - } - for _, address := range walletAddresses { - account, err := storage.GetRawAccount(ctx, address) - if err != nil { - continue - } - balance += account.TonBalance - } - return balance, nil -} - func (h *Handler) GetWalletsByPublicKey(ctx context.Context, params oas.GetWalletsByPublicKeyParams) (*oas.Accounts, error) { publicKey, err := hex.DecodeString(params.PublicKey) if err != nil { return nil, toError(http.StatusBadRequest, err) } - walletAddresses, err := h.storage.SearchAccountsByPubKey(publicKey) + walletAddresses, err := h.storage.SearchAccountsByPubKey(ctx, publicKey) if err != nil { return nil, toError(http.StatusBadRequest, err) } @@ -148,6 +38,9 @@ func (h *Handler) GetWalletsByPublicKey(ctx context.Context, params oas.GetWalle } else { res = convertToAccount(account, nil, h.state) } + if account.ExtraBalances != nil { + res.ExtraBalance = convertExtraCurrencies(account.ExtraBalances) + } results = append(results, res) } return &oas.Accounts{Accounts: results}, nil diff --git a/pkg/bath/actions.go b/pkg/bath/actions.go index 0b1dee5d..771d8881 100644 --- a/pkg/bath/actions.go +++ b/pkg/bath/actions.go @@ -19,6 +19,7 @@ import ( const ( TonTransfer ActionType = "TonTransfer" + ExtraCurrencyTransfer ActionType = "ExtraCurrencyTransfer" SmartContractExec ActionType = "SmartContractExec" NftItemTransfer ActionType = "NftItemTransfer" NftPurchase ActionType = "NftPurchase" @@ -69,6 +70,7 @@ type ( Action struct { TonTransfer *TonTransferAction `json:",omitempty"` + ExtraCurrencyTransfer *ExtraCurrencyTransferAction `json:",omitempty"` SmartContractExec *SmartContractAction `json:",omitempty"` NftItemTransfer *NftTransferAction `json:",omitempty"` NftPurchase *NftPurchaseAction `json:",omitempty"` @@ -101,6 +103,14 @@ type ( Sender tongo.AccountID Refund *Refund } + ExtraCurrencyTransferAction struct { + CurrencyID int32 + Amount tlb.VarUInteger32 + Comment *string + EncryptedComment *EncryptedComment + Recipient tongo.AccountID + Sender tongo.AccountID + } SmartContractAction struct { TonAttached int64 Executor tongo.AccountID @@ -263,7 +273,7 @@ func (a Action) ContributeToExtra(account tongo.AccountID) int64 { return 0 } switch a.Type { - case NftItemTransfer, ContractDeploy, UnSubscription, JettonMint, JettonBurn, WithdrawStakeRequest, DomainRenew, InscriptionMint, InscriptionTransfer: // actions without extra + case NftItemTransfer, ContractDeploy, UnSubscription, JettonMint, JettonBurn, WithdrawStakeRequest, DomainRenew, InscriptionMint, InscriptionTransfer, ExtraCurrencyTransfer: // actions without extra return 0 case TonTransfer: return detectDirection(account, a.TonTransfer.Sender, a.TonTransfer.Recipient, a.TonTransfer.Amount) @@ -340,6 +350,10 @@ func (a *TonTransferAction) SubjectAccounts() []tongo.AccountID { return []tongo.AccountID{a.Sender, a.Recipient} } +func (a *ExtraCurrencyTransferAction) SubjectAccounts() []tongo.AccountID { + return []tongo.AccountID{a.Sender, a.Recipient} +} + func (a *SmartContractAction) SubjectAccounts() []tongo.AccountID { return []tongo.AccountID{a.Contract, a.Executor} } diff --git a/pkg/bath/bubble.go b/pkg/bath/bubble.go index 9adae96c..6a274fee 100644 --- a/pkg/bath/bubble.go +++ b/pkg/bath/bubble.go @@ -78,6 +78,7 @@ func fromTrace(trace *core.Trace) *Bubble { btx.bounced = msg.Bounced btx.inputAmount += msg.Value btx.inputAmount += msg.IhrFee + btx.inputExtraAmount = msg.ValueExtra btx.opCode = msg.OpCode btx.decodedBody = msg.DecodedBody btx.inputFrom = source @@ -95,6 +96,7 @@ func fromTrace(trace *core.Trace) *Bubble { Children: make([]*Bubble, len(trace.Children)), ValueFlow: &ValueFlow{ Accounts: map[tongo.AccountID]*AccountValueFlow{ + // TODO: add extra currency trace.Account: { Ton: inputAmount, }, diff --git a/pkg/bath/bubble_tx.go b/pkg/bath/bubble_tx.go index 7f60e231..6ee55fb1 100644 --- a/pkg/bath/bubble_tx.go +++ b/pkg/bath/bubble_tx.go @@ -2,24 +2,24 @@ package bath import ( "fmt" - "github.com/ghodss/yaml" "github.com/tonkeeper/opentonapi/pkg/core" "github.com/tonkeeper/tongo/abi" ) type BubbleTx struct { - success bool - transactionType core.TransactionType - inputAmount int64 - inputFrom *Account - bounce bool - bounced bool - external bool - account Account - opCode *uint32 - decodedBody *core.DecodedMessageBody - init []byte + success bool + transactionType core.TransactionType + inputAmount int64 + inputExtraAmount core.ExtraCurrencies + inputFrom *Account + bounce bool + bounced bool + external bool + account Account + opCode *uint32 + decodedBody *core.DecodedMessageBody + init []byte additionalInfo *core.TraceAdditionalInfo accountWasActiveAtComputingTime bool @@ -52,7 +52,7 @@ func (b BubbleTx) ToAction() *Action { } return nil } - if b.opCode != nil && (*b.opCode != 0 && !b.operation(abi.EncryptedTextCommentMsgOp)) && b.accountWasActiveAtComputingTime && !b.account.Is(abi.Wallet) { + if b.opCode != nil && (*b.opCode != 0 && !b.operation(abi.EncryptedTextCommentMsgOp)) && b.accountWasActiveAtComputingTime && !b.account.Is(abi.Wallet) && len(b.inputExtraAmount) == 0 { operation := fmt.Sprintf("0x%08x", *b.opCode) payload := "" if b.decodedBody != nil { @@ -71,25 +71,48 @@ func (b BubbleTx) ToAction() *Action { Type: SmartContractExec, } } - a := &Action{ - TonTransfer: &TonTransferAction{ - Amount: b.inputAmount, - Recipient: b.account.Address, - Sender: b.inputFrom.Address, //can't be null because we check IsExternal - }, - Success: true, - Type: TonTransfer, - } + var ( + comment *string + encryptedComment *EncryptedComment + ) if b.decodedBody != nil { switch s := b.decodedBody.Value.(type) { case abi.TextCommentMsgBody: converted := string(s.Text) - a.TonTransfer.Comment = &converted + comment = &converted case abi.EncryptedTextCommentMsgBody: - a.TonTransfer.EncryptedComment = &EncryptedComment{EncryptionType: "simple", CipherText: s.CipherText} + encryptedComment = &EncryptedComment{EncryptionType: "simple", CipherText: s.CipherText} } } - return a + if len(b.inputExtraAmount) > 0 { + action := Action{ + ExtraCurrencyTransfer: &ExtraCurrencyTransferAction{ + Recipient: b.account.Address, + Sender: b.inputFrom.Address, //can't be null because we check IsExternal + Comment: comment, + EncryptedComment: encryptedComment, + }, + Success: true, + Type: ExtraCurrencyTransfer, + } + for id, amount := range b.inputExtraAmount { + action.ExtraCurrencyTransfer.CurrencyID = id + action.ExtraCurrencyTransfer.Amount = amount + break // TODO: extract more than one currency + } + return &action + } + return &Action{ + TonTransfer: &TonTransferAction{ + Amount: b.inputAmount, + Recipient: b.account.Address, + Sender: b.inputFrom.Address, //can't be null because we check IsExternal + Comment: comment, + EncryptedComment: encryptedComment, + }, + Success: true, + Type: TonTransfer, + } } func (b BubbleTx) operation(name string) bool { diff --git a/pkg/bath/jettons.go b/pkg/bath/jettons.go index 35615f90..ee8805f0 100644 --- a/pkg/bath/jettons.go +++ b/pkg/bath/jettons.go @@ -98,9 +98,9 @@ func (b BubbleJettonBurn) ToAction() (action *Action) { return &a } -// DedustLPJettonMintStraw example: https://tonviewer.com/transaction/6d33487c44249d7844db8fac38a5cecf1502ec7e0c09d266e98e95a2b1be17b5 -var DedustLPJettonMintStraw = Straw[BubbleJettonMint]{ - CheckFuncs: []bubbleCheck{IsTx, Or(HasOpcode(0xb56b9598), HasOpcode(0x1674b0a0))}, //todo: switch to check interface to be jetton master and rename straw to be more generic +// JettonMintFromMasterStraw example: https://tonviewer.com/transaction/6d33487c44249d7844db8fac38a5cecf1502ec7e0c09d266e98e95a2b1be17b5 +var JettonMintFromMasterStraw = Straw[BubbleJettonMint]{ + CheckFuncs: []bubbleCheck{IsTx, HasInterface(abi.JettonMaster)}, Builder: func(newAction *BubbleJettonMint, bubble *Bubble) (err error) { tx := bubble.Info.(BubbleTx) newAction.master = tx.account.Address diff --git a/pkg/bath/straws.go b/pkg/bath/straws.go index 49ead9b1..d8ed12a2 100644 --- a/pkg/bath/straws.go +++ b/pkg/bath/straws.go @@ -18,7 +18,7 @@ var JettonTransfersBurnsMints = []Merger{ JettonTransferClassicStraw, JettonTransferMinimalStraw, JettonBurnStraw, - DedustLPJettonMintStraw, + JettonMintFromMasterStraw, JettonMintStrawGovernance, WtonMintStraw, } @@ -54,7 +54,7 @@ var DefaultStraws = []Merger{ StrawAuctionBigGetgems, StrawAuctionBuyGetgems, StrawAuctionBuyFragments, - DedustLPJettonMintStraw, + JettonMintFromMasterStraw, JettonMintStrawGovernance, MegatonFiJettonSwap, InitialSubscriptionStraw, diff --git a/pkg/blockchain/msg_sender.go b/pkg/blockchain/msg_sender.go index ffabd89b..5a59f04b 100644 --- a/pkg/blockchain/msg_sender.go +++ b/pkg/blockchain/msg_sender.go @@ -49,6 +49,8 @@ type ExtInMsgCopy struct { // Accounts is set when the message is emulated. Accounts map[tongo.AccountID]struct{} + Meta map[string]string + SendFailed bool // default is false, so we are good with backward compatibility. } @@ -147,16 +149,16 @@ func (ms *MsgSender) SendMessage(ctx context.Context, msgCopy ExtInMsgCopy) erro return err } err := ms.send(ctx, msgCopy.Payload) - msgCopy.SendFailed = err != nil - - for name, ch := range ms.receivers { - select { - case ch <- msgCopy: - default: - ms.logger.Warn("receiver is too slow", zap.String("name", name)) + if err == nil { + for name, ch := range ms.receivers { + select { + case ch <- msgCopy: + default: + ms.logger.Warn("receiver is too slow", zap.String("name", name)) + } } } - return nil + return err } func (ms *MsgSender) send(ctx context.Context, payload []byte) error { diff --git a/pkg/core/account.go b/pkg/core/account.go index 57581f14..645e8b4c 100644 --- a/pkg/core/account.go +++ b/pkg/core/account.go @@ -6,7 +6,6 @@ import ( "github.com/tonkeeper/tongo/abi" "github.com/tonkeeper/tongo/ton" - "github.com/shopspring/decimal" "github.com/tonkeeper/tongo/tlb" ) @@ -15,7 +14,7 @@ type Account struct { AccountAddress ton.AccountID Status tlb.AccountStatus TonBalance int64 - ExtraBalances map[uint32]decimal.Decimal + ExtraBalances ExtraCurrencies LastTransactionLt uint64 LastTransactionHash ton.Bits256 Code []byte @@ -56,7 +55,8 @@ type Contract struct { LastTransactionLt uint64 } -type AccountStats struct { +type AccountStat struct { + TonBalance int64 AccountID ton.AccountID NftsCount int32 JettonsCount int32 diff --git a/pkg/core/converters.go b/pkg/core/converters.go index 08e8a03e..c9184801 100644 --- a/pkg/core/converters.go +++ b/pkg/core/converters.go @@ -5,7 +5,6 @@ import ( "math/big" "sort" - "github.com/shopspring/decimal" "github.com/tonkeeper/tongo" "github.com/tonkeeper/tongo/abi" "github.com/tonkeeper/tongo/boc" @@ -313,6 +312,7 @@ func ConvertMessage(message tlb.Message, txLT uint64, cd *abi.ContractDescriptio if err != nil { return Message{}, err } + return Message{ MessageID: MessageID{ CreatedLt: info.CreatedLt, @@ -324,6 +324,7 @@ func ConvertMessage(message tlb.Message, txLT uint64, cd *abi.ContractDescriptio Bounce: info.Bounce, Bounced: info.Bounced, Value: int64(info.Value.Grams), + ValueExtra: extractExtraCurrencies(info.Value.Other), FwdFee: int64(info.FwdFee), IhrFee: int64(info.IhrFee), ImportFee: 0, @@ -434,15 +435,7 @@ func ConvertToAccount(accountId tongo.AccountID, shardAccount tlb.ShardAccount) } balance := acc.Account.Storage.Balance res.TonBalance = int64(balance.Grams) - items := balance.Other.Dict.Items() - if len(items) > 0 { - otherBalances := make(map[uint32]decimal.Decimal, len(items)) - for _, item := range items { - value := big.Int(item.Value) - otherBalances[uint32(item.Key)] = decimal.NewFromBigInt(&value, 0) - } - res.ExtraBalances = otherBalances - } + res.ExtraBalances = extractExtraCurrencies(balance.Other) res.LastTransactionLt = shardAccount.LastTransLt res.LastTransactionHash = tongo.Bits256(shardAccount.LastTransHash) if acc.Account.Storage.State.SumType == "AccountUninit" { @@ -481,6 +474,18 @@ func ConvertToAccount(accountId tongo.AccountID, shardAccount tlb.ShardAccount) return res, nil } +func extractExtraCurrencies(extraCurrencyCollection tlb.ExtraCurrencyCollection) ExtraCurrencies { + items := extraCurrencyCollection.Dict.Items() + if len(items) > 0 { + res := make(map[int32]tlb.VarUInteger32, len(items)) + for _, item := range items { + res[int32(item.Key)] = item.Value + } + return res + } + return nil +} + func ExtractTransactions(id tongo.BlockIDExt, block *tlb.Block) ([]*Transaction, error) { rawTransactions := block.AllTransactions() transactions := make([]*Transaction, 0, len(rawTransactions)) diff --git a/pkg/core/extra_currencies.go b/pkg/core/extra_currencies.go new file mode 100644 index 00000000..dc9a8fa2 --- /dev/null +++ b/pkg/core/extra_currencies.go @@ -0,0 +1,7 @@ +package core + +import ( + "github.com/tonkeeper/tongo/tlb" +) + +type ExtraCurrencies map[int32]tlb.VarUInteger32 diff --git a/pkg/core/transactions.go b/pkg/core/transactions.go index 77f70930..b6b6db24 100644 --- a/pkg/core/transactions.go +++ b/pkg/core/transactions.go @@ -163,6 +163,7 @@ type Message struct { Bounce bool Bounced bool Value int64 + ValueExtra ExtraCurrencies FwdFee int64 IhrFee int64 ImportFee int64 @@ -189,6 +190,5 @@ func externalAddressFromTlb(address tlb.MsgAddress) *ExternalAddress { if address.SumType != "AddrExtern" { return nil } - external := address.AddrExtern.ExternalAddress - return &external + return address.AddrExtern } diff --git a/pkg/litestorage/account.go b/pkg/litestorage/account.go index d30c59a3..a9fd98ce 100644 --- a/pkg/litestorage/account.go +++ b/pkg/litestorage/account.go @@ -36,7 +36,7 @@ func (s *LiteStorage) AccountStatusAndInterfaces(addr tongo.AccountID) (tlb.Acco return account.Status, account.Interfaces, err } -func (s *LiteStorage) SearchAccountsByPubKey(pubKey ed25519.PublicKey) ([]tongo.AccountID, error) { +func (s *LiteStorage) SearchAccountsByPubKey(ctx context.Context, pubKey ed25519.PublicKey) ([]tongo.AccountID, error) { versions := []tongoWallet.Version{ tongoWallet.V1R1, tongoWallet.V1R2, tongoWallet.V1R3, tongoWallet.V2R1, tongoWallet.V2R2, diff --git a/pkg/litestorage/transactions.go b/pkg/litestorage/transactions.go index aa552e42..fd56c579 100644 --- a/pkg/litestorage/transactions.go +++ b/pkg/litestorage/transactions.go @@ -35,7 +35,7 @@ func (s *LiteStorage) GetLogs(ctx context.Context, account tongo.AccountID, dest if destination.SumType == "AddrNone" && m.DestinationExtern != nil { continue } - if destination.SumType == "AddrExtern" && (m.DestinationExtern == nil || m.DestinationExtern.ToFiftHex() != destination.AddrExtern.ExternalAddress.ToFiftHex()) { + if destination.SumType == "AddrExtern" && (m.DestinationExtern == nil || m.DestinationExtern.ToFiftHex() != destination.AddrExtern.ToFiftHex()) { continue } } diff --git a/pkg/oas/oas_handlers_gen.go b/pkg/oas/oas_handlers_gen.go index 67b5eedd..7493946a 100644 --- a/pkg/oas/oas_handlers_gen.go +++ b/pkg/oas/oas_handlers_gen.go @@ -8,6 +8,7 @@ import ( "time" "github.com/go-faster/errors" + "github.com/go-faster/jx" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" @@ -1219,6 +1220,10 @@ func (s *Server) handleExecGetMethodForBlockchainAccountRequest(args [2]string, Name: "args", In: "query", }: params.Args, + { + Name: "fix_order", + In: "query", + }: params.FixOrder, }, Raw: r, } @@ -8549,6 +8554,202 @@ func (s *Server) handleGetNftItemsByAddressesRequest(args [0]string, argsEscaped } } +// handleGetOpenapiJsonRequest handles getOpenapiJson operation. +// +// Get the openapi.json file. +// +// GET /v2/openapi.json +func (s *Server) handleGetOpenapiJsonRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getOpenapiJson"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/v2/openapi.json"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "GetOpenapiJson", + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + s.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + err error + ) + + var response jx.Raw + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "GetOpenapiJson", + OperationSummary: "", + OperationID: "getOpenapiJson", + Body: nil, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = struct{} + Params = struct{} + Response = jx.Raw + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetOpenapiJson(ctx) + return response, err + }, + ) + } else { + response, err = s.h.GetOpenapiJson(ctx) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + recordError("Internal", err) + } + return + } + + if err := encodeGetOpenapiJsonResponse(response, w, span); err != nil { + recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleGetOpenapiYmlRequest handles getOpenapiYml operation. +// +// Get the openapi.yml file. +// +// GET /v2/openapi.yml +func (s *Server) handleGetOpenapiYmlRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("getOpenapiYml"), + semconv.HTTPMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/v2/openapi.yml"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), "GetOpenapiYml", + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + s.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + s.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + err error + ) + + var response GetOpenapiYmlOK + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: "GetOpenapiYml", + OperationSummary: "", + OperationID: "getOpenapiYml", + Body: nil, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = struct{} + Params = struct{} + Response = GetOpenapiYmlOK + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetOpenapiYml(ctx) + return response, err + }, + ) + } else { + response, err = s.h.GetOpenapiYml(ctx) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + recordError("Internal", err) + } + return + } + + if err := encodeGetOpenapiYmlResponse(response, w, span); err != nil { + recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + // handleGetOutMsgQueueSizesRequest handles getOutMsgQueueSizes operation. // // Get out msg queue sizes. @@ -11324,123 +11525,6 @@ func (s *Server) handleGetTraceRequest(args [1]string, argsEscaped bool, w http. } } -// handleGetWalletBackupRequest handles getWalletBackup operation. -// -// Get backup info. -// -// GET /v2/wallet/backup -func (s *Server) handleGetWalletBackupRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { - otelAttrs := []attribute.KeyValue{ - otelogen.OperationID("getWalletBackup"), - semconv.HTTPMethodKey.String("GET"), - semconv.HTTPRouteKey.String("/v2/wallet/backup"), - } - - // Start a span for this request. - ctx, span := s.cfg.Tracer.Start(r.Context(), "GetWalletBackup", - trace.WithAttributes(otelAttrs...), - serverSpanKind, - ) - defer span.End() - - // Run stopwatch. - startTime := time.Now() - defer func() { - elapsedDuration := time.Since(startTime) - // Use floating point division here for higher precision (instead of Millisecond method). - s.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), metric.WithAttributes(otelAttrs...)) - }() - - // Increment request counter. - s.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) - - var ( - recordError = func(stage string, err error) { - span.RecordError(err) - span.SetStatus(codes.Error, stage) - s.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) - } - err error - opErrContext = ogenerrors.OperationContext{ - Name: "GetWalletBackup", - ID: "getWalletBackup", - } - ) - params, err := decodeGetWalletBackupParams(args, argsEscaped, r) - if err != nil { - err = &ogenerrors.DecodeParamsError{ - OperationContext: opErrContext, - Err: err, - } - recordError("DecodeParams", err) - s.cfg.ErrorHandler(ctx, w, r, err) - return - } - - var response *GetWalletBackupOK - if m := s.cfg.Middleware; m != nil { - mreq := middleware.Request{ - Context: ctx, - OperationName: "GetWalletBackup", - OperationSummary: "", - OperationID: "getWalletBackup", - Body: nil, - Params: middleware.Parameters{ - { - Name: "X-TonConnect-Auth", - In: "header", - }: params.XTonConnectAuth, - }, - Raw: r, - } - - type ( - Request = struct{} - Params = GetWalletBackupParams - Response = *GetWalletBackupOK - ) - response, err = middleware.HookMiddleware[ - Request, - Params, - Response, - ]( - m, - mreq, - unpackGetWalletBackupParams, - func(ctx context.Context, request Request, params Params) (response Response, err error) { - response, err = s.h.GetWalletBackup(ctx, params) - return response, err - }, - ) - } else { - response, err = s.h.GetWalletBackup(ctx, params) - } - if err != nil { - if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { - if err := encodeErrorResponse(errRes, w, span); err != nil { - recordError("Internal", err) - } - return - } - if errors.Is(err, ht.ErrNotImplemented) { - s.cfg.ErrorHandler(ctx, w, r, err) - return - } - if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { - recordError("Internal", err) - } - return - } - - if err := encodeGetWalletBackupResponse(response, w, span); err != nil { - recordError("EncodeResponse", err) - if !errors.Is(err, ht.ErrInternalServerErrorResponse) { - s.cfg.ErrorHandler(ctx, w, r, err) - } - return - } -} - // handleGetWalletsByPublicKeyRequest handles getWalletsByPublicKey operation. // // Get wallets by public key. @@ -12026,138 +12110,6 @@ func (s *Server) handleSendRawMessageRequest(args [0]string, argsEscaped bool, w } } -// handleSetWalletBackupRequest handles setWalletBackup operation. -// -// Set backup info. -// -// PUT /v2/wallet/backup -func (s *Server) handleSetWalletBackupRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { - otelAttrs := []attribute.KeyValue{ - otelogen.OperationID("setWalletBackup"), - semconv.HTTPMethodKey.String("PUT"), - semconv.HTTPRouteKey.String("/v2/wallet/backup"), - } - - // Start a span for this request. - ctx, span := s.cfg.Tracer.Start(r.Context(), "SetWalletBackup", - trace.WithAttributes(otelAttrs...), - serverSpanKind, - ) - defer span.End() - - // Run stopwatch. - startTime := time.Now() - defer func() { - elapsedDuration := time.Since(startTime) - // Use floating point division here for higher precision (instead of Millisecond method). - s.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), metric.WithAttributes(otelAttrs...)) - }() - - // Increment request counter. - s.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) - - var ( - recordError = func(stage string, err error) { - span.RecordError(err) - span.SetStatus(codes.Error, stage) - s.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) - } - err error - opErrContext = ogenerrors.OperationContext{ - Name: "SetWalletBackup", - ID: "setWalletBackup", - } - ) - params, err := decodeSetWalletBackupParams(args, argsEscaped, r) - if err != nil { - err = &ogenerrors.DecodeParamsError{ - OperationContext: opErrContext, - Err: err, - } - recordError("DecodeParams", err) - s.cfg.ErrorHandler(ctx, w, r, err) - return - } - request, close, err := s.decodeSetWalletBackupRequest(r) - if err != nil { - err = &ogenerrors.DecodeRequestError{ - OperationContext: opErrContext, - Err: err, - } - recordError("DecodeRequest", err) - s.cfg.ErrorHandler(ctx, w, r, err) - return - } - defer func() { - if err := close(); err != nil { - recordError("CloseRequest", err) - } - }() - - var response *SetWalletBackupOK - if m := s.cfg.Middleware; m != nil { - mreq := middleware.Request{ - Context: ctx, - OperationName: "SetWalletBackup", - OperationSummary: "", - OperationID: "setWalletBackup", - Body: request, - Params: middleware.Parameters{ - { - Name: "X-TonConnect-Auth", - In: "header", - }: params.XTonConnectAuth, - }, - Raw: r, - } - - type ( - Request = SetWalletBackupReq - Params = SetWalletBackupParams - Response = *SetWalletBackupOK - ) - response, err = middleware.HookMiddleware[ - Request, - Params, - Response, - ]( - m, - mreq, - unpackSetWalletBackupParams, - func(ctx context.Context, request Request, params Params) (response Response, err error) { - err = s.h.SetWalletBackup(ctx, request, params) - return response, err - }, - ) - } else { - err = s.h.SetWalletBackup(ctx, request, params) - } - if err != nil { - if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { - if err := encodeErrorResponse(errRes, w, span); err != nil { - recordError("Internal", err) - } - return - } - if errors.Is(err, ht.ErrNotImplemented) { - s.cfg.ErrorHandler(ctx, w, r, err) - return - } - if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { - recordError("Internal", err) - } - return - } - - if err := encodeSetWalletBackupResponse(response, w, span); err != nil { - recordError("EncodeResponse", err) - if !errors.Is(err, ht.ErrInternalServerErrorResponse) { - s.cfg.ErrorHandler(ctx, w, r, err) - } - return - } -} - // handleStatusRequest handles status operation. // // Status. diff --git a/pkg/oas/oas_json_gen.go b/pkg/oas/oas_json_gen.go index 4e7cdee3..f9ee6621 100644 --- a/pkg/oas/oas_json_gen.go +++ b/pkg/oas/oas_json_gen.go @@ -71,6 +71,16 @@ func (s *Account) encodeFields(e *jx.Encoder) { e.FieldStart("balance") e.Int64(s.Balance) } + { + if s.ExtraBalance != nil { + e.FieldStart("extra_balance") + e.ArrStart() + for _, elem := range s.ExtraBalance { + elem.Encode(e) + } + e.ArrEnd() + } + } { if s.CurrenciesBalance.Set { e.FieldStart("currencies_balance") @@ -139,20 +149,21 @@ func (s *Account) encodeFields(e *jx.Encoder) { } } -var jsonFieldsNameOfAccount = [13]string{ +var jsonFieldsNameOfAccount = [14]string{ 0: "address", 1: "balance", - 2: "currencies_balance", - 3: "last_activity", - 4: "status", - 5: "interfaces", - 6: "name", - 7: "is_scam", - 8: "icon", - 9: "memo_required", - 10: "get_methods", - 11: "is_suspended", - 12: "is_wallet", + 2: "extra_balance", + 3: "currencies_balance", + 4: "last_activity", + 5: "status", + 6: "interfaces", + 7: "name", + 8: "is_scam", + 9: "icon", + 10: "memo_required", + 11: "get_methods", + 12: "is_suspended", + 13: "is_wallet", } // Decode decodes Account from json. @@ -188,6 +199,23 @@ func (s *Account) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"balance\"") } + case "extra_balance": + if err := func() error { + s.ExtraBalance = make([]ExtraCurrency, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem ExtraCurrency + if err := elem.Decode(d); err != nil { + return err + } + s.ExtraBalance = append(s.ExtraBalance, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"extra_balance\"") + } case "currencies_balance": if err := func() error { s.CurrenciesBalance.Reset() @@ -199,7 +227,7 @@ func (s *Account) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"currencies_balance\"") } case "last_activity": - requiredBitSet[0] |= 1 << 3 + requiredBitSet[0] |= 1 << 4 if err := func() error { v, err := d.Int64() s.LastActivity = int64(v) @@ -211,7 +239,7 @@ func (s *Account) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"last_activity\"") } case "status": - requiredBitSet[0] |= 1 << 4 + requiredBitSet[0] |= 1 << 5 if err := func() error { if err := s.Status.Decode(d); err != nil { return err @@ -280,7 +308,7 @@ func (s *Account) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"memo_required\"") } case "get_methods": - requiredBitSet[1] |= 1 << 2 + requiredBitSet[1] |= 1 << 3 if err := func() error { s.GetMethods = make([]string, 0) if err := d.Arr(func(d *jx.Decoder) error { @@ -310,7 +338,7 @@ func (s *Account) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"is_suspended\"") } case "is_wallet": - requiredBitSet[1] |= 1 << 4 + requiredBitSet[1] |= 1 << 5 if err := func() error { v, err := d.Bool() s.IsWallet = bool(v) @@ -331,8 +359,8 @@ func (s *Account) Decode(d *jx.Decoder) error { // Validate required fields. var failures []validate.FieldError for i, mask := range [2]uint8{ - 0b00011011, - 0b00010100, + 0b00110011, + 0b00101000, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -1666,6 +1694,12 @@ func (s *Action) encodeFields(e *jx.Encoder) { s.TonTransfer.Encode(e) } } + { + if s.ExtraCurrencyTransfer.Set { + e.FieldStart("ExtraCurrencyTransfer") + s.ExtraCurrencyTransfer.Encode(e) + } + } { if s.ContractDeploy.Set { e.FieldStart("ContractDeploy") @@ -1794,31 +1828,32 @@ func (s *Action) encodeFields(e *jx.Encoder) { } } -var jsonFieldsNameOfAction = [24]string{ +var jsonFieldsNameOfAction = [25]string{ 0: "type", 1: "status", 2: "TonTransfer", - 3: "ContractDeploy", - 4: "JettonTransfer", - 5: "JettonBurn", - 6: "JettonMint", - 7: "NftItemTransfer", - 8: "Subscribe", - 9: "UnSubscribe", - 10: "AuctionBid", - 11: "NftPurchase", - 12: "DepositStake", - 13: "WithdrawStake", - 14: "WithdrawStakeRequest", - 15: "ElectionsDepositStake", - 16: "ElectionsRecoverStake", - 17: "JettonSwap", - 18: "SmartContractExec", - 19: "DomainRenew", - 20: "InscriptionTransfer", - 21: "InscriptionMint", - 22: "simple_preview", - 23: "base_transactions", + 3: "ExtraCurrencyTransfer", + 4: "ContractDeploy", + 5: "JettonTransfer", + 6: "JettonBurn", + 7: "JettonMint", + 8: "NftItemTransfer", + 9: "Subscribe", + 10: "UnSubscribe", + 11: "AuctionBid", + 12: "NftPurchase", + 13: "DepositStake", + 14: "WithdrawStake", + 15: "WithdrawStakeRequest", + 16: "ElectionsDepositStake", + 17: "ElectionsRecoverStake", + 18: "JettonSwap", + 19: "SmartContractExec", + 20: "DomainRenew", + 21: "InscriptionTransfer", + 22: "InscriptionMint", + 23: "simple_preview", + 24: "base_transactions", } // Decode decodes Action from json. @@ -1826,7 +1861,7 @@ func (s *Action) Decode(d *jx.Decoder) error { if s == nil { return errors.New("invalid: unable to decode Action to nil") } - var requiredBitSet [3]uint8 + var requiredBitSet [4]uint8 if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { switch string(k) { @@ -1860,6 +1895,16 @@ func (s *Action) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"TonTransfer\"") } + case "ExtraCurrencyTransfer": + if err := func() error { + s.ExtraCurrencyTransfer.Reset() + if err := s.ExtraCurrencyTransfer.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"ExtraCurrencyTransfer\"") + } case "ContractDeploy": if err := func() error { s.ContractDeploy.Reset() @@ -2051,7 +2096,7 @@ func (s *Action) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"InscriptionMint\"") } case "simple_preview": - requiredBitSet[2] |= 1 << 6 + requiredBitSet[2] |= 1 << 7 if err := func() error { if err := s.SimplePreview.Decode(d); err != nil { return err @@ -2061,7 +2106,7 @@ func (s *Action) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"simple_preview\"") } case "base_transactions": - requiredBitSet[2] |= 1 << 7 + requiredBitSet[3] |= 1 << 0 if err := func() error { s.BaseTransactions = make([]string, 0) if err := d.Arr(func(d *jx.Decoder) error { @@ -2089,10 +2134,11 @@ func (s *Action) Decode(d *jx.Decoder) error { } // Validate required fields. var failures []validate.FieldError - for i, mask := range [3]uint8{ + for i, mask := range [4]uint8{ 0b00000011, 0b00000000, - 0b11000000, + 0b10000000, + 0b00000001, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -2585,6 +2631,8 @@ func (s *ActionType) Decode(d *jx.Decoder) error { switch ActionType(v) { case ActionTypeTonTransfer: *s = ActionTypeTonTransfer + case ActionTypeExtraCurrencyTransfer: + *s = ActionTypeExtraCurrencyTransfer case ActionTypeJettonTransfer: *s = ActionTypeJettonTransfer case ActionTypeJettonBurn: @@ -5975,6 +6023,12 @@ func (s *BlockchainConfig) encodeFields(e *jx.Encoder) { e.FieldStart("44") s.R44.Encode(e) } + { + if s.R45.Set { + e.FieldStart("45") + s.R45.Encode(e) + } + } { if s.R71.Set { e.FieldStart("71") @@ -6013,7 +6067,7 @@ func (s *BlockchainConfig) encodeFields(e *jx.Encoder) { } } -var jsonFieldsNameOfBlockchainConfig = [44]string{ +var jsonFieldsNameOfBlockchainConfig = [45]string{ 0: "raw", 1: "0", 2: "1", @@ -6052,12 +6106,13 @@ var jsonFieldsNameOfBlockchainConfig = [44]string{ 35: "40", 36: "43", 37: "44", - 38: "71", - 39: "72", - 40: "73", - 41: "79", - 42: "81", - 43: "82", + 38: "45", + 39: "71", + 40: "72", + 41: "73", + 42: "79", + 43: "81", + 44: "82", } // Decode decodes BlockchainConfig from json. @@ -6459,6 +6514,16 @@ func (s *BlockchainConfig) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"44\"") } + case "45": + if err := func() error { + s.R45.Reset() + if err := s.R45.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"45\"") + } case "71": if err := func() error { s.R71.Reset() @@ -9290,6 +9355,225 @@ func (s *BlockchainConfig44) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *BlockchainConfig45) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *BlockchainConfig45) encodeFields(e *jx.Encoder) { + { + e.FieldStart("contracts") + e.ArrStart() + for _, elem := range s.Contracts { + elem.Encode(e) + } + e.ArrEnd() + } +} + +var jsonFieldsNameOfBlockchainConfig45 = [1]string{ + 0: "contracts", +} + +// Decode decodes BlockchainConfig45 from json. +func (s *BlockchainConfig45) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode BlockchainConfig45 to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "contracts": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + s.Contracts = make([]BlockchainConfig45ContractsItem, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem BlockchainConfig45ContractsItem + if err := elem.Decode(d); err != nil { + return err + } + s.Contracts = append(s.Contracts, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"contracts\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode BlockchainConfig45") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfBlockchainConfig45) { + name = jsonFieldsNameOfBlockchainConfig45[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *BlockchainConfig45) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *BlockchainConfig45) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *BlockchainConfig45ContractsItem) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *BlockchainConfig45ContractsItem) encodeFields(e *jx.Encoder) { + { + e.FieldStart("code_hash") + e.Str(s.CodeHash) + } + { + e.FieldStart("gas_usage") + e.Int64(s.GasUsage) + } +} + +var jsonFieldsNameOfBlockchainConfig45ContractsItem = [2]string{ + 0: "code_hash", + 1: "gas_usage", +} + +// Decode decodes BlockchainConfig45ContractsItem from json. +func (s *BlockchainConfig45ContractsItem) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode BlockchainConfig45ContractsItem to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "code_hash": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.CodeHash = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"code_hash\"") + } + case "gas_usage": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Int64() + s.GasUsage = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"gas_usage\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode BlockchainConfig45ContractsItem") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfBlockchainConfig45ContractsItem) { + name = jsonFieldsNameOfBlockchainConfig45ContractsItem[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *BlockchainConfig45ContractsItem) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *BlockchainConfig45ContractsItem) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *BlockchainConfig5) Encode(e *jx.Encoder) { e.ObjStart() @@ -14163,71 +14447,90 @@ func (s *DomainRenewAction) UnmarshalJSON(data []byte) error { } // Encode implements json.Marshaler. -func (s *ElectionsDepositStakeAction) Encode(e *jx.Encoder) { +func (s *EcPreview) Encode(e *jx.Encoder) { e.ObjStart() s.encodeFields(e) e.ObjEnd() } // encodeFields encodes fields. -func (s *ElectionsDepositStakeAction) encodeFields(e *jx.Encoder) { +func (s *EcPreview) encodeFields(e *jx.Encoder) { { - e.FieldStart("amount") - e.Int64(s.Amount) + e.FieldStart("symbol") + e.Str(s.Symbol) } { - e.FieldStart("staker") - s.Staker.Encode(e) + e.FieldStart("decimals") + e.Int(s.Decimals) + } + { + e.FieldStart("image") + e.Str(s.Image) } } -var jsonFieldsNameOfElectionsDepositStakeAction = [2]string{ - 0: "amount", - 1: "staker", +var jsonFieldsNameOfEcPreview = [3]string{ + 0: "symbol", + 1: "decimals", + 2: "image", } -// Decode decodes ElectionsDepositStakeAction from json. -func (s *ElectionsDepositStakeAction) Decode(d *jx.Decoder) error { +// Decode decodes EcPreview from json. +func (s *EcPreview) Decode(d *jx.Decoder) error { if s == nil { - return errors.New("invalid: unable to decode ElectionsDepositStakeAction to nil") + return errors.New("invalid: unable to decode EcPreview to nil") } var requiredBitSet [1]uint8 if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { switch string(k) { - case "amount": + case "symbol": requiredBitSet[0] |= 1 << 0 if err := func() error { - v, err := d.Int64() - s.Amount = int64(v) + v, err := d.Str() + s.Symbol = string(v) if err != nil { return err } return nil }(); err != nil { - return errors.Wrap(err, "decode field \"amount\"") + return errors.Wrap(err, "decode field \"symbol\"") } - case "staker": + case "decimals": requiredBitSet[0] |= 1 << 1 if err := func() error { - if err := s.Staker.Decode(d); err != nil { + v, err := d.Int() + s.Decimals = int(v) + if err != nil { return err } return nil }(); err != nil { - return errors.Wrap(err, "decode field \"staker\"") + return errors.Wrap(err, "decode field \"decimals\"") + } + case "image": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.Image = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"image\"") } default: return d.Skip() } return nil }); err != nil { - return errors.Wrap(err, "decode ElectionsDepositStakeAction") + return errors.Wrap(err, "decode EcPreview") } // Validate required fields. var failures []validate.FieldError for i, mask := range [1]uint8{ - 0b00000011, + 0b00000111, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -14239,8 +14542,119 @@ func (s *ElectionsDepositStakeAction) Decode(d *jx.Decoder) error { bitIdx := bits.TrailingZeros8(result) fieldIdx := i*8 + bitIdx var name string - if fieldIdx < len(jsonFieldsNameOfElectionsDepositStakeAction) { - name = jsonFieldsNameOfElectionsDepositStakeAction[fieldIdx] + if fieldIdx < len(jsonFieldsNameOfEcPreview) { + name = jsonFieldsNameOfEcPreview[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *EcPreview) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *EcPreview) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ElectionsDepositStakeAction) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ElectionsDepositStakeAction) encodeFields(e *jx.Encoder) { + { + e.FieldStart("amount") + e.Int64(s.Amount) + } + { + e.FieldStart("staker") + s.Staker.Encode(e) + } +} + +var jsonFieldsNameOfElectionsDepositStakeAction = [2]string{ + 0: "amount", + 1: "staker", +} + +// Decode decodes ElectionsDepositStakeAction from json. +func (s *ElectionsDepositStakeAction) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ElectionsDepositStakeAction to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "amount": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int64() + s.Amount = int64(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"amount\"") + } + case "staker": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Staker.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"staker\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ElectionsDepositStakeAction") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfElectionsDepositStakeAction) { + name = jsonFieldsNameOfElectionsDepositStakeAction[fieldIdx] } else { name = strconv.Itoa(fieldIdx) } @@ -15035,10 +15449,17 @@ func (s *Error) encodeFields(e *jx.Encoder) { e.FieldStart("error") e.Str(s.Error) } + { + if s.ErrorCode.Set { + e.FieldStart("error_code") + s.ErrorCode.Encode(e) + } + } } -var jsonFieldsNameOfError = [1]string{ +var jsonFieldsNameOfError = [2]string{ 0: "error", + 1: "error_code", } // Decode decodes Error from json. @@ -15062,6 +15483,16 @@ func (s *Error) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"error\"") } + case "error_code": + if err := func() error { + s.ErrorCode.Reset() + if err := s.ErrorCode.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"error_code\"") + } default: return d.Skip() } @@ -15336,6 +15767,328 @@ func (s *Event) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *ExtraCurrency) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ExtraCurrency) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + e.Int32(s.ID) + } + { + e.FieldStart("amount") + e.Str(s.Amount) + } + { + if s.Name.Set { + e.FieldStart("name") + s.Name.Encode(e) + } + } + { + e.FieldStart("decimals") + e.Int(s.Decimals) + } +} + +var jsonFieldsNameOfExtraCurrency = [4]string{ + 0: "id", + 1: "amount", + 2: "name", + 3: "decimals", +} + +// Decode decodes ExtraCurrency from json. +func (s *ExtraCurrency) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ExtraCurrency to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Int32() + s.ID = int32(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + case "amount": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.Amount = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"amount\"") + } + case "name": + if err := func() error { + s.Name.Reset() + if err := s.Name.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"name\"") + } + case "decimals": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := d.Int() + s.Decimals = int(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"decimals\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ExtraCurrency") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00001011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfExtraCurrency) { + name = jsonFieldsNameOfExtraCurrency[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ExtraCurrency) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ExtraCurrency) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *ExtraCurrencyTransferAction) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *ExtraCurrencyTransferAction) encodeFields(e *jx.Encoder) { + { + e.FieldStart("sender") + s.Sender.Encode(e) + } + { + e.FieldStart("recipient") + s.Recipient.Encode(e) + } + { + e.FieldStart("amount") + e.Str(s.Amount) + } + { + if s.Comment.Set { + e.FieldStart("comment") + s.Comment.Encode(e) + } + } + { + if s.EncryptedComment.Set { + e.FieldStart("encrypted_comment") + s.EncryptedComment.Encode(e) + } + } + { + e.FieldStart("currency") + s.Currency.Encode(e) + } +} + +var jsonFieldsNameOfExtraCurrencyTransferAction = [6]string{ + 0: "sender", + 1: "recipient", + 2: "amount", + 3: "comment", + 4: "encrypted_comment", + 5: "currency", +} + +// Decode decodes ExtraCurrencyTransferAction from json. +func (s *ExtraCurrencyTransferAction) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode ExtraCurrencyTransferAction to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "sender": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + if err := s.Sender.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"sender\"") + } + case "recipient": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Recipient.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"recipient\"") + } + case "amount": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + v, err := d.Str() + s.Amount = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"amount\"") + } + case "comment": + if err := func() error { + s.Comment.Reset() + if err := s.Comment.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"comment\"") + } + case "encrypted_comment": + if err := func() error { + s.EncryptedComment.Reset() + if err := s.EncryptedComment.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"encrypted_comment\"") + } + case "currency": + requiredBitSet[0] |= 1 << 5 + if err := func() error { + if err := s.Currency.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"currency\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode ExtraCurrencyTransferAction") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00100111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfExtraCurrencyTransferAction) { + name = jsonFieldsNameOfExtraCurrencyTransferAction[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *ExtraCurrencyTransferAction) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *ExtraCurrencyTransferAction) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *FoundAccounts) Encode(e *jx.Encoder) { e.ObjStart() @@ -21326,102 +22079,6 @@ func (s *GetTonConnectPayloadOK) UnmarshalJSON(data []byte) error { return s.Decode(d) } -// Encode implements json.Marshaler. -func (s *GetWalletBackupOK) Encode(e *jx.Encoder) { - e.ObjStart() - s.encodeFields(e) - e.ObjEnd() -} - -// encodeFields encodes fields. -func (s *GetWalletBackupOK) encodeFields(e *jx.Encoder) { - { - e.FieldStart("dump") - e.Str(s.Dump) - } -} - -var jsonFieldsNameOfGetWalletBackupOK = [1]string{ - 0: "dump", -} - -// Decode decodes GetWalletBackupOK from json. -func (s *GetWalletBackupOK) Decode(d *jx.Decoder) error { - if s == nil { - return errors.New("invalid: unable to decode GetWalletBackupOK to nil") - } - var requiredBitSet [1]uint8 - - if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { - switch string(k) { - case "dump": - requiredBitSet[0] |= 1 << 0 - if err := func() error { - v, err := d.Str() - s.Dump = string(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"dump\"") - } - default: - return d.Skip() - } - return nil - }); err != nil { - return errors.Wrap(err, "decode GetWalletBackupOK") - } - // Validate required fields. - var failures []validate.FieldError - for i, mask := range [1]uint8{ - 0b00000001, - } { - if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { - // Mask only required fields and check equality to mask using XOR. - // - // If XOR result is not zero, result is not equal to expected, so some fields are missed. - // Bits of fields which would be set are actually bits of missed fields. - missed := bits.OnesCount8(result) - for bitN := 0; bitN < missed; bitN++ { - bitIdx := bits.TrailingZeros8(result) - fieldIdx := i*8 + bitIdx - var name string - if fieldIdx < len(jsonFieldsNameOfGetWalletBackupOK) { - name = jsonFieldsNameOfGetWalletBackupOK[fieldIdx] - } else { - name = strconv.Itoa(fieldIdx) - } - failures = append(failures, validate.FieldError{ - Name: name, - Error: validate.ErrFieldRequired, - }) - // Reset bit. - result &^= 1 << bitIdx - } - } - } - if len(failures) > 0 { - return &validate.Error{Fields: failures} - } - - return nil -} - -// MarshalJSON implements stdjson.Marshaler. -func (s *GetWalletBackupOK) MarshalJSON() ([]byte, error) { - e := jx.Encoder{} - s.Encode(&e) - return e.Bytes(), nil -} - -// UnmarshalJSON implements stdjson.Unmarshaler. -func (s *GetWalletBackupOK) UnmarshalJSON(data []byte) error { - d := jx.DecodeBytes(data) - return s.Decode(d) -} - // Encode implements json.Marshaler. func (s *ImagePreview) Encode(e *jx.Encoder) { e.ObjStart() @@ -23501,6 +24158,10 @@ func (s *JettonInfo) encodeFields(e *jx.Encoder) { e.FieldStart("metadata") s.Metadata.Encode(e) } + { + e.FieldStart("preview") + e.Str(s.Preview) + } { e.FieldStart("verification") s.Verification.Encode(e) @@ -23517,14 +24178,15 @@ func (s *JettonInfo) encodeFields(e *jx.Encoder) { } } -var jsonFieldsNameOfJettonInfo = [7]string{ +var jsonFieldsNameOfJettonInfo = [8]string{ 0: "mintable", 1: "total_supply", 2: "admin", 3: "metadata", - 4: "verification", - 5: "holders_count", - 6: "score", + 4: "preview", + 5: "verification", + 6: "holders_count", + 7: "score", } // Decode decodes JettonInfo from json. @@ -23580,8 +24242,20 @@ func (s *JettonInfo) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"metadata\"") } - case "verification": + case "preview": requiredBitSet[0] |= 1 << 4 + if err := func() error { + v, err := d.Str() + s.Preview = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"preview\"") + } + case "verification": + requiredBitSet[0] |= 1 << 5 if err := func() error { if err := s.Verification.Decode(d); err != nil { return err @@ -23591,7 +24265,7 @@ func (s *JettonInfo) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"verification\"") } case "holders_count": - requiredBitSet[0] |= 1 << 5 + requiredBitSet[0] |= 1 << 6 if err := func() error { v, err := d.Int32() s.HoldersCount = int32(v) @@ -23622,7 +24296,7 @@ func (s *JettonInfo) Decode(d *jx.Decoder) error { // Validate required fields. var failures []validate.FieldError for i, mask := range [1]uint8{ - 0b00111011, + 0b01111011, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -25436,6 +26110,16 @@ func (s *Message) encodeFields(e *jx.Encoder) { e.FieldStart("value") e.Int64(s.Value) } + { + if s.ValueExtra != nil { + e.FieldStart("value_extra") + e.ArrStart() + for _, elem := range s.ValueExtra { + elem.Encode(e) + } + e.ArrEnd() + } + } { e.FieldStart("fwd_fee") e.Int64(s.FwdFee) @@ -25500,25 +26184,26 @@ func (s *Message) encodeFields(e *jx.Encoder) { } } -var jsonFieldsNameOfMessage = [18]string{ +var jsonFieldsNameOfMessage = [19]string{ 0: "msg_type", 1: "created_lt", 2: "ihr_disabled", 3: "bounce", 4: "bounced", 5: "value", - 6: "fwd_fee", - 7: "ihr_fee", - 8: "destination", - 9: "source", - 10: "import_fee", - 11: "created_at", - 12: "op_code", - 13: "init", - 14: "hash", - 15: "raw_body", - 16: "decoded_op_name", - 17: "decoded_body", + 6: "value_extra", + 7: "fwd_fee", + 8: "ihr_fee", + 9: "destination", + 10: "source", + 11: "import_fee", + 12: "created_at", + 13: "op_code", + 14: "init", + 15: "hash", + 16: "raw_body", + 17: "decoded_op_name", + 18: "decoded_body", } // Decode decodes Message from json. @@ -25600,8 +26285,25 @@ func (s *Message) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"value\"") } + case "value_extra": + if err := func() error { + s.ValueExtra = make([]ExtraCurrency, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem ExtraCurrency + if err := elem.Decode(d); err != nil { + return err + } + s.ValueExtra = append(s.ValueExtra, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"value_extra\"") + } case "fwd_fee": - requiredBitSet[0] |= 1 << 6 + requiredBitSet[0] |= 1 << 7 if err := func() error { v, err := d.Int64() s.FwdFee = int64(v) @@ -25613,7 +26315,7 @@ func (s *Message) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"fwd_fee\"") } case "ihr_fee": - requiredBitSet[0] |= 1 << 7 + requiredBitSet[1] |= 1 << 0 if err := func() error { v, err := d.Int64() s.IhrFee = int64(v) @@ -25645,7 +26347,7 @@ func (s *Message) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"source\"") } case "import_fee": - requiredBitSet[1] |= 1 << 2 + requiredBitSet[1] |= 1 << 3 if err := func() error { v, err := d.Int64() s.ImportFee = int64(v) @@ -25657,7 +26359,7 @@ func (s *Message) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"import_fee\"") } case "created_at": - requiredBitSet[1] |= 1 << 3 + requiredBitSet[1] |= 1 << 4 if err := func() error { v, err := d.Int64() s.CreatedAt = int64(v) @@ -25689,7 +26391,7 @@ func (s *Message) Decode(d *jx.Decoder) error { return errors.Wrap(err, "decode field \"init\"") } case "hash": - requiredBitSet[1] |= 1 << 6 + requiredBitSet[1] |= 1 << 7 if err := func() error { v, err := d.Str() s.Hash = string(v) @@ -25741,8 +26443,8 @@ func (s *Message) Decode(d *jx.Decoder) error { // Validate required fields. var failures []validate.FieldError for i, mask := range [3]uint8{ - 0b11111111, - 0b01001100, + 0b10111111, + 0b10011001, 0b00000000, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { @@ -29453,6 +30155,39 @@ func (s *OptBlockchainConfig43) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode encodes BlockchainConfig45 as json. +func (o OptBlockchainConfig45) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes BlockchainConfig45 from json. +func (o *OptBlockchainConfig45) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptBlockchainConfig45 to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptBlockchainConfig45) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptBlockchainConfig45) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode encodes BlockchainConfig5 as json. func (o OptBlockchainConfig5) Encode(e *jx.Encoder) { if !o.Set { @@ -30380,6 +31115,39 @@ func (s *OptEncryptedComment) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode encodes ExtraCurrencyTransferAction as json. +func (o OptExtraCurrencyTransferAction) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes ExtraCurrencyTransferAction from json. +func (o *OptExtraCurrencyTransferAction) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptExtraCurrencyTransferAction to nil") + } + o.Set = true + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptExtraCurrencyTransferAction) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptExtraCurrencyTransferAction) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode encodes GetAccountsReq as json. func (o OptGetAccountsReq) Encode(e *jx.Encoder) { if !o.Set { @@ -31179,6 +31947,40 @@ func (s *OptSale) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode encodes SendBlockchainMessageReqMeta as json. +func (o OptSendBlockchainMessageReqMeta) Encode(e *jx.Encoder) { + if !o.Set { + return + } + o.Value.Encode(e) +} + +// Decode decodes SendBlockchainMessageReqMeta from json. +func (o *OptSendBlockchainMessageReqMeta) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptSendBlockchainMessageReqMeta to nil") + } + o.Set = true + o.Value = make(SendBlockchainMessageReqMeta) + if err := o.Value.Decode(d); err != nil { + return err + } + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptSendBlockchainMessageReqMeta) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptSendBlockchainMessageReqMeta) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode encodes SmartContractAction as json. func (o OptSmartContractAction) Encode(e *jx.Encoder) { if !o.Set { @@ -33615,11 +34417,18 @@ func (s *SendBlockchainMessageReq) encodeFields(e *jx.Encoder) { e.ArrEnd() } } + { + if s.Meta.Set { + e.FieldStart("meta") + s.Meta.Encode(e) + } + } } -var jsonFieldsNameOfSendBlockchainMessageReq = [2]string{ +var jsonFieldsNameOfSendBlockchainMessageReq = [3]string{ 0: "boc", 1: "batch", + 2: "meta", } // Decode decodes SendBlockchainMessageReq from json. @@ -33659,6 +34468,16 @@ func (s *SendBlockchainMessageReq) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"batch\"") } + case "meta": + if err := func() error { + s.Meta.Reset() + if err := s.Meta.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"meta\"") + } default: return d.Skip() } @@ -33683,6 +34502,62 @@ func (s *SendBlockchainMessageReq) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s SendBlockchainMessageReqMeta) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields implements json.Marshaler. +func (s SendBlockchainMessageReqMeta) encodeFields(e *jx.Encoder) { + for k, elem := range s { + e.FieldStart(k) + + e.Str(elem) + } +} + +// Decode decodes SendBlockchainMessageReqMeta from json. +func (s *SendBlockchainMessageReqMeta) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SendBlockchainMessageReqMeta to nil") + } + m := s.init() + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + var elem string + if err := func() error { + v, err := d.Str() + elem = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrapf(err, "decode field %q", k) + } + m[string(k)] = elem + return nil + }); err != nil { + return errors.Wrap(err, "decode SendBlockchainMessageReqMeta") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s SendBlockchainMessageReqMeta) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SendBlockchainMessageReqMeta) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *SendRawMessageOK) Encode(e *jx.Encoder) { e.ObjStart() diff --git a/pkg/oas/oas_parameters_gen.go b/pkg/oas/oas_parameters_gen.go index bee88e4d..b47eb857 100644 --- a/pkg/oas/oas_parameters_gen.go +++ b/pkg/oas/oas_parameters_gen.go @@ -709,6 +709,7 @@ type ExecGetMethodForBlockchainAccountParams struct { // Contract get method name. MethodName string Args []string + FixOrder OptBool } func unpackExecGetMethodForBlockchainAccountParams(packed middleware.Parameters) (params ExecGetMethodForBlockchainAccountParams) { @@ -735,6 +736,15 @@ func unpackExecGetMethodForBlockchainAccountParams(packed middleware.Parameters) params.Args = v.([]string) } } + { + key := middleware.ParameterKey{ + Name: "fix_order", + In: "query", + } + if v, ok := packed[key]; ok { + params.FixOrder = v.(OptBool) + } + } return params } @@ -873,6 +883,52 @@ func decodeExecGetMethodForBlockchainAccountParams(args [2]string, argsEscaped b Err: err, } } + // Set default value for query: fix_order. + { + val := bool(true) + params.FixOrder.SetTo(val) + } + // Decode query: fix_order. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "fix_order", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotFixOrderVal bool + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToBool(val) + if err != nil { + return err + } + + paramsDotFixOrderVal = c + return nil + }(); err != nil { + return err + } + params.FixOrder.SetTo(paramsDotFixOrderVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "fix_order", + In: "query", + Err: err, + } + } return params, nil } @@ -11247,61 +11303,6 @@ func decodeGetTraceParams(args [1]string, argsEscaped bool, r *http.Request) (pa return params, nil } -// GetWalletBackupParams is parameters of getWalletBackup operation. -type GetWalletBackupParams struct { - XTonConnectAuth string -} - -func unpackGetWalletBackupParams(packed middleware.Parameters) (params GetWalletBackupParams) { - { - key := middleware.ParameterKey{ - Name: "X-TonConnect-Auth", - In: "header", - } - params.XTonConnectAuth = packed[key].(string) - } - return params -} - -func decodeGetWalletBackupParams(args [0]string, argsEscaped bool, r *http.Request) (params GetWalletBackupParams, _ error) { - h := uri.NewHeaderDecoder(r.Header) - // Decode header: X-TonConnect-Auth. - if err := func() error { - cfg := uri.HeaderParameterDecodingConfig{ - Name: "X-TonConnect-Auth", - Explode: false, - } - if err := h.HasParam(cfg); err == nil { - if err := h.DecodeParam(cfg, func(d uri.Decoder) error { - val, err := d.DecodeValue() - if err != nil { - return err - } - - c, err := conv.ToString(val) - if err != nil { - return err - } - - params.XTonConnectAuth = c - return nil - }); err != nil { - return err - } - } else { - return validate.ErrFieldRequired - } - return nil - }(); err != nil { - return params, &ogenerrors.DecodeParamError{ - Name: "X-TonConnect-Auth", - In: "header", - Err: err, - } - } - return params, nil -} - // GetWalletsByPublicKeyParams is parameters of getWalletsByPublicKey operation. type GetWalletsByPublicKeyParams struct { PublicKey string @@ -11505,58 +11506,3 @@ func decodeSearchAccountsParams(args [0]string, argsEscaped bool, r *http.Reques } return params, nil } - -// SetWalletBackupParams is parameters of setWalletBackup operation. -type SetWalletBackupParams struct { - XTonConnectAuth string -} - -func unpackSetWalletBackupParams(packed middleware.Parameters) (params SetWalletBackupParams) { - { - key := middleware.ParameterKey{ - Name: "X-TonConnect-Auth", - In: "header", - } - params.XTonConnectAuth = packed[key].(string) - } - return params -} - -func decodeSetWalletBackupParams(args [0]string, argsEscaped bool, r *http.Request) (params SetWalletBackupParams, _ error) { - h := uri.NewHeaderDecoder(r.Header) - // Decode header: X-TonConnect-Auth. - if err := func() error { - cfg := uri.HeaderParameterDecodingConfig{ - Name: "X-TonConnect-Auth", - Explode: false, - } - if err := h.HasParam(cfg); err == nil { - if err := h.DecodeParam(cfg, func(d uri.Decoder) error { - val, err := d.DecodeValue() - if err != nil { - return err - } - - c, err := conv.ToString(val) - if err != nil { - return err - } - - params.XTonConnectAuth = c - return nil - }); err != nil { - return err - } - } else { - return validate.ErrFieldRequired - } - return nil - }(); err != nil { - return params, &ogenerrors.DecodeParamError{ - Name: "X-TonConnect-Auth", - In: "header", - Err: err, - } - } - return params, nil -} diff --git a/pkg/oas/oas_request_decoders_gen.go b/pkg/oas/oas_request_decoders_gen.go index e68c4099..173b76b8 100644 --- a/pkg/oas/oas_request_decoders_gen.go +++ b/pkg/oas/oas_request_decoders_gen.go @@ -989,40 +989,6 @@ func (s *Server) decodeSendRawMessageRequest(r *http.Request) ( } } -func (s *Server) decodeSetWalletBackupRequest(r *http.Request) ( - req SetWalletBackupReq, - close func() error, - rerr error, -) { - var closers []func() error - close = func() error { - var merr error - // Close in reverse order, to match defer behavior. - for i := len(closers) - 1; i >= 0; i-- { - c := closers[i] - merr = multierr.Append(merr, c()) - } - return merr - } - defer func() { - if rerr != nil { - rerr = multierr.Append(rerr, close()) - } - }() - ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) - if err != nil { - return req, close, errors.Wrap(err, "parse media type") - } - switch { - case ct == "application/octet-stream": - reader := r.Body - request := SetWalletBackupReq{Data: reader} - return request, close, nil - default: - return req, close, validate.InvalidContentType(ct) - } -} - func (s *Server) decodeTonConnectProofRequest(r *http.Request) ( req *TonConnectProofReq, close func() error, diff --git a/pkg/oas/oas_response_encoders_gen.go b/pkg/oas/oas_response_encoders_gen.go index 9d126200..faa1e2ab 100644 --- a/pkg/oas/oas_response_encoders_gen.go +++ b/pkg/oas/oas_response_encoders_gen.go @@ -3,6 +3,7 @@ package oas import ( + "io" "net/http" "github.com/go-faster/errors" @@ -986,6 +987,35 @@ func encodeGetNftItemsByAddressesResponse(response *NftItems, w http.ResponseWri return nil } +func encodeGetOpenapiJsonResponse(response jx.Raw, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + if len(response) != 0 { + e.Raw(response) + } + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + +func encodeGetOpenapiYmlResponse(response GetOpenapiYmlOK, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/x-yaml") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + writer := w + if _, err := io.Copy(writer, response); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + func encodeGetOutMsgQueueSizesResponse(response *GetOutMsgQueueSizesOK, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) @@ -1322,20 +1352,6 @@ func encodeGetTraceResponse(response *Trace, w http.ResponseWriter, span trace.S return nil } -func encodeGetWalletBackupResponse(response *GetWalletBackupOK, w http.ResponseWriter, span trace.Span) error { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(200) - span.SetStatus(codes.Ok, http.StatusText(200)) - - e := new(jx.Encoder) - response.Encode(e) - if _, err := e.WriteTo(w); err != nil { - return errors.Wrap(err, "write") - } - - return nil -} - func encodeGetWalletsByPublicKeyResponse(response *Accounts, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) @@ -1392,13 +1408,6 @@ func encodeSendRawMessageResponse(response *SendRawMessageOK, w http.ResponseWri return nil } -func encodeSetWalletBackupResponse(response *SetWalletBackupOK, w http.ResponseWriter, span trace.Span) error { - w.WriteHeader(200) - span.SetStatus(codes.Ok, http.StatusText(200)) - - return nil -} - func encodeStatusResponse(response *ServiceStatus, w http.ResponseWriter, span trace.Span) error { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(200) diff --git a/pkg/oas/oas_router_gen.go b/pkg/oas/oas_router_gen.go index 98f702c7..9770d744 100644 --- a/pkg/oas/oas_router_gen.go +++ b/pkg/oas/oas_router_gen.go @@ -2657,6 +2657,63 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { elem = origElem } + elem = origElem + case 'o': // Prefix: "openapi." + origElem := elem + if l := len("openapi."); len(elem) >= l && elem[0:l] == "openapi." { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'j': // Prefix: "json" + origElem := elem + if l := len("json"); len(elem) >= l && elem[0:l] == "json" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleGetOpenapiJsonRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + + elem = origElem + case 'y': // Prefix: "yml" + origElem := elem + if l := len("yml"); len(elem) >= l && elem[0:l] == "yml" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "GET": + s.handleGetOpenapiYmlRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + + elem = origElem + } + elem = origElem case 'p': // Prefix: "pubkeys/" origElem := elem @@ -3168,29 +3225,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - elem = origElem - case 'b': // Prefix: "backup" - origElem := elem - if l := len("backup"); len(elem) >= l && elem[0:l] == "backup" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - // Leaf node. - switch r.Method { - case "GET": - s.handleGetWalletBackupRequest([0]string{}, elemIsEscaped, w, r) - case "PUT": - s.handleSetWalletBackupRequest([0]string{}, elemIsEscaped, w, r) - default: - s.notAllowed(w, r, "GET,PUT") - } - - return - } - elem = origElem case 'e': // Prefix: "emulate" origElem := elem @@ -6156,6 +6190,71 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { elem = origElem } + elem = origElem + case 'o': // Prefix: "openapi." + origElem := elem + if l := len("openapi."); len(elem) >= l && elem[0:l] == "openapi." { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'j': // Prefix: "json" + origElem := elem + if l := len("json"); len(elem) >= l && elem[0:l] == "json" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "GET": + // Leaf: GetOpenapiJson + r.name = "GetOpenapiJson" + r.summary = "" + r.operationID = "getOpenapiJson" + r.pathPattern = "/v2/openapi.json" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + elem = origElem + case 'y': // Prefix: "yml" + origElem := elem + if l := len("yml"); len(elem) >= l && elem[0:l] == "yml" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "GET": + // Leaf: GetOpenapiYml + r.name = "GetOpenapiYml" + r.summary = "" + r.operationID = "getOpenapiYml" + r.pathPattern = "/v2/openapi.yml" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + elem = origElem + } + elem = origElem case 'p': // Prefix: "pubkeys/" origElem := elem @@ -6717,40 +6816,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { } } - elem = origElem - case 'b': // Prefix: "backup" - origElem := elem - if l := len("backup"); len(elem) >= l && elem[0:l] == "backup" { - elem = elem[l:] - } else { - break - } - - if len(elem) == 0 { - switch method { - case "GET": - // Leaf: GetWalletBackup - r.name = "GetWalletBackup" - r.summary = "" - r.operationID = "getWalletBackup" - r.pathPattern = "/v2/wallet/backup" - r.args = args - r.count = 0 - return r, true - case "PUT": - // Leaf: SetWalletBackup - r.name = "SetWalletBackup" - r.summary = "" - r.operationID = "setWalletBackup" - r.pathPattern = "/v2/wallet/backup" - r.args = args - r.count = 0 - return r, true - default: - return - } - } - elem = origElem case 'e': // Prefix: "emulate" origElem := elem diff --git a/pkg/oas/oas_schemas_gen.go b/pkg/oas/oas_schemas_gen.go index 4e862ca1..d6ffdf5f 100644 --- a/pkg/oas/oas_schemas_gen.go +++ b/pkg/oas/oas_schemas_gen.go @@ -65,8 +65,9 @@ func (s *AccStatusChange) UnmarshalText(data []byte) error { // Ref: #/components/schemas/Account type Account struct { - Address string `json:"address"` - Balance int64 `json:"balance"` + Address string `json:"address"` + Balance int64 `json:"balance"` + ExtraBalance []ExtraCurrency `json:"extra_balance"` // {'USD': 1, 'IDR': 1000}. CurrenciesBalance OptAccountCurrenciesBalance `json:"currencies_balance"` // Unix timestamp. @@ -92,6 +93,11 @@ func (s *Account) GetBalance() int64 { return s.Balance } +// GetExtraBalance returns the value of ExtraBalance. +func (s *Account) GetExtraBalance() []ExtraCurrency { + return s.ExtraBalance +} + // GetCurrenciesBalance returns the value of CurrenciesBalance. func (s *Account) GetCurrenciesBalance() OptAccountCurrenciesBalance { return s.CurrenciesBalance @@ -157,6 +163,11 @@ func (s *Account) SetBalance(val int64) { s.Balance = val } +// SetExtraBalance sets the value of ExtraBalance. +func (s *Account) SetExtraBalance(val []ExtraCurrency) { + s.ExtraBalance = val +} + // SetCurrenciesBalance sets the value of CurrenciesBalance. func (s *Account) SetCurrenciesBalance(val OptAccountCurrenciesBalance) { s.CurrenciesBalance = val @@ -648,6 +659,7 @@ type Action struct { Type ActionType `json:"type"` Status ActionStatus `json:"status"` TonTransfer OptTonTransferAction `json:"TonTransfer"` + ExtraCurrencyTransfer OptExtraCurrencyTransferAction `json:"ExtraCurrencyTransfer"` ContractDeploy OptContractDeployAction `json:"ContractDeploy"` JettonTransfer OptJettonTransferAction `json:"JettonTransfer"` JettonBurn OptJettonBurnAction `json:"JettonBurn"` @@ -686,6 +698,11 @@ func (s *Action) GetTonTransfer() OptTonTransferAction { return s.TonTransfer } +// GetExtraCurrencyTransfer returns the value of ExtraCurrencyTransfer. +func (s *Action) GetExtraCurrencyTransfer() OptExtraCurrencyTransferAction { + return s.ExtraCurrencyTransfer +} + // GetContractDeploy returns the value of ContractDeploy. func (s *Action) GetContractDeploy() OptContractDeployAction { return s.ContractDeploy @@ -806,6 +823,11 @@ func (s *Action) SetTonTransfer(val OptTonTransferAction) { s.TonTransfer = val } +// SetExtraCurrencyTransfer sets the value of ExtraCurrencyTransfer. +func (s *Action) SetExtraCurrencyTransfer(val OptExtraCurrencyTransferAction) { + s.ExtraCurrencyTransfer = val +} + // SetContractDeploy sets the value of ContractDeploy. func (s *Action) SetContractDeploy(val OptContractDeployAction) { s.ContractDeploy = val @@ -1110,6 +1132,7 @@ type ActionType string const ( ActionTypeTonTransfer ActionType = "TonTransfer" + ActionTypeExtraCurrencyTransfer ActionType = "ExtraCurrencyTransfer" ActionTypeJettonTransfer ActionType = "JettonTransfer" ActionTypeJettonBurn ActionType = "JettonBurn" ActionTypeJettonMint ActionType = "JettonMint" @@ -1136,6 +1159,7 @@ const ( func (ActionType) AllValues() []ActionType { return []ActionType{ ActionTypeTonTransfer, + ActionTypeExtraCurrencyTransfer, ActionTypeJettonTransfer, ActionTypeJettonBurn, ActionTypeJettonMint, @@ -1164,6 +1188,8 @@ func (s ActionType) MarshalText() ([]byte, error) { switch s { case ActionTypeTonTransfer: return []byte(s), nil + case ActionTypeExtraCurrencyTransfer: + return []byte(s), nil case ActionTypeJettonTransfer: return []byte(s), nil case ActionTypeJettonBurn: @@ -1215,6 +1241,9 @@ func (s *ActionType) UnmarshalText(data []byte) error { case ActionTypeTonTransfer: *s = ActionTypeTonTransfer return nil + case ActionTypeExtraCurrencyTransfer: + *s = ActionTypeExtraCurrencyTransfer + return nil case ActionTypeJettonTransfer: *s = ActionTypeJettonTransfer return nil @@ -2485,6 +2514,8 @@ type BlockchainConfig struct { R43 OptBlockchainConfig43 `json:"43"` // Suspended accounts. R44 BlockchainConfig44 `json:"44"` + // Precompiled contracts. + R45 OptBlockchainConfig45 `json:"45"` // Bridge parameters for wrapping TON in other networks. R71 OptBlockchainConfig71 `json:"71"` // Bridge parameters for wrapping TON in other networks. @@ -2689,6 +2720,11 @@ func (s *BlockchainConfig) GetR44() BlockchainConfig44 { return s.R44 } +// GetR45 returns the value of R45. +func (s *BlockchainConfig) GetR45() OptBlockchainConfig45 { + return s.R45 +} + // GetR71 returns the value of R71. func (s *BlockchainConfig) GetR71() OptBlockchainConfig71 { return s.R71 @@ -2909,6 +2945,11 @@ func (s *BlockchainConfig) SetR44(val BlockchainConfig44) { s.R44 = val } +// SetR45 sets the value of R45. +func (s *BlockchainConfig) SetR45(val OptBlockchainConfig45) { + s.R45 = val +} + // SetR71 sets the value of R71. func (s *BlockchainConfig) SetR71(val OptBlockchainConfig71) { s.R71 = val @@ -3638,6 +3679,46 @@ func (s *BlockchainConfig44) SetSuspendedUntil(val int) { s.SuspendedUntil = val } +// Precompiled contracts. +type BlockchainConfig45 struct { + Contracts []BlockchainConfig45ContractsItem `json:"contracts"` +} + +// GetContracts returns the value of Contracts. +func (s *BlockchainConfig45) GetContracts() []BlockchainConfig45ContractsItem { + return s.Contracts +} + +// SetContracts sets the value of Contracts. +func (s *BlockchainConfig45) SetContracts(val []BlockchainConfig45ContractsItem) { + s.Contracts = val +} + +type BlockchainConfig45ContractsItem struct { + CodeHash string `json:"code_hash"` + GasUsage int64 `json:"gas_usage"` +} + +// GetCodeHash returns the value of CodeHash. +func (s *BlockchainConfig45ContractsItem) GetCodeHash() string { + return s.CodeHash +} + +// GetGasUsage returns the value of GasUsage. +func (s *BlockchainConfig45ContractsItem) GetGasUsage() int64 { + return s.GasUsage +} + +// SetCodeHash sets the value of CodeHash. +func (s *BlockchainConfig45ContractsItem) SetCodeHash(val string) { + s.CodeHash = val +} + +// SetGasUsage sets the value of GasUsage. +func (s *BlockchainConfig45ContractsItem) SetGasUsage(val int64) { + s.GasUsage = val +} + type BlockchainConfig5 struct { BlackholeAddr OptString `json:"blackhole_addr"` FeeBurnNom int64 `json:"fee_burn_nom"` @@ -5042,6 +5123,43 @@ func (s *DomainRenewAction) SetRenewer(val AccountAddress) { s.Renewer = val } +// Ref: #/components/schemas/EcPreview +type EcPreview struct { + Symbol string `json:"symbol"` + Decimals int `json:"decimals"` + Image string `json:"image"` +} + +// GetSymbol returns the value of Symbol. +func (s *EcPreview) GetSymbol() string { + return s.Symbol +} + +// GetDecimals returns the value of Decimals. +func (s *EcPreview) GetDecimals() int { + return s.Decimals +} + +// GetImage returns the value of Image. +func (s *EcPreview) GetImage() string { + return s.Image +} + +// SetSymbol sets the value of Symbol. +func (s *EcPreview) SetSymbol(val string) { + s.Symbol = val +} + +// SetDecimals sets the value of Decimals. +func (s *EcPreview) SetDecimals(val int) { + s.Decimals = val +} + +// SetImage sets the value of Image. +func (s *EcPreview) SetImage(val string) { + s.Image = val +} + // Ref: #/components/schemas/ElectionsDepositStakeAction type ElectionsDepositStakeAction struct { Amount int64 `json:"amount"` @@ -5214,7 +5332,8 @@ func (s *EncryptedComment) SetCipherText(val string) { } type Error struct { - Error string `json:"error"` + Error string `json:"error"` + ErrorCode OptInt64 `json:"error_code"` } // GetError returns the value of Error. @@ -5222,11 +5341,21 @@ func (s *Error) GetError() string { return s.Error } +// GetErrorCode returns the value of ErrorCode. +func (s *Error) GetErrorCode() OptInt64 { + return s.ErrorCode +} + // SetError sets the value of Error. func (s *Error) SetError(val string) { s.Error = val } +// SetErrorCode sets the value of ErrorCode. +func (s *Error) SetErrorCode(val OptInt64) { + s.ErrorCode = val +} + // ErrorStatusCode wraps Error with StatusCode. type ErrorStatusCode struct { StatusCode int @@ -5336,6 +5465,125 @@ func (s *Event) SetInProgress(val bool) { s.InProgress = val } +// Ref: #/components/schemas/ExtraCurrency +type ExtraCurrency struct { + ID int32 `json:"id"` + Amount string `json:"amount"` + Name OptString `json:"name"` + Decimals int `json:"decimals"` +} + +// GetID returns the value of ID. +func (s *ExtraCurrency) GetID() int32 { + return s.ID +} + +// GetAmount returns the value of Amount. +func (s *ExtraCurrency) GetAmount() string { + return s.Amount +} + +// GetName returns the value of Name. +func (s *ExtraCurrency) GetName() OptString { + return s.Name +} + +// GetDecimals returns the value of Decimals. +func (s *ExtraCurrency) GetDecimals() int { + return s.Decimals +} + +// SetID sets the value of ID. +func (s *ExtraCurrency) SetID(val int32) { + s.ID = val +} + +// SetAmount sets the value of Amount. +func (s *ExtraCurrency) SetAmount(val string) { + s.Amount = val +} + +// SetName sets the value of Name. +func (s *ExtraCurrency) SetName(val OptString) { + s.Name = val +} + +// SetDecimals sets the value of Decimals. +func (s *ExtraCurrency) SetDecimals(val int) { + s.Decimals = val +} + +// Ref: #/components/schemas/ExtraCurrencyTransferAction +type ExtraCurrencyTransferAction struct { + Sender AccountAddress `json:"sender"` + Recipient AccountAddress `json:"recipient"` + // Amount in quanta of tokens. + Amount string `json:"amount"` + Comment OptString `json:"comment"` + EncryptedComment OptEncryptedComment `json:"encrypted_comment"` + Currency EcPreview `json:"currency"` +} + +// GetSender returns the value of Sender. +func (s *ExtraCurrencyTransferAction) GetSender() AccountAddress { + return s.Sender +} + +// GetRecipient returns the value of Recipient. +func (s *ExtraCurrencyTransferAction) GetRecipient() AccountAddress { + return s.Recipient +} + +// GetAmount returns the value of Amount. +func (s *ExtraCurrencyTransferAction) GetAmount() string { + return s.Amount +} + +// GetComment returns the value of Comment. +func (s *ExtraCurrencyTransferAction) GetComment() OptString { + return s.Comment +} + +// GetEncryptedComment returns the value of EncryptedComment. +func (s *ExtraCurrencyTransferAction) GetEncryptedComment() OptEncryptedComment { + return s.EncryptedComment +} + +// GetCurrency returns the value of Currency. +func (s *ExtraCurrencyTransferAction) GetCurrency() EcPreview { + return s.Currency +} + +// SetSender sets the value of Sender. +func (s *ExtraCurrencyTransferAction) SetSender(val AccountAddress) { + s.Sender = val +} + +// SetRecipient sets the value of Recipient. +func (s *ExtraCurrencyTransferAction) SetRecipient(val AccountAddress) { + s.Recipient = val +} + +// SetAmount sets the value of Amount. +func (s *ExtraCurrencyTransferAction) SetAmount(val string) { + s.Amount = val +} + +// SetComment sets the value of Comment. +func (s *ExtraCurrencyTransferAction) SetComment(val OptString) { + s.Comment = val +} + +// SetEncryptedComment sets the value of EncryptedComment. +func (s *ExtraCurrencyTransferAction) SetEncryptedComment(val OptEncryptedComment) { + s.EncryptedComment = val +} + +// SetCurrency sets the value of Currency. +func (s *ExtraCurrencyTransferAction) SetCurrency(val EcPreview) { + s.Currency = val +} + // Ref: #/components/schemas/FoundAccounts type FoundAccounts struct { Addresses []FoundAccountsAddressesItem `json:"addresses"` @@ -5915,6 +6163,20 @@ func (s *GetNftItemsByAddressesReq) SetAccountIds(val []string) { s.AccountIds = val } +type GetOpenapiYmlOK struct { + Data io.Reader +} + +// Read reads data from the Data reader. +// +// Kept to satisfy the io.Reader interface. +func (s GetOpenapiYmlOK) Read(p []byte) (n int, err error) { + if s.Data == nil { + return 0, io.EOF + } + return s.Data.Read(p) +} + type GetOutMsgQueueSizesOK struct { ExtMsgQueueSizeLimit uint32 `json:"ext_msg_queue_size_limit"` Shards []GetOutMsgQueueSizesOKShardsItem `json:"shards"` @@ -6945,20 +7207,6 @@ func (s *GetTonConnectPayloadOK) SetPayload(val string) { s.Payload = val } -type GetWalletBackupOK struct { - Dump string `json:"dump"` -} - -// GetDump returns the value of Dump. -func (s *GetWalletBackupOK) GetDump() string { - return s.Dump -} - -// SetDump sets the value of Dump. -func (s *GetWalletBackupOK) SetDump(val string) { - s.Dump = val -} - // Ref: #/components/schemas/ImagePreview type ImagePreview struct { Resolution string `json:"resolution"` @@ -7715,6 +7963,7 @@ type JettonInfo struct { TotalSupply string `json:"total_supply"` Admin OptAccountAddress `json:"admin"` Metadata JettonMetadata `json:"metadata"` + Preview string `json:"preview"` Verification JettonVerificationType `json:"verification"` HoldersCount int32 `json:"holders_count"` Score OptInt32 `json:"score"` @@ -7740,6 +7989,11 @@ func (s *JettonInfo) GetMetadata() JettonMetadata { return s.Metadata } +// GetPreview returns the value of Preview. +func (s *JettonInfo) GetPreview() string { + return s.Preview +} + // GetVerification returns the value of Verification. func (s *JettonInfo) GetVerification() JettonVerificationType { return s.Verification @@ -7775,6 +8029,11 @@ func (s *JettonInfo) SetMetadata(val JettonMetadata) { s.Metadata = val } +// SetPreview sets the value of Preview. +func (s *JettonInfo) SetPreview(val string) { + s.Preview = val +} + // SetVerification sets the value of Verification. func (s *JettonInfo) SetVerification(val JettonVerificationType) { s.Verification = val @@ -7792,10 +8051,13 @@ func (s *JettonInfo) SetScore(val OptInt32) { // Ref: #/components/schemas/JettonMetadata type JettonMetadata struct { - Address string `json:"address"` - Name string `json:"name"` - Symbol string `json:"symbol"` - Decimals string `json:"decimals"` + Address string `json:"address"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimals string `json:"decimals"` + // This field currently returns a cached image URL (e.g., "https://cache.tonapi.io/images/jetton. + // jpg"). In the future, this will be replaced with the original URL from the metadata. The cached + // image is already available in the `preview` field of `JettonInfo` and will remain there. Image OptString `json:"image"` Description OptString `json:"description"` Social []string `json:"social"` @@ -8489,6 +8751,7 @@ type Message struct { Bounce bool `json:"bounce"` Bounced bool `json:"bounced"` Value int64 `json:"value"` + ValueExtra []ExtraCurrency `json:"value_extra"` FwdFee int64 `json:"fwd_fee"` IhrFee int64 `json:"ihr_fee"` Destination OptAccountAddress `json:"destination"` @@ -8534,6 +8797,11 @@ func (s *Message) GetValue() int64 { return s.Value } +// GetValueExtra returns the value of ValueExtra. +func (s *Message) GetValueExtra() []ExtraCurrency { + return s.ValueExtra +} + // GetFwdFee returns the value of FwdFee. func (s *Message) GetFwdFee() int64 { return s.FwdFee @@ -8624,6 +8892,11 @@ func (s *Message) SetValue(val int64) { s.Value = val } +// SetValueExtra sets the value of ValueExtra. +func (s *Message) SetValueExtra(val []ExtraCurrency) { + s.ValueExtra = val +} + // SetFwdFee sets the value of FwdFee. func (s *Message) SetFwdFee(val int64) { s.FwdFee = val @@ -10954,6 +11227,52 @@ func (o OptBlockchainConfig43) Or(d BlockchainConfig43) BlockchainConfig43 { return d } +// NewOptBlockchainConfig45 returns new OptBlockchainConfig45 with value set to v. +func NewOptBlockchainConfig45(v BlockchainConfig45) OptBlockchainConfig45 { + return OptBlockchainConfig45{ + Value: v, + Set: true, + } +} + +// OptBlockchainConfig45 is optional BlockchainConfig45. +type OptBlockchainConfig45 struct { + Value BlockchainConfig45 + Set bool +} + +// IsSet returns true if OptBlockchainConfig45 was set. +func (o OptBlockchainConfig45) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptBlockchainConfig45) Reset() { + var v BlockchainConfig45 + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptBlockchainConfig45) SetTo(v BlockchainConfig45) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptBlockchainConfig45) Get() (v BlockchainConfig45, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptBlockchainConfig45) Or(d BlockchainConfig45) BlockchainConfig45 { + if v, ok := o.Get(); ok { + return v + } + return d +} + // NewOptBlockchainConfig5 returns new OptBlockchainConfig5 with value set to v. func NewOptBlockchainConfig5(v BlockchainConfig5) OptBlockchainConfig5 { return OptBlockchainConfig5{ @@ -12242,6 +12561,52 @@ func (o OptEncryptedComment) Or(d EncryptedComment) EncryptedComment { return d } +// NewOptExtraCurrencyTransferAction returns new OptExtraCurrencyTransferAction with value set to v. +func NewOptExtraCurrencyTransferAction(v ExtraCurrencyTransferAction) OptExtraCurrencyTransferAction { + return OptExtraCurrencyTransferAction{ + Value: v, + Set: true, + } +} + +// OptExtraCurrencyTransferAction is optional ExtraCurrencyTransferAction. +type OptExtraCurrencyTransferAction struct { + Value ExtraCurrencyTransferAction + Set bool +} + +// IsSet returns true if OptExtraCurrencyTransferAction was set. +func (o OptExtraCurrencyTransferAction) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptExtraCurrencyTransferAction) Reset() { + var v ExtraCurrencyTransferAction + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptExtraCurrencyTransferAction) SetTo(v ExtraCurrencyTransferAction) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptExtraCurrencyTransferAction) Get() (v ExtraCurrencyTransferAction, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptExtraCurrencyTransferAction) Or(d ExtraCurrencyTransferAction) ExtraCurrencyTransferAction { + if v, ok := o.Get(); ok { + return v + } + return d +} + // NewOptGetAccountsReq returns new OptGetAccountsReq with value set to v. func NewOptGetAccountsReq(v GetAccountsReq) OptGetAccountsReq { return OptGetAccountsReq{ @@ -13392,6 +13757,52 @@ func (o OptSale) Or(d Sale) Sale { return d } +// NewOptSendBlockchainMessageReqMeta returns new OptSendBlockchainMessageReqMeta with value set to v. +func NewOptSendBlockchainMessageReqMeta(v SendBlockchainMessageReqMeta) OptSendBlockchainMessageReqMeta { + return OptSendBlockchainMessageReqMeta{ + Value: v, + Set: true, + } +} + +// OptSendBlockchainMessageReqMeta is optional SendBlockchainMessageReqMeta. +type OptSendBlockchainMessageReqMeta struct { + Value SendBlockchainMessageReqMeta + Set bool +} + +// IsSet returns true if OptSendBlockchainMessageReqMeta was set. +func (o OptSendBlockchainMessageReqMeta) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptSendBlockchainMessageReqMeta) Reset() { + var v SendBlockchainMessageReqMeta + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptSendBlockchainMessageReqMeta) SetTo(v SendBlockchainMessageReqMeta) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptSendBlockchainMessageReqMeta) Get() (v SendBlockchainMessageReqMeta, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptSendBlockchainMessageReqMeta) Or(d SendBlockchainMessageReqMeta) SendBlockchainMessageReqMeta { + if v, ok := o.Get(); ok { + return v + } + return d +} + // NewOptSmartContractAction returns new OptSmartContractAction with value set to v. func NewOptSmartContractAction(v SmartContractAction) OptSmartContractAction { return OptSmartContractAction{ @@ -14816,8 +15227,9 @@ func (s *Sale) SetPrice(val Price) { type SendBlockchainMessageOK struct{} type SendBlockchainMessageReq struct { - Boc OptString `json:"boc"` - Batch []string `json:"batch"` + Boc OptString `json:"boc"` + Batch []string `json:"batch"` + Meta OptSendBlockchainMessageReqMeta `json:"meta"` } // GetBoc returns the value of Boc. @@ -14830,6 +15242,11 @@ func (s *SendBlockchainMessageReq) GetBatch() []string { return s.Batch } +// GetMeta returns the value of Meta. +func (s *SendBlockchainMessageReq) GetMeta() OptSendBlockchainMessageReqMeta { + return s.Meta +} + // SetBoc sets the value of Boc. func (s *SendBlockchainMessageReq) SetBoc(val OptString) { s.Boc = val @@ -14840,6 +15257,22 @@ func (s *SendBlockchainMessageReq) SetBatch(val []string) { s.Batch = val } +// SetMeta sets the value of Meta. +func (s *SendBlockchainMessageReq) SetMeta(val OptSendBlockchainMessageReqMeta) { + s.Meta = val +} + +type SendBlockchainMessageReqMeta map[string]string + +func (s *SendBlockchainMessageReqMeta) init() SendBlockchainMessageReqMeta { + m := *s + if m == nil { + m = map[string]string{} + *s = m + } + return m +} + type SendRawMessageOK struct { Code int32 `json:"code"` } @@ -14920,23 +15353,6 @@ func (s *ServiceStatus) SetLastKnownMasterchainSeqno(val int32) { s.LastKnownMasterchainSeqno = val } -// SetWalletBackupOK is response for SetWalletBackup operation. -type SetWalletBackupOK struct{} - -type SetWalletBackupReq struct { - Data io.Reader -} - -// Read reads data from the Data reader. -// -// Kept to satisfy the io.Reader interface. -func (s SetWalletBackupReq) Read(p []byte) (n int, err error) { - if s.Data == nil { - return 0, io.EOF - } - return s.Data.Read(p) -} - // Ref: #/components/schemas/SignRawMessage type SignRawMessage struct { Address string `json:"address"` diff --git a/pkg/oas/oas_server_gen.go b/pkg/oas/oas_server_gen.go index 67261348..c06b8774 100644 --- a/pkg/oas/oas_server_gen.go +++ b/pkg/oas/oas_server_gen.go @@ -4,6 +4,8 @@ package oas import ( "context" + + "github.com/go-faster/jx" ) // Handler handles operations described by OpenAPI v3 specification. @@ -444,6 +446,18 @@ type Handler interface { // // POST /v2/nfts/_bulk GetNftItemsByAddresses(ctx context.Context, req OptGetNftItemsByAddressesReq) (*NftItems, error) + // GetOpenapiJson implements getOpenapiJson operation. + // + // Get the openapi.json file. + // + // GET /v2/openapi.json + GetOpenapiJson(ctx context.Context) (jx.Raw, error) + // GetOpenapiYml implements getOpenapiYml operation. + // + // Get the openapi.yml file. + // + // GET /v2/openapi.yml + GetOpenapiYml(ctx context.Context) (GetOpenapiYmlOK, error) // GetOutMsgQueueSizes implements getOutMsgQueueSizes operation. // // Get out msg queue sizes. @@ -589,12 +603,6 @@ type Handler interface { // // GET /v2/traces/{trace_id} GetTrace(ctx context.Context, params GetTraceParams) (*Trace, error) - // GetWalletBackup implements getWalletBackup operation. - // - // Get backup info. - // - // GET /v2/wallet/backup - GetWalletBackup(ctx context.Context, params GetWalletBackupParams) (*GetWalletBackupOK, error) // GetWalletsByPublicKey implements getWalletsByPublicKey operation. // // Get wallets by public key. @@ -625,12 +633,6 @@ type Handler interface { // // POST /v2/liteserver/send_message SendRawMessage(ctx context.Context, req *SendRawMessageReq) (*SendRawMessageOK, error) - // SetWalletBackup implements setWalletBackup operation. - // - // Set backup info. - // - // PUT /v2/wallet/backup - SetWalletBackup(ctx context.Context, req SetWalletBackupReq, params SetWalletBackupParams) error // Status implements status operation. // // Status. diff --git a/pkg/oas/oas_unimplemented_gen.go b/pkg/oas/oas_unimplemented_gen.go index cf659758..659e7a0d 100644 --- a/pkg/oas/oas_unimplemented_gen.go +++ b/pkg/oas/oas_unimplemented_gen.go @@ -5,6 +5,8 @@ package oas import ( "context" + "github.com/go-faster/jx" + ht "github.com/ogen-go/ogen/http" ) @@ -659,6 +661,24 @@ func (UnimplementedHandler) GetNftItemsByAddresses(ctx context.Context, req OptG return r, ht.ErrNotImplemented } +// GetOpenapiJson implements getOpenapiJson operation. +// +// Get the openapi.json file. +// +// GET /v2/openapi.json +func (UnimplementedHandler) GetOpenapiJson(ctx context.Context) (r jx.Raw, _ error) { + return r, ht.ErrNotImplemented +} + +// GetOpenapiYml implements getOpenapiYml operation. +// +// Get the openapi.yml file. +// +// GET /v2/openapi.yml +func (UnimplementedHandler) GetOpenapiYml(ctx context.Context) (r GetOpenapiYmlOK, _ error) { + return r, ht.ErrNotImplemented +} + // GetOutMsgQueueSizes implements getOutMsgQueueSizes operation. // // Get out msg queue sizes. @@ -876,15 +896,6 @@ func (UnimplementedHandler) GetTrace(ctx context.Context, params GetTraceParams) return r, ht.ErrNotImplemented } -// GetWalletBackup implements getWalletBackup operation. -// -// Get backup info. -// -// GET /v2/wallet/backup -func (UnimplementedHandler) GetWalletBackup(ctx context.Context, params GetWalletBackupParams) (r *GetWalletBackupOK, _ error) { - return r, ht.ErrNotImplemented -} - // GetWalletsByPublicKey implements getWalletsByPublicKey operation. // // Get wallets by public key. @@ -930,15 +941,6 @@ func (UnimplementedHandler) SendRawMessage(ctx context.Context, req *SendRawMess return r, ht.ErrNotImplemented } -// SetWalletBackup implements setWalletBackup operation. -// -// Set backup info. -// -// PUT /v2/wallet/backup -func (UnimplementedHandler) SetWalletBackup(ctx context.Context, req SetWalletBackupReq, params SetWalletBackupParams) error { - return ht.ErrNotImplemented -} - // Status implements status operation. // // Status. diff --git a/pkg/oas/oas_validators_gen.go b/pkg/oas/oas_validators_gen.go index 5e06eb54..0ad09224 100644 --- a/pkg/oas/oas_validators_gen.go +++ b/pkg/oas/oas_validators_gen.go @@ -579,6 +579,8 @@ func (s ActionType) Validate() error { switch s { case "TonTransfer": return nil + case "ExtraCurrencyTransfer": + return nil case "JettonTransfer": return nil case "JettonBurn": @@ -1298,6 +1300,24 @@ func (s *BlockchainConfig) Validate() error { Error: err, }) } + if err := func() error { + if value, ok := s.R45.Get(); ok { + if err := func() error { + if err := value.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "45", + Error: err, + }) + } if err := func() error { if value, ok := s.R71.Get(); ok { if err := func() error { @@ -1527,6 +1547,29 @@ func (s *BlockchainConfig44) Validate() error { return nil } +func (s *BlockchainConfig45) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.Contracts == nil { + return errors.New("nil is invalid value") + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "contracts", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + func (s *BlockchainConfig7) Validate() error { if s == nil { return validate.ErrNilPointer diff --git a/pkg/rates/market.go b/pkg/rates/market.go index 97c9a429..fcaedcf7 100644 --- a/pkg/rates/market.go +++ b/pkg/rates/market.go @@ -97,7 +97,7 @@ func (m *Mock) GetCurrentMarketsTonPrice() ([]Market, error) { { ID: 3, Name: bybit, - URL: "https://api.bybit.com/derivatives/v3/public/tickers?symbol=TONUSDT", + URL: "https://api.bybit.com/v5/market/tickers?category=spot&symbol=TONUSDT", TonPriceConverter: convertedTonBybitResponse, DateUpdate: now, }, diff --git a/pkg/references/errors.go b/pkg/references/errors.go new file mode 100644 index 00000000..1698c47b --- /dev/null +++ b/pkg/references/errors.go @@ -0,0 +1,18 @@ +package references + +type ExtendedCode int64 + +const ( + ErrGaslessJettonIsNotSupported ExtendedCode = iota + 40_000 + ErrGaslessTemporary + ErrGaslessSignature + ErrGaslessPendingMessages + ErrGaslessBadRequest + ErrGaslessOperationIsNotSupported + ErrGaslessUserDisabled + ErrGaslessEstimatingCommission + ErrGaslessCommission + ErrGaslessBalance + ErrGaslessBootstrapTransferDisabled + ErrGaslessUnknown +) diff --git a/pkg/references/extra_currency.go b/pkg/references/extra_currency.go new file mode 100644 index 00000000..b79f3b99 --- /dev/null +++ b/pkg/references/extra_currency.go @@ -0,0 +1,29 @@ +package references + +const DefaultExtraCurrencyDecimals = 9 + +type ExtraCurrencyMeta struct { + Name string + Symbol string + Image string + Decimals int +} + +var extraCurrencies = map[int32]ExtraCurrencyMeta{ + 239: { + Name: "FMS", + Decimals: 5, + Symbol: "FMS", + }, +} + +func GetExtraCurrencyMeta(id int32) ExtraCurrencyMeta { + meta, ok := extraCurrencies[id] + if ok { + return meta + } + return ExtraCurrencyMeta{ + Decimals: DefaultExtraCurrencyDecimals, + // TODO: add default placeholders + } +} diff --git a/pkg/spam/spam.go b/pkg/spam/spam.go index 687feb89..188cc833 100644 --- a/pkg/spam/spam.go +++ b/pkg/spam/spam.go @@ -18,7 +18,7 @@ func NewSpamFilter() *Filter { } } -func (f Filter) CheckActions(actions []oas.Action, viewer *ton.AccountID) bool { +func (f Filter) CheckActions(actions []oas.Action, viewer *ton.AccountID, initiator ton.AccountID) bool { var comment string for _, action := range actions { switch { diff --git a/pkg/wallet/risk.go b/pkg/wallet/risk.go index 89b23bf3..3edc0e76 100644 --- a/pkg/wallet/risk.go +++ b/pkg/wallet/risk.go @@ -41,45 +41,55 @@ func ExtractRiskFromRawMessages(rawMessages []tongoWallet.RawMessage) (*Risk, er risk.TransferAllRemainingBalance = true } var m abi.MessageRelaxed + rawMsg.Message.ResetCounters() // to avoid the case when several messages are taken from the same cell if err := tlb.Unmarshal(rawMsg.Message, &m); err != nil { return nil, err } - tonValue := uint64(m.MessageInternal.Value.Grams) - destination, err := ton.AccountIDFromTlb(m.MessageInternal.Dest) + var err error + risk, err = ExtractRiskFromMessage(m, risk, rawMsg.Mode) if err != nil { return nil, err } - risk.Ton += tonValue - msgBody := m.MessageInternal.Body.Value.Value - if err != nil { - continue + } + return &risk, nil +} + +func ExtractRiskFromMessage(m abi.MessageRelaxed, risk Risk, mode byte) (Risk, error) { + if tongoWallet.IsMessageModeSet(int(mode), tongoWallet.AttachAllRemainingBalance) { + risk.TransferAllRemainingBalance = true + } + tonValue := uint64(m.MessageInternal.Value.Grams) + destination, err := ton.AccountIDFromTlb(m.MessageInternal.Dest) + if err != nil { + return Risk{}, err + } + risk.Ton += tonValue + msgBody := m.MessageInternal.Body.Value.Value + switch x := msgBody.(type) { + case abi.NftTransferMsgBody: + if destination == nil { + return risk, nil } - switch x := msgBody.(type) { - case abi.NftTransferMsgBody: - if destination == nil { - continue - } - // here, destination is an NFT - risk.Nfts = append(risk.Nfts, *destination) - case abi.JettonBurnMsgBody: - if destination == nil { - continue - } - // here, destination is a jetton wallet - amount := big.Int(x.Amount) - currentJettons := risk.Jettons[*destination] - var total big.Int - risk.Jettons[*destination] = *total.Add(¤tJettons, &amount) - case abi.JettonTransferMsgBody: - if destination == nil { - continue - } - // here, destination is a jetton wallet - amount := big.Int(x.Amount) - currentJettons := risk.Jettons[*destination] - var total big.Int - risk.Jettons[*destination] = *total.Add(¤tJettons, &amount) + // here, destination is an NFT + risk.Nfts = append(risk.Nfts, *destination) + case abi.JettonBurnMsgBody: + if destination == nil { + return risk, nil } + // here, destination is a jetton wallet + amount := big.Int(x.Amount) + currentJettons := risk.Jettons[*destination] + var total big.Int + risk.Jettons[*destination] = *total.Add(¤tJettons, &amount) + case abi.JettonTransferMsgBody: + if destination == nil { + return risk, nil + } + // here, destination is a jetton wallet + amount := big.Int(x.Amount) + currentJettons := risk.Jettons[*destination] + var total big.Int + risk.Jettons[*destination] = *total.Add(¤tJettons, &amount) } - return &risk, nil + return risk, nil }