diff --git a/api/openapi.json b/api/openapi.json index 2303873e..8bd64cc6 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -1063,6 +1063,9 @@ "ElectionsRecoverStake": { "$ref": "#/components/schemas/ElectionsRecoverStakeAction" }, + "ExtraCurrencyTransfer": { + "$ref": "#/components/schemas/ExtraCurrencyTransferAction" + }, "InscriptionMint": { "$ref": "#/components/schemas/InscriptionMintAction" }, @@ -1127,6 +1130,7 @@ "type": { "enum": [ "TonTransfer", + "ExtraCurrencyTransfer", "JettonTransfer", "JettonBurn", "JettonMint", @@ -3119,6 +3123,33 @@ ], "type": "object" }, + "EcPreview": { + "properties": { + "decimals": { + "example": 5, + "type": "integer" + }, + "image": { + "example": "https://cache.tonapi.io/images/extra.jpg", + "type": "string" + }, + "name": { + "example": "FMS", + "type": "string" + }, + "symbol": { + "example": "FMS", + "type": "string" + } + }, + "required": [ + "name", + "symbol", + "decimals", + "image" + ], + "type": "object" + }, "ElectionsDepositStakeAction": { "properties": { "amount": { @@ -3263,6 +3294,42 @@ ], "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" + }, + "refund": { + "$ref": "#/components/schemas/Refund" + }, + "sender": { + "$ref": "#/components/schemas/AccountAddress" + } + }, + "required": [ + "sender", + "recipient", + "amount", + "currency" + ], + "type": "object" + }, "FoundAccounts": { "properties": { "addresses": { diff --git a/api/openapi.yml b/api/openapi.yml index 2664866b..b78bd48a 100644 --- a/api/openapi.yml +++ b/api/openapi.yml @@ -5744,6 +5744,7 @@ components: example: "TonTransfer" enum: - TonTransfer + - ExtraCurrencyTransfer - JettonTransfer - JettonBurn - JettonMint @@ -5772,6 +5773,8 @@ components: - failed TonTransfer: $ref: '#/components/schemas/TonTransferAction' + ExtraCurrencyTransfer: + $ref: '#/components/schemas/ExtraCurrencyTransferAction' ContractDeploy: $ref: '#/components/schemas/ContractDeployAction' JettonTransfer: @@ -5842,6 +5845,52 @@ components: $ref: '#/components/schemas/EncryptedComment' refund: $ref: '#/components/schemas/Refund' + EcPreview: + type: object + required: + - name + - symbol + - decimals + - image + properties: + name: + type: string + example: FMS + 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' + refund: + $ref: '#/components/schemas/Refund' + currency: + $ref: '#/components/schemas/EcPreview' SmartContractAction: type: object required: diff --git a/pkg/api/account_converters.go b/pkg/api/account_converters.go index 7ea08294..656c2883 100644 --- a/pkg/api/account_converters.go +++ b/pkg/api/account_converters.go @@ -4,6 +4,7 @@ import ( "fmt" imgGenerator "github.com/tonkeeper/opentonapi/pkg/image" "github.com/tonkeeper/opentonapi/pkg/references" + "math/big" "sort" "github.com/tonkeeper/tongo/abi" @@ -60,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", int32(key))] = fmt.Sprintf("%v", value) + v := big.Int(value) + balances[fmt.Sprintf("%v", key)] = fmt.Sprintf("%v", v.String()) } rawAccount.ExtraBalance = oas.NewOptBlockchainRawAccountExtraBalance(balances) } @@ -76,15 +78,15 @@ func convertToRawAccount(account *core.Account) (oas.BlockchainRawAccount, error 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: int32(k), - Amount: v.String(), - Decimals: 9, // TODO: or replace with default const + ID: k, + Amount: amount.String(), + Decimals: meta.Decimals, } - meta, ok := references.ExtraCurrencies[int32(k)] - if ok { + if meta.Name != "" { cur.Name.SetTo(meta.Name) - cur.Decimals = meta.Decimals } res = append(res, cur) } diff --git a/pkg/api/event_converters.go b/pkg/api/event_converters.go index e265a5c8..7f3dc857 100644 --- a/pkg/api/event_converters.go +++ b/pkg/api/event_converters.go @@ -151,6 +151,38 @@ 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{ + Name: meta.Name, + Symbol: meta.Symbol, + Decimals: meta.Decimals, + Image: meta.Image, + }, + }) + if t.Refund != nil { + action.Value.Refund.SetTo(oas.Refund{ + Type: oas.RefundType(t.Refund.Type), + Origin: t.Refund.Origin, + }) + } + 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{ @@ -428,6 +460,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: diff --git a/pkg/bath/actions.go b/pkg/bath/actions.go index 0b1dee5d..68d53890 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,15 @@ type ( Sender tongo.AccountID Refund *Refund } + ExtraCurrencyTransferAction struct { + CurrencyID int32 + Amount tlb.VarUInteger32 + Comment *string + EncryptedComment *EncryptedComment + Recipient tongo.AccountID + Sender tongo.AccountID + Refund *Refund + } SmartContractAction struct { TonAttached int64 Executor tongo.AccountID 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/core/converters.go b/pkg/core/converters.go index 94ac61ce..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" @@ -478,14 +477,13 @@ func ConvertToAccount(accountId tongo.AccountID, shardAccount tlb.ShardAccount) func extractExtraCurrencies(extraCurrencyCollection tlb.ExtraCurrencyCollection) ExtraCurrencies { items := extraCurrencyCollection.Dict.Items() if len(items) > 0 { - res := make(map[uint32]decimal.Decimal, len(items)) + res := make(map[int32]tlb.VarUInteger32, len(items)) for _, item := range items { - value := big.Int(item.Value) - res[uint32(item.Key)] = decimal.NewFromBigInt(&value, 0) + res[int32(item.Key)] = item.Value } return res } - return nil // TODO: or return empty map + return nil } func ExtractTransactions(id tongo.BlockIDExt, block *tlb.Block) ([]*Transaction, error) { diff --git a/pkg/core/extra_currencies.go b/pkg/core/extra_currencies.go index 9101a526..dc9a8fa2 100644 --- a/pkg/core/extra_currencies.go +++ b/pkg/core/extra_currencies.go @@ -1,5 +1,7 @@ package core -import "github.com/shopspring/decimal" +import ( + "github.com/tonkeeper/tongo/tlb" +) -type ExtraCurrencies map[uint32]decimal.Decimal +type ExtraCurrencies map[int32]tlb.VarUInteger32 diff --git a/pkg/oas/oas_json_gen.go b/pkg/oas/oas_json_gen.go index cd5c6f80..97071c2a 100644 --- a/pkg/oas/oas_json_gen.go +++ b/pkg/oas/oas_json_gen.go @@ -1694,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") @@ -1822,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. @@ -1854,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) { @@ -1888,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() @@ -2079,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 @@ -2089,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 { @@ -2117,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. @@ -2613,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: @@ -14426,6 +14446,153 @@ func (s *DomainRenewAction) UnmarshalJSON(data []byte) error { return s.Decode(d) } +// Encode implements json.Marshaler. +func (s *EcPreview) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *EcPreview) encodeFields(e *jx.Encoder) { + { + e.FieldStart("name") + e.Str(s.Name) + } + { + e.FieldStart("symbol") + e.Str(s.Symbol) + } + { + e.FieldStart("decimals") + e.Int(s.Decimals) + } + { + e.FieldStart("image") + e.Str(s.Image) + } +} + +var jsonFieldsNameOfEcPreview = [4]string{ + 0: "name", + 1: "symbol", + 2: "decimals", + 3: "image", +} + +// Decode decodes EcPreview from json. +func (s *EcPreview) Decode(d *jx.Decoder) error { + if s == 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 "name": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := d.Str() + s.Name = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"name\"") + } + case "symbol": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.Symbol = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"symbol\"") + } + case "decimals": + requiredBitSet[0] |= 1 << 2 + 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\"") + } + case "image": + requiredBitSet[0] |= 1 << 3 + 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 EcPreview") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00001111, + } { + 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(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() @@ -15764,6 +15931,198 @@ func (s *ExtraCurrency) UnmarshalJSON(data []byte) error { 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) + } + } + { + if s.Refund.Set { + e.FieldStart("refund") + s.Refund.Encode(e) + } + } + { + e.FieldStart("currency") + s.Currency.Encode(e) + } +} + +var jsonFieldsNameOfExtraCurrencyTransferAction = [7]string{ + 0: "sender", + 1: "recipient", + 2: "amount", + 3: "comment", + 4: "encrypted_comment", + 5: "refund", + 6: "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 "refund": + if err := func() error { + s.Refund.Reset() + if err := s.Refund.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"refund\"") + } + case "currency": + requiredBitSet[0] |= 1 << 6 + 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{ + 0b01000111, + } { + 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() @@ -30756,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 { diff --git a/pkg/oas/oas_schemas_gen.go b/pkg/oas/oas_schemas_gen.go index 87118f3b..5b7e2c69 100644 --- a/pkg/oas/oas_schemas_gen.go +++ b/pkg/oas/oas_schemas_gen.go @@ -658,6 +658,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"` @@ -696,6 +697,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 @@ -816,6 +822,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 @@ -1120,6 +1131,7 @@ type ActionType string const ( ActionTypeTonTransfer ActionType = "TonTransfer" + ActionTypeExtraCurrencyTransfer ActionType = "ExtraCurrencyTransfer" ActionTypeJettonTransfer ActionType = "JettonTransfer" ActionTypeJettonBurn ActionType = "JettonBurn" ActionTypeJettonMint ActionType = "JettonMint" @@ -1146,6 +1158,7 @@ const ( func (ActionType) AllValues() []ActionType { return []ActionType{ ActionTypeTonTransfer, + ActionTypeExtraCurrencyTransfer, ActionTypeJettonTransfer, ActionTypeJettonBurn, ActionTypeJettonMint, @@ -1174,6 +1187,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: @@ -1225,6 +1240,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 @@ -5104,6 +5122,54 @@ func (s *DomainRenewAction) SetRenewer(val AccountAddress) { s.Renewer = val } +// Ref: #/components/schemas/EcPreview +type EcPreview struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimals int `json:"decimals"` + Image string `json:"image"` +} + +// GetName returns the value of Name. +func (s *EcPreview) GetName() string { + return s.Name +} + +// 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 +} + +// SetName sets the value of Name. +func (s *EcPreview) SetName(val string) { + s.Name = val +} + +// 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"` @@ -5457,6 +5523,88 @@ 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"` + Refund OptRefund `json:"refund"` + 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 +} + +// GetRefund returns the value of Refund. +func (s *ExtraCurrencyTransferAction) GetRefund() OptRefund { + return s.Refund +} + +// 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 +} + +// SetRefund sets the value of Refund. +func (s *ExtraCurrencyTransferAction) SetRefund(val OptRefund) { + s.Refund = 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"` @@ -12398,6 +12546,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{ diff --git a/pkg/oas/oas_validators_gen.go b/pkg/oas/oas_validators_gen.go index fb1c9670..0e5f49f4 100644 --- a/pkg/oas/oas_validators_gen.go +++ b/pkg/oas/oas_validators_gen.go @@ -261,6 +261,24 @@ func (s *Action) Validate() error { Error: err, }) } + if err := func() error { + if value, ok := s.ExtraCurrencyTransfer.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: "ExtraCurrencyTransfer", + Error: err, + }) + } if err := func() error { if value, ok := s.ContractDeploy.Get(); ok { if err := func() error { @@ -579,6 +597,8 @@ func (s ActionType) Validate() error { switch s { case "TonTransfer": return nil + case "ExtraCurrencyTransfer": + return nil case "JettonTransfer": return nil case "JettonBurn": @@ -2361,6 +2381,36 @@ func (s *Event) Validate() error { return nil } +func (s *ExtraCurrencyTransferAction) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if value, ok := s.Refund.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: "refund", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + func (s *FoundAccounts) Validate() error { if s == nil { return validate.ErrNilPointer diff --git a/pkg/references/extra_currency.go b/pkg/references/extra_currency.go index 5aea713e..b79f3b99 100644 --- a/pkg/references/extra_currency.go +++ b/pkg/references/extra_currency.go @@ -1,13 +1,29 @@ package references +const DefaultExtraCurrencyDecimals = 9 + type ExtraCurrencyMeta struct { Name string + Symbol string + Image string Decimals int } -var ExtraCurrencies = map[int32]ExtraCurrencyMeta{ +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 + } +}