From 3fdd48ae5c961565ee499f3f2208d28866655820 Mon Sep 17 00:00:00 2001 From: CrossAgent Date: Mon, 25 May 2026 16:46:09 +0800 Subject: [PATCH 1/2] Display multisig account info --- plugins/balance/dao/account.go | 56 +++++++++++++++++++++++++ plugins/balance/dao/account_test.go | 40 ++++++++++++++++++ plugins/balance/model/model.go | 6 +++ plugins/balance/service/service.go | 9 +++- ui-react/src/pages/sub/account/[id].tsx | 23 ++++++++++ ui-react/src/utils/api.ts | 2 + 6 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 plugins/balance/dao/account_test.go diff --git a/plugins/balance/dao/account.go b/plugins/balance/dao/account.go index a535a2e..aaad739 100644 --- a/plugins/balance/dao/account.go +++ b/plugins/balance/dao/account.go @@ -5,10 +5,12 @@ import ( "github.com/itering/subscan-plugin/storage" "github.com/itering/subscan/model" bModel "github.com/itering/subscan/plugins/balance/model" + "github.com/itering/subscan/util" "github.com/itering/subscan/util/address" "github.com/itering/substrate-api-rpc/rpc" "gorm.io/gorm" "gorm.io/gorm/clause" + "strings" ) func GetAccountListCursor(db storage.DB, limit int, before, after *uint) ([]bModel.Account, bool, bool) { @@ -58,6 +60,60 @@ func GetAccountByAddress(ctx context.Context, db storage.DB, address string) *bM } +func GetMultisigAccountInfo(ctx context.Context, db storage.DB, accountID string) (string, string) { + if accountID == "" { + return "", "" + } + d := db.GetDbInstance().(*gorm.DB) + currentBlock, err := db.GetCurrentBlockNum(ctx) + if err != nil { + return "", "" + } + accountID = strings.TrimPrefix(strings.ToLower(accountID), "0x") + maxTableIndex := int(currentBlock / uint64(model.SplitTableBlockNum)) + for index := maxTableIndex; index >= 0; index-- { + var events []model.ChainEvent + table := model.TableNameFromInterface(&model.ChainEvent{BlockNum: uint(index) * model.SplitTableBlockNum}, d) + q := d.WithContext(ctx). + Table(table). + Where("module_id = ?", "multisig"). + Where("event_id = ?", "NewMultisig"). + Order("id desc"). + Limit(200). + Find(&events) + if q.Error != nil { + continue + } + for _, event := range events { + if composer, ok := multisigComposerFromEvent(event, accountID); ok { + return "Multisig", composer + } + } + } + return "", "" +} + +func multisigComposerFromEvent(event model.ChainEvent, accountID string) (string, bool) { + var composer string + var matched bool + for _, param := range event.Params { + value := strings.TrimPrefix(strings.ToLower(util.ToString(param.Value)), "0x") + switch strings.ToLower(param.Name) { + case "approving": + composer = value + case "multisig": + matched = value == accountID + } + } + if !matched { + return "", false + } + if composer == "" { + return "", true + } + return address.Encode(composer), true +} + func RefreshAccount(ctx context.Context, s *Storage, accountId string) error { accountId = address.Format(accountId) if accountId == "" { diff --git a/plugins/balance/dao/account_test.go b/plugins/balance/dao/account_test.go new file mode 100644 index 0000000..672273e --- /dev/null +++ b/plugins/balance/dao/account_test.go @@ -0,0 +1,40 @@ +package dao + +import ( + "testing" + + "github.com/itering/subscan/model" + "github.com/itering/subscan/util" + "github.com/stretchr/testify/assert" +) + +func TestMultisigComposerFromEvent(t *testing.T) { + originalAddressType := util.AddressType + t.Cleanup(func() { util.AddressType = originalAddressType }) + util.AddressType = "31" + event := model.ChainEvent{ + Params: model.EventParams{ + {Name: "approving", Value: "0x9c6c86c4936b94ae7772d1045e8f4e36690c84bb6df01c49e167de90902d0817"}, + {Name: "multisig", Value: "0x8898bfb77f32226ec1990ed415ce0f215b05e3d5a872a4e4f4e6a4718be04f85"}, + {Name: "call_hash", Value: "0xf2472a3093ba21e07c3ed2938da1158e3d03d1f9527b2085180288a804317a21"}, + }, + } + + composer, ok := multisigComposerFromEvent(event, "8898bfb77f32226ec1990ed415ce0f215b05e3d5a872a4e4f4e6a4718be04f85") + + assert.True(t, ok) + assert.Equal(t, "49wYitCNCSrF2swm88DekiF1BQmyi3K5sna3SGjDZ78fsLaL", composer) +} + +func TestMultisigComposerFromEventIgnoresOtherAccount(t *testing.T) { + event := model.ChainEvent{ + Params: model.EventParams{ + {Name: "approving", Value: "0x9c6c86c4936b94ae7772d1045e8f4e36690c84bb6df01c49e167de90902d0817"}, + {Name: "multisig", Value: "0x8898bfb77f32226ec1990ed415ce0f215b05e3d5a872a4e4f4e6a4718be04f85"}, + }, + } + + _, ok := multisigComposerFromEvent(event, "dbc968c19add01bc24568a02b7d02c68e98965fe7df757d8d2733f82bf3aa941") + + assert.False(t, ok) +} diff --git a/plugins/balance/model/model.go b/plugins/balance/model/model.go index 5809908..2fdb01c 100644 --- a/plugins/balance/model/model.go +++ b/plugins/balance/model/model.go @@ -12,6 +12,12 @@ type Account struct { Vested decimal.Decimal `json:"vested" gorm:"type:decimal(65,0);"` } +type AccountJson struct { + Account + AccountType string `json:"account_type,omitempty"` + MultisigComposer string `json:"multisig_composer,omitempty"` +} + func (a *Account) TableName() string { return "balance_accounts" } diff --git a/plugins/balance/service/service.go b/plugins/balance/service/service.go index d2265c6..036c108 100644 --- a/plugins/balance/service/service.go +++ b/plugins/balance/service/service.go @@ -33,13 +33,18 @@ func (s *Service) GetAccountListCursor(_ context.Context, limit int, before, aft } } -func (s *Service) GetAccountJson(ctx context.Context, addr string) *model.Account { +func (s *Service) GetAccountJson(ctx context.Context, addr string) *model.AccountJson { account := dao.GetAccountByAddress(ctx, s.d, addr) if account == nil { return nil } account.Address = address.Encode(account.Address) - return account + accountType, composer := dao.GetMultisigAccountInfo(ctx, s.d, addr) + return &model.AccountJson{ + Account: *account, + AccountType: accountType, + MultisigComposer: composer, + } } func (s *Service) GetTransferCursor(ctx context.Context, addr string, blockNum uint, limit int, before, after *uint) ([]model.Transfer, map[string]interface{}) { diff --git a/ui-react/src/pages/sub/account/[id].tsx b/ui-react/src/pages/sub/account/[id].tsx index 17bb35d..5db23b8 100644 --- a/ui-react/src/pages/sub/account/[id].tsx +++ b/ui-react/src/pages/sub/account/[id].tsx @@ -11,6 +11,7 @@ import { BIG_ZERO } from '@/utils/const' import { Container, PageContent } from '@/ui' import { env } from 'next-runtime-env' import { LoadingSpinner, LoadingText } from '@/components/loading' +import { Link } from '@/components/link' export default function Page() { const router = useRouter() @@ -54,6 +55,28 @@ export default function Page() { + {accountData.account_type && ( + <> +
+
Account Type
+
{accountData.account_type}
+
+ + + )} + {accountData.multisig_composer && ( + <> +
+
Multisig Composer
+
+ + {accountData.multisig_composer} + +
+
+ + + )}
Total Balance
{getBalanceAmount(new BigNumber(accountData.balance), token?.decimals).toFormat()}
diff --git a/ui-react/src/utils/api.ts b/ui-react/src/utils/api.ts index 29c5d06..3ba3c00 100644 --- a/ui-react/src/utils/api.ts +++ b/ui-react/src/utils/api.ts @@ -257,6 +257,8 @@ export type accountType = { nonce: string reserved: string vested?: string + account_type?: string + multisig_composer?: string } type getAccountParams = { From ddf877b9abfbcdc070c15401aaf2508127a08251 Mon Sep 17 00:00:00 2001 From: CrossAgent Date: Tue, 26 May 2026 10:38:46 +0800 Subject: [PATCH 2/2] Make multisig composer address readable --- ui-react/src/pages/sub/account/[id].tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ui-react/src/pages/sub/account/[id].tsx b/ui-react/src/pages/sub/account/[id].tsx index 5db23b8..16e39e8 100644 --- a/ui-react/src/pages/sub/account/[id].tsx +++ b/ui-react/src/pages/sub/account/[id].tsx @@ -53,23 +53,26 @@ export default function Page() {
Account
#{accountData.address}
- + {accountData.account_type && ( <> -
-
Account Type
-
{accountData.account_type}
+
+
Account Type
+
{accountData.account_type}
)} {accountData.multisig_composer && ( <> -
-
Multisig Composer
-
- +
+
Multisig Composer
+
+ {accountData.multisig_composer}