From fb48bfa19945638dc25c6df941a6ed2ebec5489b Mon Sep 17 00:00:00 2001 From: Totemancer Date: Tue, 2 Jul 2024 12:43:04 +0200 Subject: [PATCH 1/5] Refactor V5R1 to V5Beta and Prepare Support for V5 Final (WIP) --- ton/wallet/address.go | 34 +++++++-- ton/wallet/{v5r1.go => v5_beta.go} | 20 +++--- ton/wallet/v5_final.go | 112 +++++++++++++++++++++++++++++ ton/wallet/wallet.go | 46 ++++++++---- 4 files changed, 181 insertions(+), 31 deletions(-) rename ton/wallet/{v5r1.go => v5_beta.go} (81%) create mode 100644 ton/wallet/v5_final.go diff --git a/ton/wallet/address.go b/ton/wallet/address.go index 01a1df1..d62ef6b 100644 --- a/ton/wallet/address.go +++ b/ton/wallet/address.go @@ -55,13 +55,17 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin switch ver { case HighloadV3: return nil, fmt.Errorf("use ConfigHighloadV3 for highload v3 spec") - case V5R1: - return nil, fmt.Errorf("use ConfigV5R1 for v5 spec") + case V5Beta: + return nil, fmt.Errorf("use ConfigV5Beta for v5b spec") + case V5Final: + return nil, fmt.Errorf("use ConfigV5Final for v5f spec") } case ConfigHighloadV3: ver = HighloadV3 - case ConfigV5R1: - ver = V5R1 + case ConfigV5Beta: + ver = V5Beta + case ConfigV5Final: + ver = V5Final } code, ok := walletCode[ver] @@ -84,8 +88,8 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin MustStoreSlice(pubKey, 256). MustStoreDict(nil). // empty dict of plugins EndCell() - case V5R1: - config := version.(ConfigV5R1) + case V5Beta: + config := version.(ConfigV5Beta) data = cell.BeginCell(). MustStoreUInt(0, 33). // seqno @@ -96,6 +100,24 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin MustStoreSlice(pubKey, 256). MustStoreDict(nil). // empty dict of plugins EndCell() + case V5Final: + config := version.(ConfigV5Final) + + // Create WalletId instance + walletId := WalletId{ + NetworkGlobalID: config.NetworkGlobalID, + WorkChain: config.Workchain, + SubwalletNumber: subWallet, + walletVersion: 0, + } + + data = cell.BeginCell(). + MustStoreBoolBit(true). + MustStoreUInt(0, 32). // seqno + MustStoreUInt(uint64(walletId.Serialized()), 32). // serialize + MustStoreSlice(pubKey, 256). + MustStoreDict(nil). // empty dict of plugins + EndCell() case HighloadV2R2, HighloadV2Verified: data = cell.BeginCell(). MustStoreUInt(uint64(subWallet), 32). diff --git a/ton/wallet/v5r1.go b/ton/wallet/v5_beta.go similarity index 81% rename from ton/wallet/v5r1.go rename to ton/wallet/v5_beta.go index 8c560ca..99c07c4 100644 --- a/ton/wallet/v5r1.go +++ b/ton/wallet/v5_beta.go @@ -4,32 +4,30 @@ import ( "context" "errors" "fmt" + "time" + "github.com/xssnick/tonutils-go/tlb" "github.com/xssnick/tonutils-go/ton" - "time" "github.com/xssnick/tonutils-go/tvm/cell" ) // https://github.com/tonkeeper/tonkeeper-ton/commit/e8a7f3415e241daf4ac723f273fbc12776663c49#diff-c20d462b2e1ec616bbba2db39acc7a6c61edc3d5e768f5c2034a80169b1a56caR29 -const _V5R1CodeHex = "b5ee9c7241010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f34120dcd8e11" +const _V5BetaCodeHex = "b5ee9c7241010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f34120dcd8e11" -type ConfigV5R1 struct { +type ConfigV5Beta struct { NetworkGlobalID int32 Workchain int8 } -type SpecV5R1 struct { +type SpecV5Beta struct { SpecRegular SpecSeqno - config ConfigV5R1 + config ConfigV5Beta } -const MainnetGlobalID = -239 -const TestnetGlobalID = -3 - -func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { +func (s *SpecV5Beta) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { // TODO: remove block, now it is here for backwards compatibility if len(messages) > 255 { @@ -41,7 +39,7 @@ func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, return nil, fmt.Errorf("failed to fetch seqno: %w", err) } - actions, err := packV5Actions(messages) + actions, err := packV5BetaActions(messages) if err != nil { return nil, fmt.Errorf("failed to build actions: %w", err) } @@ -62,7 +60,7 @@ func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, return msg, nil } -func packV5Actions(messages []*Message) (*cell.Builder, error) { +func packV5BetaActions(messages []*Message) (*cell.Builder, error) { if len(messages) > 255 { return nil, fmt.Errorf("max 255 messages allowed for v5") } diff --git a/ton/wallet/v5_final.go b/ton/wallet/v5_final.go new file mode 100644 index 0000000..dc27063 --- /dev/null +++ b/ton/wallet/v5_final.go @@ -0,0 +1,112 @@ +package wallet + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/ton" + + "github.com/xssnick/tonutils-go/tvm/cell" +) + +// https://github.com/tolya-yanot/w5/commit/84eaf15caf79b354507116964c283da08efbb564 +const _V5FinalCodeHex = "b5ee9c724102140100028c000114ff00f4a413f4bcf2c80b0102012004020102f203012020d70b1f82107369676ebaf2e08a7f700f0201480e0502012007060019be5f0f6a2684080a0eb90fa02c0201200b080201480a090011b262fb513435c280200017b325fb51341c75c875c2c7e002016e0d0c0019af1df6a2684010eb90eb858fc00019adce76a2684020eb90eb85ffc002f2d020d749c120915b8f6e20d70b1f2082106578746ebd21821073696e74bdb0925f03e002d0d60301c713c200925f03e00282106578746eba8eb08020d72101fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810282b99130e07070e2100f01e48eefeda2edfb228308d722038308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ddf9109a29945f0bdb31e1f2c087df02b35007b0f2d0845125baf2e0855037baf2e086f823bbf2d0882392f800de01a47fc8ca00cb1f01cf16c9ed542192f80fdedb3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a1312110010935bdb31e1d74cd0007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de2009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed545bfe7180" + +type ConfigV5Final struct { + NetworkGlobalID int32 + Workchain int8 +} + +type SpecV5Final struct { + SpecRegular + SpecSeqno + + config ConfigV5Final +} + +type WalletId struct { + NetworkGlobalID int32 + WorkChain int8 + SubwalletNumber uint32 + walletVersion uint8 +} + +func (w WalletId) Serialized() uint32 { + // Serialize WalletId into a 32-bit integer + return uint32(w.NetworkGlobalID)<<24 | uint32(w.WorkChain)<<16 | w.SubwalletNumber +} + +func (s *SpecV5Final) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { + if len(messages) > 255 { + return nil, errors.New("for this type of wallet max 255 messages can be sent at the same time") + } + + seq, err := s.seqnoFetcher(ctx, s.wallet.subwallet) + if err != nil { + return nil, fmt.Errorf("failed to fetch seqno: %w", err) + } + + actions, err := packV5FinalActions(messages) + if err != nil { + return nil, fmt.Errorf("failed to build actions: %w", err) + } + + walletId := WalletId{ + NetworkGlobalID: s.config.NetworkGlobalID, + WorkChain: s.config.Workchain, + SubwalletNumber: s.wallet.subwallet, + walletVersion: 0, + } + + payload := cell.BeginCell(). + MustStoreUInt(0x7369676e, 32). // external sign op code + MustStoreUInt(uint64(walletId.Serialized()), 32). // serialized WalletId + MustStoreUInt(uint64(time.Now().Add(time.Duration(s.messagesTTL)*time.Second).UTC().Unix()), 32). // validUntil + MustStoreUInt(uint64(seq), 32). // seq (block) + MustStoreBuilder(actions) // Action list + + sign := payload.EndCell().Sign(s.wallet.key) + msg := cell.BeginCell().MustStoreBuilder(payload).MustStoreSlice(sign, 512).EndCell() + + return msg, nil +} + +// Validate messages +func validateMessageFields(messages []*Message) error { + if len(messages) > 255 { + return fmt.Errorf("max 255 messages allowed for v5") + } + for _, message := range messages { + if message.InternalMessage == nil { + return fmt.Errorf("internal message cannot be nil") + } + } + return nil +} + +// Pack Actions +func packV5FinalActions(messages []*Message) (*cell.Builder, error) { + if err := validateMessageFields(messages); err != nil { + return nil, err + } + + var list = cell.BeginCell().EndCell() + for _, message := range messages { + outMsg, err := tlb.ToCell(message.InternalMessage) + if err != nil { + return nil, err + } + + msg := cell.BeginCell().MustStoreUInt(0x0ec3c86d, 32). // action_send_msg prefix + MustStoreUInt(uint64(message.Mode), 8). // mode + MustStoreRef(outMsg) // message reference + + list = cell.BeginCell().MustStoreRef(list).MustStoreBuilder(msg).EndCell() + } + + // Ensure the action list ends with 0, 1 as per the new specification + return cell.BeginCell().MustStoreUInt(1, 1).MustStoreRef(list).MustStoreUInt(0, 1), nil +} diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index e571935..7f76d87 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -13,9 +13,10 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/xssnick/tonutils-go/adnl" "time" + "github.com/xssnick/tonutils-go/adnl" + "github.com/xssnick/tonutils-go/ton" "github.com/xssnick/tonutils-go/address" @@ -25,6 +26,10 @@ import ( type Version int +// Network IDs +const MainnetGlobalID = -239 +const TestnetGlobalID = -3 + const ( V1R1 Version = 11 V1R2 Version = 12 @@ -36,7 +41,8 @@ const ( V3 = V3R2 V4R1 Version = 41 V4R2 Version = 42 - V5R1 Version = 51 + V5Beta Version = 51 + V5Final Version = 52 HighloadV2R2 Version = 122 HighloadV2Verified Version = 123 HighloadV3 Version = 300 @@ -79,7 +85,8 @@ var ( V2R1: _V2R1CodeHex, V2R2: _V2R2CodeHex, V3R1: _V3R1CodeHex, V3R2: _V3R2CodeHex, V4R1: _V4R1CodeHex, V4R2: _V4R2CodeHex, - V5R1: _V5R1CodeHex, + V5Beta: _V5BetaCodeHex, + V5Final: _V5FinalCodeHex, HighloadV2R2: _HighloadV2R2CodeHex, HighloadV2Verified: _HighloadV2VerifiedCodeHex, HighloadV3: _HighloadV3CodeHex, Lockup: _LockupCodeHex, @@ -155,7 +162,8 @@ func FromPrivateKey(api TonAPI, key ed25519.PrivateKey, version VersionConfig) ( // default subwallet depends on wallet type switch version.(type) { - case ConfigV5R1: + case ConfigV5Beta: + case ConfigV5Final: subwallet = 0 } @@ -182,7 +190,7 @@ func FromPrivateKey(api TonAPI, key ed25519.PrivateKey, version VersionConfig) ( func getSpec(w *Wallet) (any, error) { switch v := w.ver.(type) { - case Version, ConfigV5R1: + case Version, ConfigV5Beta, ConfigV5Final: regular := SpecRegular{ wallet: w, messagesTTL: 60 * 3, // default ttl 3 min @@ -210,11 +218,16 @@ func getSpec(w *Wallet) (any, error) { } switch x := w.ver.(type) { - case ConfigV5R1: + case ConfigV5Beta: if x.NetworkGlobalID == 0 { - return nil, fmt.Errorf("NetworkGlobalID should be set in v5 config") + return nil, fmt.Errorf("NetworkGlobalID should be set in V5B config") } - return &SpecV5R1{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil + return &SpecV5Beta{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil + case ConfigV5Final: + if x.NetworkGlobalID == 0 { + return nil, fmt.Errorf("NetworkGlobalID should be set in V5F config") + } + return &SpecV5Final{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil } switch v { @@ -226,8 +239,10 @@ func getSpec(w *Wallet) (any, error) { return &SpecHighloadV2R2{regular, SpecQuery{}}, nil case HighloadV3: return nil, fmt.Errorf("use ConfigHighloadV3 for highload v3 spec") - case V5R1: - return nil, fmt.Errorf("use ConfigV5R1 for v5 spec") + case V5Beta: + return nil, fmt.Errorf("use ConfigV5Beta for v5b spec") + case V5Final: + return nil, fmt.Errorf("use ConfigV5Final for v5f spec") } case ConfigHighloadV3: return &SpecHighloadV3{wallet: w, config: v}, nil @@ -328,13 +343,16 @@ func (w *Wallet) PrepareExternalMessageForMany(ctx context.Context, withStateIni var msg *cell.Cell switch v := w.ver.(type) { - case Version, ConfigV5R1: - if _, ok := v.(ConfigV5R1); ok { - v = V5R1 + case Version, ConfigV5Beta, ConfigV5Final: + if _, ok := v.(ConfigV5Beta); ok { + v = V5Beta + } + if _, ok := v.(ConfigV5Final); ok { + v = V5Final } switch v { - case V3R2, V3R1, V4R2, V4R1, V5R1: + case V3R2, V3R1, V4R2, V4R1, V5Beta, V5Final: msg, err = w.spec.(RegularBuilder).BuildMessage(ctx, !withStateInit, nil, messages) if err != nil { return nil, fmt.Errorf("build message err: %w", err) From 0fedda1d1c18d9c057bb43c247f25d2d1ae85ede Mon Sep 17 00:00:00 2001 From: Totemancer Date: Wed, 3 Jul 2024 16:15:21 +0200 Subject: [PATCH 2/5] Update to Finalized V5 Contract (WIP) from upstream tonkeeper/w5 compiled base64 IINLe3KxEhR+Gy+0V7hOdNGjDwT3N9T2KmaOlVLSty8= --- ton/wallet/v5_final.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ton/wallet/v5_final.go b/ton/wallet/v5_final.go index dc27063..994fbfb 100644 --- a/ton/wallet/v5_final.go +++ b/ton/wallet/v5_final.go @@ -12,8 +12,8 @@ import ( "github.com/xssnick/tonutils-go/tvm/cell" ) -// https://github.com/tolya-yanot/w5/commit/84eaf15caf79b354507116964c283da08efbb564 -const _V5FinalCodeHex = "b5ee9c724102140100028c000114ff00f4a413f4bcf2c80b0102012004020102f203012020d70b1f82107369676ebaf2e08a7f700f0201480e0502012007060019be5f0f6a2684080a0eb90fa02c0201200b080201480a090011b262fb513435c280200017b325fb51341c75c875c2c7e002016e0d0c0019af1df6a2684010eb90eb858fc00019adce76a2684020eb90eb85ffc002f2d020d749c120915b8f6e20d70b1f2082106578746ebd21821073696e74bdb0925f03e002d0d60301c713c200925f03e00282106578746eba8eb08020d72101fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810282b99130e07070e2100f01e48eefeda2edfb228308d722038308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ddf9109a29945f0bdb31e1f2c087df02b35007b0f2d0845125baf2e0855037baf2e086f823bbf2d0882392f800de01a47fc8ca00cb1f01cf16c9ed542192f80fdedb3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a1312110010935bdb31e1d74cd0007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de2009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed545bfe7180" +// https://github.com/tonkeeper/w5/blob/main/build/wallet_v5.compiled.json +const _V5FinalCodeHex = "b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e" type ConfigV5Final struct { NetworkGlobalID int32 From 8ba6af6f8bec49e7560e010f88bf51525c9cc27e Mon Sep 17 00:00:00 2001 From: Totemancer Date: Thu, 4 Jul 2024 14:40:43 +0200 Subject: [PATCH 3/5] Update name to V5R1, V5R2 --- ton/wallet/address.go | 24 +++++++-------- ton/wallet/{v5_beta.go => v5r1.go} | 14 ++++----- ton/wallet/{v5_final.go => v5r2.go} | 16 +++++----- ton/wallet/wallet.go | 46 ++++++++++++++--------------- 4 files changed, 50 insertions(+), 50 deletions(-) rename ton/wallet/{v5_beta.go => v5r1.go} (83%) rename ton/wallet/{v5_final.go => v5r2.go} (62%) diff --git a/ton/wallet/address.go b/ton/wallet/address.go index d62ef6b..adb304f 100644 --- a/ton/wallet/address.go +++ b/ton/wallet/address.go @@ -55,17 +55,17 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin switch ver { case HighloadV3: return nil, fmt.Errorf("use ConfigHighloadV3 for highload v3 spec") - case V5Beta: - return nil, fmt.Errorf("use ConfigV5Beta for v5b spec") - case V5Final: - return nil, fmt.Errorf("use ConfigV5Final for v5f spec") + case V5R1: + return nil, fmt.Errorf("use ConfigV5R1 for V5 spec") + case V5R2: + return nil, fmt.Errorf("use ConfigV5R2 for V5 spec") } case ConfigHighloadV3: ver = HighloadV3 - case ConfigV5Beta: - ver = V5Beta - case ConfigV5Final: - ver = V5Final + case ConfigV5R1: + ver = V5R1 + case ConfigV5R2: + ver = V5R2 } code, ok := walletCode[ver] @@ -88,8 +88,8 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin MustStoreSlice(pubKey, 256). MustStoreDict(nil). // empty dict of plugins EndCell() - case V5Beta: - config := version.(ConfigV5Beta) + case V5R1: + config := version.(ConfigV5R1) data = cell.BeginCell(). MustStoreUInt(0, 33). // seqno @@ -100,8 +100,8 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin MustStoreSlice(pubKey, 256). MustStoreDict(nil). // empty dict of plugins EndCell() - case V5Final: - config := version.(ConfigV5Final) + case V5R2: + config := version.(ConfigV5R2) // Create WalletId instance walletId := WalletId{ diff --git a/ton/wallet/v5_beta.go b/ton/wallet/v5r1.go similarity index 83% rename from ton/wallet/v5_beta.go rename to ton/wallet/v5r1.go index 99c07c4..17f5983 100644 --- a/ton/wallet/v5_beta.go +++ b/ton/wallet/v5r1.go @@ -13,21 +13,21 @@ import ( ) // https://github.com/tonkeeper/tonkeeper-ton/commit/e8a7f3415e241daf4ac723f273fbc12776663c49#diff-c20d462b2e1ec616bbba2db39acc7a6c61edc3d5e768f5c2034a80169b1a56caR29 -const _V5BetaCodeHex = "b5ee9c7241010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f34120dcd8e11" +const _V5R1CodeHex = "b5ee9c7241010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f34120dcd8e11" -type ConfigV5Beta struct { +type ConfigV5R1 struct { NetworkGlobalID int32 Workchain int8 } -type SpecV5Beta struct { +type SpecV5R1 struct { SpecRegular SpecSeqno - config ConfigV5Beta + config ConfigV5R1 } -func (s *SpecV5Beta) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { +func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { // TODO: remove block, now it is here for backwards compatibility if len(messages) > 255 { @@ -39,7 +39,7 @@ func (s *SpecV5Beta) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt return nil, fmt.Errorf("failed to fetch seqno: %w", err) } - actions, err := packV5BetaActions(messages) + actions, err := packV5R1Actions(messages) if err != nil { return nil, fmt.Errorf("failed to build actions: %w", err) } @@ -60,7 +60,7 @@ func (s *SpecV5Beta) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt return msg, nil } -func packV5BetaActions(messages []*Message) (*cell.Builder, error) { +func packV5R1Actions(messages []*Message) (*cell.Builder, error) { if len(messages) > 255 { return nil, fmt.Errorf("max 255 messages allowed for v5") } diff --git a/ton/wallet/v5_final.go b/ton/wallet/v5r2.go similarity index 62% rename from ton/wallet/v5_final.go rename to ton/wallet/v5r2.go index 994fbfb..1fe33e0 100644 --- a/ton/wallet/v5_final.go +++ b/ton/wallet/v5r2.go @@ -12,19 +12,19 @@ import ( "github.com/xssnick/tonutils-go/tvm/cell" ) -// https://github.com/tonkeeper/w5/blob/main/build/wallet_v5.compiled.json -const _V5FinalCodeHex = "b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e" +// https://github.com/ton-blockchain/wallet-contract-v5/blob/main/build/wallet_v5.compiled.json +const _V5R2CodeHex = "b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e" -type ConfigV5Final struct { +type ConfigV5R2 struct { NetworkGlobalID int32 Workchain int8 } -type SpecV5Final struct { +type SpecV5R2 struct { SpecRegular SpecSeqno - config ConfigV5Final + config ConfigV5R2 } type WalletId struct { @@ -39,7 +39,7 @@ func (w WalletId) Serialized() uint32 { return uint32(w.NetworkGlobalID)<<24 | uint32(w.WorkChain)<<16 | w.SubwalletNumber } -func (s *SpecV5Final) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { +func (s *SpecV5R2) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { if len(messages) > 255 { return nil, errors.New("for this type of wallet max 255 messages can be sent at the same time") } @@ -49,7 +49,7 @@ func (s *SpecV5Final) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDEx return nil, fmt.Errorf("failed to fetch seqno: %w", err) } - actions, err := packV5FinalActions(messages) + actions, err := packV5R2Actions(messages) if err != nil { return nil, fmt.Errorf("failed to build actions: %w", err) } @@ -88,7 +88,7 @@ func validateMessageFields(messages []*Message) error { } // Pack Actions -func packV5FinalActions(messages []*Message) (*cell.Builder, error) { +func packV5R2Actions(messages []*Message) (*cell.Builder, error) { if err := validateMessageFields(messages); err != nil { return nil, err } diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index 7f76d87..cdec418 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -41,8 +41,8 @@ const ( V3 = V3R2 V4R1 Version = 41 V4R2 Version = 42 - V5Beta Version = 51 - V5Final Version = 52 + V5R1 Version = 51 + V5R2 Version = 52 HighloadV2R2 Version = 122 HighloadV2Verified Version = 123 HighloadV3 Version = 300 @@ -85,8 +85,8 @@ var ( V2R1: _V2R1CodeHex, V2R2: _V2R2CodeHex, V3R1: _V3R1CodeHex, V3R2: _V3R2CodeHex, V4R1: _V4R1CodeHex, V4R2: _V4R2CodeHex, - V5Beta: _V5BetaCodeHex, - V5Final: _V5FinalCodeHex, + V5R1: _V5R1CodeHex, + V5R2: _V5R2CodeHex, HighloadV2R2: _HighloadV2R2CodeHex, HighloadV2Verified: _HighloadV2VerifiedCodeHex, HighloadV3: _HighloadV3CodeHex, Lockup: _LockupCodeHex, @@ -162,8 +162,8 @@ func FromPrivateKey(api TonAPI, key ed25519.PrivateKey, version VersionConfig) ( // default subwallet depends on wallet type switch version.(type) { - case ConfigV5Beta: - case ConfigV5Final: + case ConfigV5R1: + case ConfigV5R2: subwallet = 0 } @@ -190,7 +190,7 @@ func FromPrivateKey(api TonAPI, key ed25519.PrivateKey, version VersionConfig) ( func getSpec(w *Wallet) (any, error) { switch v := w.ver.(type) { - case Version, ConfigV5Beta, ConfigV5Final: + case Version, ConfigV5R1, ConfigV5R2: regular := SpecRegular{ wallet: w, messagesTTL: 60 * 3, // default ttl 3 min @@ -218,16 +218,16 @@ func getSpec(w *Wallet) (any, error) { } switch x := w.ver.(type) { - case ConfigV5Beta: + case ConfigV5R1: if x.NetworkGlobalID == 0 { - return nil, fmt.Errorf("NetworkGlobalID should be set in V5B config") + return nil, fmt.Errorf("NetworkGlobalID should be set in V5 config") } - return &SpecV5Beta{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil - case ConfigV5Final: + return &SpecV5R1{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil + case ConfigV5R2: if x.NetworkGlobalID == 0 { - return nil, fmt.Errorf("NetworkGlobalID should be set in V5F config") + return nil, fmt.Errorf("NetworkGlobalID should be set in V5 config") } - return &SpecV5Final{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil + return &SpecV5R2{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil } switch v { @@ -239,10 +239,10 @@ func getSpec(w *Wallet) (any, error) { return &SpecHighloadV2R2{regular, SpecQuery{}}, nil case HighloadV3: return nil, fmt.Errorf("use ConfigHighloadV3 for highload v3 spec") - case V5Beta: - return nil, fmt.Errorf("use ConfigV5Beta for v5b spec") - case V5Final: - return nil, fmt.Errorf("use ConfigV5Final for v5f spec") + case V5R1: + return nil, fmt.Errorf("use ConfigV5R1 for V5 spec") + case V5R2: + return nil, fmt.Errorf("use ConfigV5R2 for V5 spec") } case ConfigHighloadV3: return &SpecHighloadV3{wallet: w, config: v}, nil @@ -343,16 +343,16 @@ func (w *Wallet) PrepareExternalMessageForMany(ctx context.Context, withStateIni var msg *cell.Cell switch v := w.ver.(type) { - case Version, ConfigV5Beta, ConfigV5Final: - if _, ok := v.(ConfigV5Beta); ok { - v = V5Beta + case Version, ConfigV5R1, ConfigV5R2: + if _, ok := v.(ConfigV5R1); ok { + v = V5R1 } - if _, ok := v.(ConfigV5Final); ok { - v = V5Final + if _, ok := v.(ConfigV5R2); ok { + v = V5R2 } switch v { - case V3R2, V3R1, V4R2, V4R1, V5Beta, V5Final: + case V3R2, V3R1, V4R2, V4R1, V5R1, V5R2: msg, err = w.spec.(RegularBuilder).BuildMessage(ctx, !withStateInit, nil, messages) if err != nil { return nil, fmt.Errorf("build message err: %w", err) From d1d859039ccab5d21388865a16cd1be596ba68ab Mon Sep 17 00:00:00 2001 From: Totemancer Date: Thu, 4 Jul 2024 16:39:49 +0200 Subject: [PATCH 4/5] Add comments near the wallet versions --- ton/wallet/wallet.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index cdec418..cec1c75 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -41,8 +41,8 @@ const ( V3 = V3R2 V4R1 Version = 41 V4R2 Version = 42 - V5R1 Version = 51 - V5R2 Version = 52 + V5R1 Version = 51 // Beta W5 + V5R2 Version = 52 // Final W5 HighloadV2R2 Version = 122 HighloadV2Verified Version = 123 HighloadV3 Version = 300 From 1cbd66d36eebe0c7b25028282b7086f36ce67eef Mon Sep 17 00:00:00 2001 From: Totemancer Date: Fri, 5 Jul 2024 18:17:39 +0200 Subject: [PATCH 5/5] Finalized V5 Contract Integration --- ton/wallet/address.go | 42 ++++++++-------- ton/wallet/v5beta.go | 90 +++++++++++++++++++++++++++++++++ ton/wallet/v5r1.go | 111 +++++++++++++++++++++++++++++++---------- ton/wallet/v5r2.go | 112 ------------------------------------------ ton/wallet/wallet.go | 32 ++++++------ 5 files changed, 212 insertions(+), 175 deletions(-) create mode 100644 ton/wallet/v5beta.go delete mode 100644 ton/wallet/v5r2.go diff --git a/ton/wallet/address.go b/ton/wallet/address.go index adb304f..7d73d8a 100644 --- a/ton/wallet/address.go +++ b/ton/wallet/address.go @@ -55,17 +55,17 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin switch ver { case HighloadV3: return nil, fmt.Errorf("use ConfigHighloadV3 for highload v3 spec") + case V5Beta: + return nil, fmt.Errorf("use ConfigV5Beta for V5 spec") case V5R1: return nil, fmt.Errorf("use ConfigV5R1 for V5 spec") - case V5R2: - return nil, fmt.Errorf("use ConfigV5R2 for V5 spec") } case ConfigHighloadV3: ver = HighloadV3 + case ConfigV5Beta: + ver = V5Beta case ConfigV5R1: ver = V5R1 - case ConfigV5R2: - ver = V5R2 } code, ok := walletCode[ver] @@ -88,35 +88,35 @@ func GetStateInit(pubKey ed25519.PublicKey, version VersionConfig, subWallet uin MustStoreSlice(pubKey, 256). MustStoreDict(nil). // empty dict of plugins EndCell() - case V5R1: - config := version.(ConfigV5R1) + case V5Beta: + config := version.(ConfigV5Beta) data = cell.BeginCell(). - MustStoreUInt(0, 33). // seqno - MustStoreInt(int64(config.NetworkGlobalID), 32). - MustStoreInt(int64(config.Workchain), 8). - MustStoreUInt(0, 8). // version of v5 - MustStoreUInt(uint64(subWallet), 32). + MustStoreUInt(0, 33). // seqno + MustStoreInt(int64(config.NetworkGlobalID), 32). // network id + MustStoreInt(int64(config.Workchain), 8). // workchain + MustStoreUInt(0, 8). // version of v5 + MustStoreUInt(uint64(subWallet), 32). // default 0 MustStoreSlice(pubKey, 256). MustStoreDict(nil). // empty dict of plugins EndCell() - case V5R2: - config := version.(ConfigV5R2) + case V5R1: + config := version.(ConfigV5R1) // Create WalletId instance walletId := WalletId{ - NetworkGlobalID: config.NetworkGlobalID, + NetworkGlobalID: config.NetworkGlobalID, // -3 Testnet, -239 Mainnet WorkChain: config.Workchain, - SubwalletNumber: subWallet, - walletVersion: 0, + SubwalletNumber: uint16(subWallet), + WalletVersion: 0, // Wallet Version } data = cell.BeginCell(). - MustStoreBoolBit(true). - MustStoreUInt(0, 32). // seqno - MustStoreUInt(uint64(walletId.Serialized()), 32). // serialize - MustStoreSlice(pubKey, 256). - MustStoreDict(nil). // empty dict of plugins + MustStoreBoolBit(true). // storeUint(1, 1) - boolean flag for context type + MustStoreUInt(0, 32). // Sequence number, hardcoded as 0 + MustStoreUInt(uint64(walletId.Serialized()), 32). // Serializing WalletId into 32-bit integer + MustStoreSlice(pubKey, 256). // Storing the public key + MustStoreDict(nil). // Storing an empty plugins dictionary EndCell() case HighloadV2R2, HighloadV2Verified: data = cell.BeginCell(). diff --git a/ton/wallet/v5beta.go b/ton/wallet/v5beta.go new file mode 100644 index 0000000..99c07c4 --- /dev/null +++ b/ton/wallet/v5beta.go @@ -0,0 +1,90 @@ +package wallet + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/ton" + + "github.com/xssnick/tonutils-go/tvm/cell" +) + +// https://github.com/tonkeeper/tonkeeper-ton/commit/e8a7f3415e241daf4ac723f273fbc12776663c49#diff-c20d462b2e1ec616bbba2db39acc7a6c61edc3d5e768f5c2034a80169b1a56caR29 +const _V5BetaCodeHex = "b5ee9c7241010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f34120dcd8e11" + +type ConfigV5Beta struct { + NetworkGlobalID int32 + Workchain int8 +} + +type SpecV5Beta struct { + SpecRegular + SpecSeqno + + config ConfigV5Beta +} + +func (s *SpecV5Beta) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { + // TODO: remove block, now it is here for backwards compatibility + + if len(messages) > 255 { + return nil, errors.New("for this type of wallet max 4 messages can be sent in the same time") + } + + seq, err := s.seqnoFetcher(ctx, s.wallet.subwallet) + if err != nil { + return nil, fmt.Errorf("failed to fetch seqno: %w", err) + } + + actions, err := packV5BetaActions(messages) + if err != nil { + return nil, fmt.Errorf("failed to build actions: %w", err) + } + + payload := cell.BeginCell(). + MustStoreUInt(0x7369676e, 32). // external sign op code + MustStoreInt(int64(s.config.NetworkGlobalID), 32). + MustStoreInt(int64(s.config.Workchain), 8). + MustStoreUInt(0, 8). // version of v5 + MustStoreUInt(uint64(s.wallet.subwallet), 32). + MustStoreUInt(uint64(timeNow().Add(time.Duration(s.messagesTTL)*time.Second).UTC().Unix()), 32). + MustStoreUInt(uint64(seq), 32). + MustStoreBuilder(actions) + + sign := payload.EndCell().Sign(s.wallet.key) + msg := cell.BeginCell().MustStoreBuilder(payload).MustStoreSlice(sign, 512).EndCell() + + return msg, nil +} + +func packV5BetaActions(messages []*Message) (*cell.Builder, error) { + if len(messages) > 255 { + return nil, fmt.Errorf("max 255 messages allowed for v5") + } + + var list = cell.BeginCell().EndCell() + for _, message := range messages { + outMsg, err := tlb.ToCell(message.InternalMessage) + if err != nil { + return nil, err + } + + /* + out_list_empty$_ = OutList 0; + out_list$_ {n:#} prev:^(OutList n) action:OutAction + = OutList (n + 1); + action_send_msg#0ec3c86d mode:(## 8) + out_msg:^(MessageRelaxed Any) = OutAction; + */ + msg := cell.BeginCell().MustStoreUInt(0x0ec3c86d, 32). + MustStoreUInt(uint64(message.Mode), 8). + MustStoreRef(outMsg) + + list = cell.BeginCell().MustStoreRef(list).MustStoreBuilder(msg).EndCell() + } + + return cell.BeginCell().MustStoreUInt(0, 1).MustStoreRef(list), nil +} diff --git a/ton/wallet/v5r1.go b/ton/wallet/v5r1.go index 17f5983..9e57c1c 100644 --- a/ton/wallet/v5r1.go +++ b/ton/wallet/v5r1.go @@ -12,8 +12,9 @@ import ( "github.com/xssnick/tonutils-go/tvm/cell" ) -// https://github.com/tonkeeper/tonkeeper-ton/commit/e8a7f3415e241daf4ac723f273fbc12776663c49#diff-c20d462b2e1ec616bbba2db39acc7a6c61edc3d5e768f5c2034a80169b1a56caR29 -const _V5R1CodeHex = "b5ee9c7241010101002300084202e4cf3b2f4c6d6a61ea0f2b5447d266785b26af3637db2deee6bcd1aa826f34120dcd8e11" +// Contract source: +// https://github.com/ton-blockchain/wallet-contract-v5/blob/main/build/wallet_v5.compiled.json +const _V5R1CodeHex = "b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e" type ConfigV5R1 struct { NetworkGlobalID int32 @@ -27,11 +28,57 @@ type SpecV5R1 struct { config ConfigV5R1 } -func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { - // TODO: remove block, now it is here for backwards compatibility +// Source: https://github.com/tonkeeper/tonkeeper-ton/commit/d9aec6adfdb853eb37e0bba7453d83ae52e2a170#diff-c8ee60dec2f4e3ee55ad5e40f56fd9a104f21df78086a114d33d62e4fa0ffee6R139 +/* + * schema: + * wallet_id -- int32 + * wallet_id = global_id ^ context_id + * context_id_client$1 = wc:int8 wallet_version:uint8 counter:uint15 + * context_id_backoffice$0 = counter:uint31 + * + * + * calculated default values serialisation: + * + * global_id = -239, workchain = 0, wallet_version = 0', subwallet_number = 0 (client context) + * gives wallet_id = 2147483409 + * + * global_id = -239, workchain = -1, wallet_version = 0', subwallet_number = 0 (client context) + * gives wallet_id = 8388369 + * + * global_id = -3, workchain = 0, wallet_version = 0', subwallet_number = 0 (client context) + * gives wallet_id = 2147483645 + * + * global_id = -3, workchain = -1, wallet_version = 0', subwallet_number = 0 (client context) + * gives wallet_id = 8388605 + */ +// Function to generate the context ID based on the given workchain +func genContextID(workchain int8) uint32 { + var context uint32 + + // Convert workchain to uint32 after ensuring it's correctly handled as an 8-bit value + context |= 1 << 31 // Write 1 bit as 1 at the leftmost bit + context |= (uint32(workchain) & 0xFF) << 23 // Write 8 bits of workchain, shifted to position + context |= uint32(0) << 15 // Write 8 bits of 0 (wallet version) + context |= uint32(0) // Write 15 bits of 0 (subwallet number) + + return context +} + +type WalletId struct { + NetworkGlobalID int32 + WorkChain int8 + SubwalletNumber uint16 + WalletVersion uint8 +} +func (w WalletId) Serialized() uint32 { + context := genContextID(w.WorkChain) + return uint32(int32(context) ^ w.NetworkGlobalID) +} + +func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { if len(messages) > 255 { - return nil, errors.New("for this type of wallet max 4 messages can be sent in the same time") + return nil, errors.New("for this type of wallet max 255 messages can be sent at the same time") } seq, err := s.seqnoFetcher(ctx, s.wallet.subwallet) @@ -44,15 +91,19 @@ func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, return nil, fmt.Errorf("failed to build actions: %w", err) } + walletId := WalletId{ + NetworkGlobalID: s.config.NetworkGlobalID, + WorkChain: s.config.Workchain, + SubwalletNumber: uint16(s.wallet.subwallet), + WalletVersion: 0, + } + payload := cell.BeginCell(). - MustStoreUInt(0x7369676e, 32). // external sign op code - MustStoreInt(int64(s.config.NetworkGlobalID), 32). - MustStoreInt(int64(s.config.Workchain), 8). - MustStoreUInt(0, 8). // version of v5 - MustStoreUInt(uint64(s.wallet.subwallet), 32). - MustStoreUInt(uint64(timeNow().Add(time.Duration(s.messagesTTL)*time.Second).UTC().Unix()), 32). - MustStoreUInt(uint64(seq), 32). - MustStoreBuilder(actions) + MustStoreUInt(0x7369676e, 32). // external sign op code + MustStoreUInt(uint64(walletId.Serialized()), 32). // serialized WalletId + MustStoreUInt(uint64(time.Now().Add(time.Duration(s.messagesTTL)*time.Second).UTC().Unix()), 32). // validUntil + MustStoreUInt(uint64(seq), 32). // seq (block) + MustStoreBuilder(actions) // Action list sign := payload.EndCell().Sign(s.wallet.key) msg := cell.BeginCell().MustStoreBuilder(payload).MustStoreSlice(sign, 512).EndCell() @@ -60,9 +111,23 @@ func (s *SpecV5R1) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, return msg, nil } -func packV5R1Actions(messages []*Message) (*cell.Builder, error) { +// Validate messages +func validateMessageFields(messages []*Message) error { if len(messages) > 255 { - return nil, fmt.Errorf("max 255 messages allowed for v5") + return fmt.Errorf("max 255 messages allowed for v5") + } + for _, message := range messages { + if message.InternalMessage == nil { + return fmt.Errorf("internal message cannot be nil") + } + } + return nil +} + +// Pack Actions +func packV5R1Actions(messages []*Message) (*cell.Builder, error) { + if err := validateMessageFields(messages); err != nil { + return nil, err } var list = cell.BeginCell().EndCell() @@ -72,19 +137,13 @@ func packV5R1Actions(messages []*Message) (*cell.Builder, error) { return nil, err } - /* - out_list_empty$_ = OutList 0; - out_list$_ {n:#} prev:^(OutList n) action:OutAction - = OutList (n + 1); - action_send_msg#0ec3c86d mode:(## 8) - out_msg:^(MessageRelaxed Any) = OutAction; - */ - msg := cell.BeginCell().MustStoreUInt(0x0ec3c86d, 32). - MustStoreUInt(uint64(message.Mode), 8). - MustStoreRef(outMsg) + msg := cell.BeginCell().MustStoreUInt(0x0ec3c86d, 32). // action_send_msg prefix + MustStoreUInt(uint64(message.Mode), 8). // mode + MustStoreRef(outMsg) // message reference list = cell.BeginCell().MustStoreRef(list).MustStoreBuilder(msg).EndCell() } - return cell.BeginCell().MustStoreUInt(0, 1).MustStoreRef(list), nil + // Ensure the action list ends with 0, 1 as per the new specification + return cell.BeginCell().MustStoreUInt(1, 1).MustStoreRef(list).MustStoreUInt(0, 1), nil } diff --git a/ton/wallet/v5r2.go b/ton/wallet/v5r2.go deleted file mode 100644 index 1fe33e0..0000000 --- a/ton/wallet/v5r2.go +++ /dev/null @@ -1,112 +0,0 @@ -package wallet - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/xssnick/tonutils-go/tlb" - "github.com/xssnick/tonutils-go/ton" - - "github.com/xssnick/tonutils-go/tvm/cell" -) - -// https://github.com/ton-blockchain/wallet-contract-v5/blob/main/build/wallet_v5.compiled.json -const _V5R2CodeHex = "b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e" - -type ConfigV5R2 struct { - NetworkGlobalID int32 - Workchain int8 -} - -type SpecV5R2 struct { - SpecRegular - SpecSeqno - - config ConfigV5R2 -} - -type WalletId struct { - NetworkGlobalID int32 - WorkChain int8 - SubwalletNumber uint32 - walletVersion uint8 -} - -func (w WalletId) Serialized() uint32 { - // Serialize WalletId into a 32-bit integer - return uint32(w.NetworkGlobalID)<<24 | uint32(w.WorkChain)<<16 | w.SubwalletNumber -} - -func (s *SpecV5R2) BuildMessage(ctx context.Context, _ bool, _ *ton.BlockIDExt, messages []*Message) (_ *cell.Cell, err error) { - if len(messages) > 255 { - return nil, errors.New("for this type of wallet max 255 messages can be sent at the same time") - } - - seq, err := s.seqnoFetcher(ctx, s.wallet.subwallet) - if err != nil { - return nil, fmt.Errorf("failed to fetch seqno: %w", err) - } - - actions, err := packV5R2Actions(messages) - if err != nil { - return nil, fmt.Errorf("failed to build actions: %w", err) - } - - walletId := WalletId{ - NetworkGlobalID: s.config.NetworkGlobalID, - WorkChain: s.config.Workchain, - SubwalletNumber: s.wallet.subwallet, - walletVersion: 0, - } - - payload := cell.BeginCell(). - MustStoreUInt(0x7369676e, 32). // external sign op code - MustStoreUInt(uint64(walletId.Serialized()), 32). // serialized WalletId - MustStoreUInt(uint64(time.Now().Add(time.Duration(s.messagesTTL)*time.Second).UTC().Unix()), 32). // validUntil - MustStoreUInt(uint64(seq), 32). // seq (block) - MustStoreBuilder(actions) // Action list - - sign := payload.EndCell().Sign(s.wallet.key) - msg := cell.BeginCell().MustStoreBuilder(payload).MustStoreSlice(sign, 512).EndCell() - - return msg, nil -} - -// Validate messages -func validateMessageFields(messages []*Message) error { - if len(messages) > 255 { - return fmt.Errorf("max 255 messages allowed for v5") - } - for _, message := range messages { - if message.InternalMessage == nil { - return fmt.Errorf("internal message cannot be nil") - } - } - return nil -} - -// Pack Actions -func packV5R2Actions(messages []*Message) (*cell.Builder, error) { - if err := validateMessageFields(messages); err != nil { - return nil, err - } - - var list = cell.BeginCell().EndCell() - for _, message := range messages { - outMsg, err := tlb.ToCell(message.InternalMessage) - if err != nil { - return nil, err - } - - msg := cell.BeginCell().MustStoreUInt(0x0ec3c86d, 32). // action_send_msg prefix - MustStoreUInt(uint64(message.Mode), 8). // mode - MustStoreRef(outMsg) // message reference - - list = cell.BeginCell().MustStoreRef(list).MustStoreBuilder(msg).EndCell() - } - - // Ensure the action list ends with 0, 1 as per the new specification - return cell.BeginCell().MustStoreUInt(1, 1).MustStoreRef(list).MustStoreUInt(0, 1), nil -} diff --git a/ton/wallet/wallet.go b/ton/wallet/wallet.go index cec1c75..6e4e2cd 100644 --- a/ton/wallet/wallet.go +++ b/ton/wallet/wallet.go @@ -41,8 +41,8 @@ const ( V3 = V3R2 V4R1 Version = 41 V4R2 Version = 42 - V5R1 Version = 51 // Beta W5 - V5R2 Version = 52 // Final W5 + V5Beta Version = 51 // W5 Beta + V5R1 Version = 52 // W5 Final HighloadV2R2 Version = 122 HighloadV2Verified Version = 123 HighloadV3 Version = 300 @@ -85,8 +85,8 @@ var ( V2R1: _V2R1CodeHex, V2R2: _V2R2CodeHex, V3R1: _V3R1CodeHex, V3R2: _V3R2CodeHex, V4R1: _V4R1CodeHex, V4R2: _V4R2CodeHex, + V5Beta: _V5BetaCodeHex, V5R1: _V5R1CodeHex, - V5R2: _V5R2CodeHex, HighloadV2R2: _HighloadV2R2CodeHex, HighloadV2Verified: _HighloadV2VerifiedCodeHex, HighloadV3: _HighloadV3CodeHex, Lockup: _LockupCodeHex, @@ -162,8 +162,8 @@ func FromPrivateKey(api TonAPI, key ed25519.PrivateKey, version VersionConfig) ( // default subwallet depends on wallet type switch version.(type) { + case ConfigV5Beta: case ConfigV5R1: - case ConfigV5R2: subwallet = 0 } @@ -190,7 +190,7 @@ func FromPrivateKey(api TonAPI, key ed25519.PrivateKey, version VersionConfig) ( func getSpec(w *Wallet) (any, error) { switch v := w.ver.(type) { - case Version, ConfigV5R1, ConfigV5R2: + case Version, ConfigV5Beta, ConfigV5R1: regular := SpecRegular{ wallet: w, messagesTTL: 60 * 3, // default ttl 3 min @@ -218,16 +218,16 @@ func getSpec(w *Wallet) (any, error) { } switch x := w.ver.(type) { - case ConfigV5R1: + case ConfigV5Beta: if x.NetworkGlobalID == 0 { return nil, fmt.Errorf("NetworkGlobalID should be set in V5 config") } - return &SpecV5R1{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil - case ConfigV5R2: + return &SpecV5Beta{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil + case ConfigV5R1: if x.NetworkGlobalID == 0 { return nil, fmt.Errorf("NetworkGlobalID should be set in V5 config") } - return &SpecV5R2{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil + return &SpecV5R1{SpecRegular: regular, SpecSeqno: SpecSeqno{seqnoFetcher: seqnoFetcher}, config: x}, nil } switch v { @@ -239,10 +239,10 @@ func getSpec(w *Wallet) (any, error) { return &SpecHighloadV2R2{regular, SpecQuery{}}, nil case HighloadV3: return nil, fmt.Errorf("use ConfigHighloadV3 for highload v3 spec") + case V5Beta: + return nil, fmt.Errorf("use ConfigV5Beta for V5 spec") case V5R1: return nil, fmt.Errorf("use ConfigV5R1 for V5 spec") - case V5R2: - return nil, fmt.Errorf("use ConfigV5R2 for V5 spec") } case ConfigHighloadV3: return &SpecHighloadV3{wallet: w, config: v}, nil @@ -343,16 +343,16 @@ func (w *Wallet) PrepareExternalMessageForMany(ctx context.Context, withStateIni var msg *cell.Cell switch v := w.ver.(type) { - case Version, ConfigV5R1, ConfigV5R2: + case Version, ConfigV5Beta, ConfigV5R1: + if _, ok := v.(ConfigV5Beta); ok { + v = V5Beta + } if _, ok := v.(ConfigV5R1); ok { v = V5R1 } - if _, ok := v.(ConfigV5R2); ok { - v = V5R2 - } switch v { - case V3R2, V3R1, V4R2, V4R1, V5R1, V5R2: + case V3R2, V3R1, V4R2, V4R1, V5Beta, V5R1: msg, err = w.spec.(RegularBuilder).BuildMessage(ctx, !withStateInit, nil, messages) if err != nil { return nil, fmt.Errorf("build message err: %w", err)