From b310abdbb5ad5ec7e9dedab971058fd8bb80a5a5 Mon Sep 17 00:00:00 2001 From: domikedos Date: Fri, 27 Jun 2025 12:28:51 +0600 Subject: [PATCH 1/2] implement airdrop claim bath --- pkg/bath/actions.go | 8 +++ pkg/bath/airdrop.go | 85 ++++++++++++++++++++++++++++ pkg/bath/bath_test.go | 10 +++- pkg/bath/straws.go | 1 + pkg/bath/testdata/claim-airdrop.json | 81 ++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 pkg/bath/airdrop.go create mode 100644 pkg/bath/testdata/claim-airdrop.json diff --git a/pkg/bath/actions.go b/pkg/bath/actions.go index ef99fc074..a98a4dadb 100644 --- a/pkg/bath/actions.go +++ b/pkg/bath/actions.go @@ -40,6 +40,7 @@ const ( DomainRenew ActionType = "DomainRenew" Purchase ActionType = "Purchase" Unknown ActionType = "Unknown" + AirdropClaim ActionType = "AirdropClaim" ) type ActionType string @@ -85,6 +86,7 @@ type ( JettonSwap *JettonSwapAction `json:",omitempty"` DnsRenew *DnsRenewAction `json:",omitempty"` Purchase *PurchaseAction `json:",omitempty"` + AirdropClaim *AirdropClaimAction `json:",omitempty"` Success bool Type ActionType Error *string `json:",omitempty"` @@ -226,6 +228,12 @@ type ( InvoiceID uuid.UUID Price core.Price } + + AirdropClaimAction struct { + Distributor tongo.AccountID + Recipient *tongo.AccountID + Claimed assetTransfer + } ) func (a Action) String() string { diff --git a/pkg/bath/airdrop.go b/pkg/bath/airdrop.go new file mode 100644 index 000000000..021f14222 --- /dev/null +++ b/pkg/bath/airdrop.go @@ -0,0 +1,85 @@ +package bath + +import ( + "fmt" + "github.com/tonkeeper/tongo" + "github.com/tonkeeper/tongo/abi" + "math/big" +) + +type BubbleAirdropClaim struct { + Distributor tongo.AccountID + Recipient *Account + RecipientJettonWallet tongo.AccountID + ClaimedAmount big.Int + JettonMaster tongo.AccountID + Success bool +} + +func (b BubbleAirdropClaim) ToAction() *Action { + return &Action{ + AirdropClaim: &AirdropClaimAction{ + Distributor: b.Distributor, + Recipient: b.Recipient.Addr(), + Claimed: assetTransfer{ + Amount: b.ClaimedAmount, + JettonMaster: b.JettonMaster, + JettonWallet: b.RecipientJettonWallet, + }, + }, + Type: AirdropClaim, + Success: b.Success, + } +} + +var AirdropClaimStraw = Straw[BubbleAirdropClaim]{ + CheckFuncs: []bubbleCheck{ + IsTx, + Or(HasInterface(abi.AirdropInterlockerV1), HasInterface(abi.AirdropInterlockerV2)), // todo find trace with interface airdrop_interlocker_v2 + // todo add here operation with opcode 0x3a86f1a0 + }, + Builder: func(newAction *BubbleAirdropClaim, bubble *Bubble) error { + tx := bubble.Info.(BubbleTx) + newAction.Recipient = tx.inputFrom + + return nil + }, + SingleChild: &Straw[BubbleAirdropClaim]{ + CheckFuncs: []bubbleCheck{ + IsTx, + }, + Builder: func(newAction *BubbleAirdropClaim, bubble *Bubble) error { + tx := bubble.Info.(BubbleTx) + newAction.Distributor = tx.account.Address + + return nil + }, + SingleChild: &Straw[BubbleAirdropClaim]{ + CheckFuncs: []bubbleCheck{ + IsTx, + HasInterface(abi.JettonWallet), + HasOperation(abi.JettonTransferMsgOp), + }, + SingleChild: &Straw[BubbleAirdropClaim]{ + CheckFuncs: []bubbleCheck{ + IsTx, + HasInterface(abi.JettonWallet), + HasOperation(abi.JettonInternalTransferMsgOp), + }, + Builder: func(newAction *BubbleAirdropClaim, bubble *Bubble) error { + tx := bubble.Info.(BubbleTx) + newAction.RecipientJettonWallet = tx.account.Address + body := tx.decodedBody.Value.(abi.JettonInternalTransferMsgBody) + newAction.ClaimedAmount = big.Int(body.Amount) + master, ok := tx.additionalInfo.JettonMaster(tx.account.Address) + if !ok { + return fmt.Errorf("cannot find jetton master") + } + newAction.JettonMaster = master + + return nil + }, + }, + }, + }, +} diff --git a/pkg/bath/bath_test.go b/pkg/bath/bath_test.go index e4235c980..a7dedfa4e 100644 --- a/pkg/bath/bath_test.go +++ b/pkg/bath/bath_test.go @@ -176,6 +176,10 @@ func TestFindActions(t *testing.T) { tongo.MustParseBlockID("(0,2000000000000000,46145069)"), tongo.MustParseBlockID("(0,6000000000000000,46151880)"), tongo.MustParseBlockID("(0,2000000000000000,46145074)"), + // airdrop claim + tongo.MustParseBlockID("(0,e000000000000000,54137798)"), + tongo.MustParseBlockID("(0,2000000000000000,54479001)"), + tongo.MustParseBlockID("(0,a000000000000000,54118958)"), }), ) @@ -289,9 +293,9 @@ func TestFindActions(t *testing.T) { filenamePrefix: "stonfi-purchase-jUSDT", }, { - name: "failed stonfi swap", - hash: "77f5acb88fd863e9c00a164eb551ef83c17088d1f603bf463f64f952b93406b0", - filenamePrefix: "stonfi-failed-swap", + name: "claim airdrop", + hash: "5da05a55dea13d511097daff8fbd227a7c2d04fb583552cd5c75f2bd56ae8266", + filenamePrefix: "claim-airdrop", }, { name: "deploy contract action", diff --git a/pkg/bath/straws.go b/pkg/bath/straws.go index c47c9be46..631557dce 100644 --- a/pkg/bath/straws.go +++ b/pkg/bath/straws.go @@ -37,6 +37,7 @@ var SubscriptionStraws = []Merger{ } var DefaultStraws = []Merger{ + AirdropClaimStraw, StrawFindAuctionBidFragmentSimple, NftTransferStraw, NftTransferNotifyStraw, diff --git a/pkg/bath/testdata/claim-airdrop.json b/pkg/bath/testdata/claim-airdrop.json new file mode 100644 index 000000000..35b424867 --- /dev/null +++ b/pkg/bath/testdata/claim-airdrop.json @@ -0,0 +1,81 @@ +{ + "Actions": [ + { + "AirdropClaim": { + "Distributor": "0:9e32eb8b51bd1d9c4c6a5da85f8c88a58649ab287863d70f421b2b954edd38da", + "Recipient": "0:c05d61797f2c1ef2fcf0b6c97239ed9c525fcf59107d97dd6c7913ed1d343a13", + "Claimed": { + "Amount": 1270000000000, + "IsTon": false, + "JettonMaster": "0:1477d9dd4a416f50e82a10b4b3fac7e7e57cd37fce13fbfc33885fa36c108005", + "JettonWallet": "0:972bb4e6c35f45317096cae4f97bcc85e9df1fe6328714f3487772381fb11fc8" + } + }, + "Success": false, + "Type": "AirdropClaim", + "BaseTransactions": [ + "2f9db7f85937f9053248f6111ccf78996e3f401e8db09e176addf823c8063888", + "f448fe47d5595f2b67abd51bd6149cbdf16bd676bc92a5ff70a8b4f73ebd89b5", + "52f7eb7c0073f18e96788e4c5591cf93fd145830d40fb8fcd26905164d3946e7", + "7e0df449e50d0eccb95bc9dab88548e5e8ad56e0a9bb6d88685ddcff62112f37" + ] + }, + { + "ContractDeploy": { + "Address": "0:11b67015bcfffa5b7ba5ce501bb0488ee427b5fe24bdd90f33190b421dc13892", + "Interfaces": null + }, + "Success": true, + "Type": "ContractDeploy", + "BaseTransactions": [ + "f448fe47d5595f2b67abd51bd6149cbdf16bd676bc92a5ff70a8b4f73ebd89b5" + ] + }, + { + "SmartContractExec": { + "TonAttached": 123146000, + "Executor": "0:972bb4e6c35f45317096cae4f97bcc85e9df1fe6328714f3487772381fb11fc8", + "Contract": "0:9e32eb8b51bd1d9c4c6a5da85f8c88a58649ab287863d70f421b2b954edd38da", + "Operation": "Excess", + "Payload": "QueryId: 27950\n" + }, + "Success": true, + "Type": "SmartContractExec", + "BaseTransactions": [ + "f6a3ee672260d913bfd3ee5c6166a80914131a6effd61ebeb93614ade8c82914" + ] + } + ], + "Accounts": [ + { + "Account": "0:11b67015bcfffa5b7ba5ce501bb0488ee427b5fe24bdd90f33190b421dc13892", + "Ton": 0, + "Fee": 3929200, + "Jettons": null + }, + { + "Account": "0:90950a41c7471a3ec9992e2b1c61df7bd636aec56b294f64591a1047940cf479", + "Ton": -109, + "Fee": 7562109, + "Jettons": null + }, + { + "Account": "0:972bb4e6c35f45317096cae4f97bcc85e9df1fe6328714f3487772381fb11fc8", + "Ton": 6889091, + "Fee": 3510909, + "Jettons": null + }, + { + "Account": "0:9e32eb8b51bd1d9c4c6a5da85f8c88a58649ab287863d70f421b2b954edd38da", + "Ton": 121167720, + "Fee": 6941080, + "Jettons": null + }, + { + "Account": "0:c05d61797f2c1ef2fcf0b6c97239ed9c525fcf59107d97dd6c7913ed1d343a13", + "Ton": -159632004, + "Fee": 9632004, + "Jettons": null + } + ] + } \ No newline at end of file From 5cf68a5d139101e8504f972087d4281c0acdb732 Mon Sep 17 00:00:00 2001 From: domikedos Date: Fri, 27 Jun 2025 19:17:39 +0600 Subject: [PATCH 2/2] fix airdrop straw --- pkg/bath/airdrop.go | 51 +++++++++------------------- pkg/bath/straws.go | 2 +- pkg/bath/testdata/claim-airdrop.json | 42 ++++++++--------------- 3 files changed, 31 insertions(+), 64 deletions(-) diff --git a/pkg/bath/airdrop.go b/pkg/bath/airdrop.go index 021f14222..00933c545 100644 --- a/pkg/bath/airdrop.go +++ b/pkg/bath/airdrop.go @@ -1,9 +1,7 @@ package bath import ( - "fmt" "github.com/tonkeeper/tongo" - "github.com/tonkeeper/tongo/abi" "math/big" ) @@ -33,50 +31,33 @@ func (b BubbleAirdropClaim) ToAction() *Action { } var AirdropClaimStraw = Straw[BubbleAirdropClaim]{ - CheckFuncs: []bubbleCheck{ - IsTx, - Or(HasInterface(abi.AirdropInterlockerV1), HasInterface(abi.AirdropInterlockerV2)), // todo find trace with interface airdrop_interlocker_v2 - // todo add here operation with opcode 0x3a86f1a0 - }, + CheckFuncs: []bubbleCheck{IsTx, HasOpcode(0x3a86f1a0)}, // todo use const from tongo.abi Builder: func(newAction *BubbleAirdropClaim, bubble *Bubble) error { tx := bubble.Info.(BubbleTx) newAction.Recipient = tx.inputFrom - return nil }, - SingleChild: &Straw[BubbleAirdropClaim]{ - CheckFuncs: []bubbleCheck{ - IsTx, - }, - Builder: func(newAction *BubbleAirdropClaim, bubble *Bubble) error { - tx := bubble.Info.(BubbleTx) - newAction.Distributor = tx.account.Address - - return nil + Children: []Straw[BubbleAirdropClaim]{ + { + CheckFuncs: []bubbleCheck{Is(BubbleContractDeploy{})}, }, - SingleChild: &Straw[BubbleAirdropClaim]{ + { CheckFuncs: []bubbleCheck{ IsTx, - HasInterface(abi.JettonWallet), - HasOperation(abi.JettonTransferMsgOp), + }, + Builder: func(newAction *BubbleAirdropClaim, bubble *Bubble) error { + tx := bubble.Info.(BubbleTx) + newAction.Distributor = tx.account.Address + return nil }, SingleChild: &Straw[BubbleAirdropClaim]{ - CheckFuncs: []bubbleCheck{ - IsTx, - HasInterface(abi.JettonWallet), - HasOperation(abi.JettonInternalTransferMsgOp), - }, + CheckFuncs: []bubbleCheck{IsJettonTransfer}, Builder: func(newAction *BubbleAirdropClaim, bubble *Bubble) error { - tx := bubble.Info.(BubbleTx) - newAction.RecipientJettonWallet = tx.account.Address - body := tx.decodedBody.Value.(abi.JettonInternalTransferMsgBody) - newAction.ClaimedAmount = big.Int(body.Amount) - master, ok := tx.additionalInfo.JettonMaster(tx.account.Address) - if !ok { - return fmt.Errorf("cannot find jetton master") - } - newAction.JettonMaster = master - + tx := bubble.Info.(BubbleJettonTransfer) + newAction.RecipientJettonWallet = tx.recipientWallet + newAction.JettonMaster = tx.master + newAction.ClaimedAmount = big.Int(tx.amount) + newAction.Success = tx.success && newAction.Recipient.Address == tx.recipient.Address return nil }, }, diff --git a/pkg/bath/straws.go b/pkg/bath/straws.go index 631557dce..234c9abf3 100644 --- a/pkg/bath/straws.go +++ b/pkg/bath/straws.go @@ -37,7 +37,6 @@ var SubscriptionStraws = []Merger{ } var DefaultStraws = []Merger{ - AirdropClaimStraw, StrawFindAuctionBidFragmentSimple, NftTransferStraw, NftTransferNotifyStraw, @@ -75,6 +74,7 @@ var DefaultStraws = []Merger{ WithdrawStakeImmediatelyStraw, WithdrawLiquidStake, DNSRenewStraw, + AirdropClaimStraw, } var JettonTransferClassicStraw = Straw[BubbleJettonTransfer]{ diff --git a/pkg/bath/testdata/claim-airdrop.json b/pkg/bath/testdata/claim-airdrop.json index 35b424867..af8612b72 100644 --- a/pkg/bath/testdata/claim-airdrop.json +++ b/pkg/bath/testdata/claim-airdrop.json @@ -11,39 +11,15 @@ "JettonWallet": "0:972bb4e6c35f45317096cae4f97bcc85e9df1fe6328714f3487772381fb11fc8" } }, - "Success": false, + "Success": true, "Type": "AirdropClaim", "BaseTransactions": [ "2f9db7f85937f9053248f6111ccf78996e3f401e8db09e176addf823c8063888", + "f6a3ee672260d913bfd3ee5c6166a80914131a6effd61ebeb93614ade8c82914", "f448fe47d5595f2b67abd51bd6149cbdf16bd676bc92a5ff70a8b4f73ebd89b5", "52f7eb7c0073f18e96788e4c5591cf93fd145830d40fb8fcd26905164d3946e7", "7e0df449e50d0eccb95bc9dab88548e5e8ad56e0a9bb6d88685ddcff62112f37" ] - }, - { - "ContractDeploy": { - "Address": "0:11b67015bcfffa5b7ba5ce501bb0488ee427b5fe24bdd90f33190b421dc13892", - "Interfaces": null - }, - "Success": true, - "Type": "ContractDeploy", - "BaseTransactions": [ - "f448fe47d5595f2b67abd51bd6149cbdf16bd676bc92a5ff70a8b4f73ebd89b5" - ] - }, - { - "SmartContractExec": { - "TonAttached": 123146000, - "Executor": "0:972bb4e6c35f45317096cae4f97bcc85e9df1fe6328714f3487772381fb11fc8", - "Contract": "0:9e32eb8b51bd1d9c4c6a5da85f8c88a58649ab287863d70f421b2b954edd38da", - "Operation": "Excess", - "Payload": "QueryId: 27950\n" - }, - "Success": true, - "Type": "SmartContractExec", - "BaseTransactions": [ - "f6a3ee672260d913bfd3ee5c6166a80914131a6effd61ebeb93614ade8c82914" - ] } ], "Accounts": [ @@ -69,13 +45,23 @@ "Account": "0:9e32eb8b51bd1d9c4c6a5da85f8c88a58649ab287863d70f421b2b954edd38da", "Ton": 121167720, "Fee": 6941080, - "Jettons": null + "Jettons": [ + { + "Address": "0:1477d9dd4a416f50e82a10b4b3fac7e7e57cd37fce13fbfc33885fa36c108005", + "Quantity": -1270000000000 + } + ] }, { "Account": "0:c05d61797f2c1ef2fcf0b6c97239ed9c525fcf59107d97dd6c7913ed1d343a13", "Ton": -159632004, "Fee": 9632004, - "Jettons": null + "Jettons": [ + { + "Address": "0:1477d9dd4a416f50e82a10b4b3fac7e7e57cd37fce13fbfc33885fa36c108005", + "Quantity": 1270000000000 + } + ] } ] } \ No newline at end of file