pump usdc support
This commit is contained in:
@@ -14,7 +14,7 @@ func main() {
|
||||
const rpcURL = "https://staked.helius-rpc.com?api-key=5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"
|
||||
txHash := os.Getenv("TX_HASH")
|
||||
if txHash == "" {
|
||||
txHash = "2AhpL5KhVmG3D38CwMzrHuRyTucEQ43GzBXL2mo5WiugdZMVmK1dtX8brGe3sxvvFDY6iSSviJTvqCtr4UL3Pc7J"
|
||||
txHash = "29v7u2ewLr3Se6cWYC2xwN8jszqMWwvVgPz7MqkctTveMo1csWWYDBcUsjuJwb5ciugc5so1jc9QcmR7syJTjEns"
|
||||
}
|
||||
|
||||
if txHash == "" {
|
||||
|
||||
6
meta.go
6
meta.go
@@ -20,12 +20,16 @@ var mayhemFeeAccounts = []solana.PublicKey{
|
||||
|
||||
var pumpGetFeesDiscriminator = calculateDiscriminator("global:get_fees")
|
||||
var pumpBuyDiscriminator = calculateDiscriminator("global:buy")
|
||||
var pumpBuyV2Discriminator = calculateDiscriminator("global:buy_exact_sol_in")
|
||||
var pumpBuyExactSolInDiscriminator = calculateDiscriminator("global:buy_exact_sol_in")
|
||||
var pumpBuyV2Discriminator = calculateDiscriminator("global:buy_v2")
|
||||
var pumpBuyExactQuoteInV2Discriminator = calculateDiscriminator("global:buy_exact_quote_in_v2")
|
||||
var pumpSellDiscriminator = calculateDiscriminator("global:sell")
|
||||
var pumpSellV2Discriminator = calculateDiscriminator("global:sell_v2")
|
||||
var pumpCreateDiscriminator = calculateDiscriminator("global:create")
|
||||
var pumpCreateV2Discriminator = calculateDiscriminator("global:create_v2")
|
||||
var pumpAdminSetCreatorDiscriminator = calculateDiscriminator("global:admin_set_creator")
|
||||
var pumpMigrateDiscriminator = calculateDiscriminator("global:migrate")
|
||||
var pumpMigrateV2Discriminator = calculateDiscriminator("global:migrate_v2")
|
||||
|
||||
var pumpEventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}
|
||||
var pumpTradeEventDiscriminator = [16]byte{228, 69, 165, 46, 81, 203, 154, 29, 189, 219, 127, 211, 78, 230, 97, 238}
|
||||
|
||||
648
pump.go
648
pump.go
@@ -33,7 +33,7 @@ func pumpParser(tx *Tx, instruction Instruction, innerInstructions InnerInstruct
|
||||
discriminator := *(*[8]byte)(decode[:8])
|
||||
|
||||
switch discriminator {
|
||||
case pumpBuyV2Discriminator, pumpBuyDiscriminator, pumpSellDiscriminator:
|
||||
case pumpBuyExactSolInDiscriminator, pumpBuyDiscriminator, pumpBuyV2Discriminator, pumpBuyExactQuoteInV2Discriminator, pumpSellDiscriminator, pumpSellV2Discriminator:
|
||||
if tx.Err != nil {
|
||||
return failedTxBuyOrSellParser(tx, instruction, innerInstructions, offset)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func pumpParser(tx *Tx, instruction Instruction, innerInstructions InnerInstruct
|
||||
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||
}
|
||||
return CreateParser(tx, instruction, innerInstructions, offset)
|
||||
case pumpMigrateDiscriminator:
|
||||
case pumpMigrateDiscriminator, pumpMigrateV2Discriminator:
|
||||
if tx.Err != nil {
|
||||
return nil, increaseOffset(offset), InstructionIgnoredError
|
||||
}
|
||||
@@ -89,6 +89,56 @@ type PumpCreateEvent struct {
|
||||
TokenProgram solana.PublicKey
|
||||
IsMayhemMode bool
|
||||
IsCashbackEnabled bool
|
||||
QuoteMint solana.PublicKey
|
||||
VirtualQuoteReserves uint64
|
||||
}
|
||||
|
||||
type pumpCreateEventLegacy struct {
|
||||
Name string
|
||||
Symbol string
|
||||
Uri string
|
||||
|
||||
Mint solana.PublicKey
|
||||
BondingCurve solana.PublicKey
|
||||
User solana.PublicKey
|
||||
Creator solana.PublicKey
|
||||
|
||||
Timestamp int64
|
||||
VirtualTokenReserves uint64
|
||||
VirtualSolReserves uint64
|
||||
RealTokenReserves uint64
|
||||
TokenTotalSupply uint64
|
||||
TokenProgram solana.PublicKey
|
||||
IsMayhemMode bool
|
||||
IsCashbackEnabled bool
|
||||
}
|
||||
|
||||
func decodePumpCreateEvent(data []byte) (PumpCreateEvent, error) {
|
||||
var event PumpCreateEvent
|
||||
if err := agbinary.NewBorshDecoder(data).Decode(&event); err == nil {
|
||||
return event, nil
|
||||
}
|
||||
var legacy pumpCreateEventLegacy
|
||||
if err := agbinary.NewBorshDecoder(data).Decode(&legacy); err != nil {
|
||||
return PumpCreateEvent{}, err
|
||||
}
|
||||
return PumpCreateEvent{
|
||||
Name: legacy.Name,
|
||||
Symbol: legacy.Symbol,
|
||||
Uri: legacy.Uri,
|
||||
Mint: legacy.Mint,
|
||||
BondingCurve: legacy.BondingCurve,
|
||||
User: legacy.User,
|
||||
Creator: legacy.Creator,
|
||||
Timestamp: legacy.Timestamp,
|
||||
VirtualTokenReserves: legacy.VirtualTokenReserves,
|
||||
VirtualSolReserves: legacy.VirtualSolReserves,
|
||||
RealTokenReserves: legacy.RealTokenReserves,
|
||||
TokenTotalSupply: legacy.TokenTotalSupply,
|
||||
TokenProgram: legacy.TokenProgram,
|
||||
IsMayhemMode: legacy.IsMayhemMode,
|
||||
IsCashbackEnabled: legacy.IsCashbackEnabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
@@ -106,7 +156,7 @@ func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions
|
||||
}
|
||||
for innerIndex, innerInstr := range inners {
|
||||
if innerInstr.ProgramIDIndex == programIndex && bytes.Equal(innerInstr.Data[:8], pumpEventDiscriminator[:]) && bytes.Equal(innerInstr.Data[8:16], pumpCreateEventDiscriminator[:]) {
|
||||
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&createEvent)
|
||||
createEvent, err = decodePumpCreateEvent(innerInstr.Data[16:])
|
||||
if offset[1] == 0 {
|
||||
offset[0] += 1
|
||||
} else {
|
||||
@@ -129,6 +179,11 @@ func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions
|
||||
}
|
||||
userBase := getAccountBalanceAfterTx(result, userIndex)
|
||||
userQuote, _ := GetSolAfterTx(result, userIndex)
|
||||
quoteMint, quoteTokenProgram, quoteDecimals := pumpCreateQuoteAccounts(result, instr, createEvent)
|
||||
userQuoteBalance := decimal.NewFromUint64(userQuote)
|
||||
if !quoteMint.IsZero() && !quoteMint.Equals(wSolMint) {
|
||||
userQuoteBalance = GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
|
||||
}
|
||||
|
||||
totalSupply := decimal.NewFromUint64(createEvent.TokenTotalSupply).Div(decimal.New(1, 6))
|
||||
tx.Token[createEvent.Mint] = TokenMeta{
|
||||
@@ -146,12 +201,12 @@ func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions
|
||||
Event: "create",
|
||||
Pool: createEvent.BondingCurve,
|
||||
BaseMint: createEvent.Mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: createEvent.TokenProgram,
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
Creator: createEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
QuoteMintDecimals: quoteDecimals,
|
||||
User: createEvent.User,
|
||||
BaseAmount: decimal.Zero,
|
||||
QuoteAmount: decimal.Zero,
|
||||
@@ -160,7 +215,7 @@ func CreateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions
|
||||
Mayhem: createEvent.IsMayhemMode,
|
||||
Cashback: createEvent.IsCashbackEnabled,
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
UserQuoteBalance: userQuoteBalance,
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}, offset, nil
|
||||
@@ -197,6 +252,141 @@ type PumpTradeEvent struct {
|
||||
MayhemMode bool
|
||||
CashbackFeeBasisPoints uint64
|
||||
Cashback uint64
|
||||
BuybackFeeBasisPoints uint64
|
||||
BuybackFee uint64
|
||||
Shareholders []PumpShareholder
|
||||
QuoteMint solana.PublicKey
|
||||
QuoteAmount uint64
|
||||
VirtualQuoteReserves uint64
|
||||
RealQuoteReserves uint64
|
||||
}
|
||||
|
||||
type PumpShareholder struct {
|
||||
Address solana.PublicKey
|
||||
ShareBps uint16
|
||||
}
|
||||
|
||||
type pumpTradeEventLegacy struct {
|
||||
Mint solana.PublicKey
|
||||
SolAmount uint64
|
||||
TokenAmount uint64
|
||||
IsBuy bool
|
||||
User solana.PublicKey
|
||||
Timestamp int64
|
||||
VirtualSolReserves uint64
|
||||
VirtualTokenReserves uint64
|
||||
|
||||
RealSolReserves uint64
|
||||
RealTokenReserves uint64
|
||||
|
||||
FeeRecipient solana.PublicKey
|
||||
FeeBasisPoints uint64
|
||||
Fee uint64
|
||||
|
||||
Creator solana.PublicKey
|
||||
|
||||
CreatorFeeBasisPoints uint64
|
||||
CreatorFee uint64
|
||||
|
||||
TrackVolume bool
|
||||
TotalUnclaimedTokens uint64
|
||||
TotalClaimedTokens uint64
|
||||
CurrentSolVolume uint64
|
||||
LastUpdateTimestamp int64
|
||||
IxName string
|
||||
MayhemMode bool
|
||||
CashbackFeeBasisPoints uint64
|
||||
Cashback uint64
|
||||
}
|
||||
|
||||
type pumpTradeEventLegacyV0 struct {
|
||||
Mint solana.PublicKey
|
||||
SolAmount uint64
|
||||
TokenAmount uint64
|
||||
IsBuy bool
|
||||
User solana.PublicKey
|
||||
Timestamp int64
|
||||
VirtualSolReserves uint64
|
||||
VirtualTokenReserves uint64
|
||||
RealSolReserves uint64
|
||||
RealTokenReserves uint64
|
||||
FeeRecipient solana.PublicKey
|
||||
FeeBasisPoints uint64
|
||||
Fee uint64
|
||||
Creator solana.PublicKey
|
||||
CreatorFeeBasisPoints uint64
|
||||
CreatorFee uint64
|
||||
TrackVolume bool
|
||||
TotalUnclaimedTokens uint64
|
||||
TotalClaimedTokens uint64
|
||||
CurrentSolVolume uint64
|
||||
LastUpdateTimestamp int64
|
||||
IxName string
|
||||
}
|
||||
|
||||
func decodePumpTradeEvent(data []byte) (PumpTradeEvent, error) {
|
||||
var event PumpTradeEvent
|
||||
if err := agbinary.NewBorshDecoder(data).Decode(&event); err == nil {
|
||||
return event, nil
|
||||
}
|
||||
var legacy pumpTradeEventLegacy
|
||||
if err := agbinary.NewBorshDecoder(data).Decode(&legacy); err == nil {
|
||||
return PumpTradeEvent{
|
||||
Mint: legacy.Mint,
|
||||
SolAmount: legacy.SolAmount,
|
||||
TokenAmount: legacy.TokenAmount,
|
||||
IsBuy: legacy.IsBuy,
|
||||
User: legacy.User,
|
||||
Timestamp: legacy.Timestamp,
|
||||
VirtualSolReserves: legacy.VirtualSolReserves,
|
||||
VirtualTokenReserves: legacy.VirtualTokenReserves,
|
||||
RealSolReserves: legacy.RealSolReserves,
|
||||
RealTokenReserves: legacy.RealTokenReserves,
|
||||
FeeRecipient: legacy.FeeRecipient,
|
||||
FeeBasisPoints: legacy.FeeBasisPoints,
|
||||
Fee: legacy.Fee,
|
||||
Creator: legacy.Creator,
|
||||
CreatorFeeBasisPoints: legacy.CreatorFeeBasisPoints,
|
||||
CreatorFee: legacy.CreatorFee,
|
||||
TrackVolume: legacy.TrackVolume,
|
||||
TotalUnclaimedTokens: legacy.TotalUnclaimedTokens,
|
||||
TotalClaimedTokens: legacy.TotalClaimedTokens,
|
||||
CurrentSolVolume: legacy.CurrentSolVolume,
|
||||
LastUpdateTimestamp: legacy.LastUpdateTimestamp,
|
||||
IxName: legacy.IxName,
|
||||
MayhemMode: legacy.MayhemMode,
|
||||
CashbackFeeBasisPoints: legacy.CashbackFeeBasisPoints,
|
||||
Cashback: legacy.Cashback,
|
||||
}, nil
|
||||
}
|
||||
var legacyV0 pumpTradeEventLegacyV0
|
||||
if err := agbinary.NewBorshDecoder(data).Decode(&legacyV0); err != nil {
|
||||
return PumpTradeEvent{}, err
|
||||
}
|
||||
return PumpTradeEvent{
|
||||
Mint: legacyV0.Mint,
|
||||
SolAmount: legacyV0.SolAmount,
|
||||
TokenAmount: legacyV0.TokenAmount,
|
||||
IsBuy: legacyV0.IsBuy,
|
||||
User: legacyV0.User,
|
||||
Timestamp: legacyV0.Timestamp,
|
||||
VirtualSolReserves: legacyV0.VirtualSolReserves,
|
||||
VirtualTokenReserves: legacyV0.VirtualTokenReserves,
|
||||
RealSolReserves: legacyV0.RealSolReserves,
|
||||
RealTokenReserves: legacyV0.RealTokenReserves,
|
||||
FeeRecipient: legacyV0.FeeRecipient,
|
||||
FeeBasisPoints: legacyV0.FeeBasisPoints,
|
||||
Fee: legacyV0.Fee,
|
||||
Creator: legacyV0.Creator,
|
||||
CreatorFeeBasisPoints: legacyV0.CreatorFeeBasisPoints,
|
||||
CreatorFee: legacyV0.CreatorFee,
|
||||
TrackVolume: legacyV0.TrackVolume,
|
||||
TotalUnclaimedTokens: legacyV0.TotalUnclaimedTokens,
|
||||
TotalClaimedTokens: legacyV0.TotalClaimedTokens,
|
||||
CurrentSolVolume: legacyV0.CurrentSolVolume,
|
||||
LastUpdateTimestamp: legacyV0.LastUpdateTimestamp,
|
||||
IxName: legacyV0.IxName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PumpTradeFeeArg struct {
|
||||
@@ -220,17 +410,185 @@ type PumpTradeArgs struct {
|
||||
|
||||
func pumpTradeAmountInfoFromArgs(args PumpTradeArgs) (swapMode SwapMode, fixedAmount decimal.Decimal, limitAmount decimal.Decimal, ok bool) {
|
||||
switch {
|
||||
case bytes.Equal(args.Discriminator[:], pumpBuyV2Discriminator[:]):
|
||||
case bytes.Equal(args.Discriminator[:], pumpBuyExactSolInDiscriminator[:]),
|
||||
bytes.Equal(args.Discriminator[:], pumpBuyExactQuoteInV2Discriminator[:]):
|
||||
return SwapModeExactIn, decimal.NewFromUint64(args.Amount1), decimal.NewFromUint64(args.Amount2), true
|
||||
case bytes.Equal(args.Discriminator[:], pumpBuyDiscriminator[:]):
|
||||
case bytes.Equal(args.Discriminator[:], pumpBuyDiscriminator[:]),
|
||||
bytes.Equal(args.Discriminator[:], pumpBuyV2Discriminator[:]):
|
||||
return SwapModeExactOut, decimal.NewFromUint64(args.Amount1), decimal.NewFromUint64(args.Amount2), true
|
||||
case bytes.Equal(args.Discriminator[:], pumpSellDiscriminator[:]):
|
||||
case bytes.Equal(args.Discriminator[:], pumpSellDiscriminator[:]),
|
||||
bytes.Equal(args.Discriminator[:], pumpSellV2Discriminator[:]):
|
||||
return SwapModeExactIn, decimal.NewFromUint64(args.Amount1), decimal.NewFromUint64(args.Amount2), true
|
||||
default:
|
||||
return SwapModeUnknown, decimal.Zero, decimal.Zero, false
|
||||
}
|
||||
}
|
||||
|
||||
type pumpTradeAccountLayout struct {
|
||||
IsV2 bool
|
||||
FeeRecipient int
|
||||
BaseMint int
|
||||
QuoteMint int
|
||||
BaseTokenProgram int
|
||||
QuoteTokenProgram int
|
||||
Pool int
|
||||
BasePoolToken int
|
||||
QuotePoolToken int
|
||||
User int
|
||||
BaseUserToken int
|
||||
QuoteUserToken int
|
||||
}
|
||||
|
||||
func pumpTradeLayout(instr Instruction) (pumpTradeAccountLayout, bool) {
|
||||
if len(instr.Data) < 8 {
|
||||
return pumpTradeAccountLayout{}, false
|
||||
}
|
||||
discriminator := instr.Data[:8]
|
||||
switch {
|
||||
case bytes.Equal(discriminator, pumpBuyDiscriminator[:]), bytes.Equal(discriminator, pumpBuyExactSolInDiscriminator[:]):
|
||||
if len(instr.Accounts) <= 8 {
|
||||
return pumpTradeAccountLayout{}, false
|
||||
}
|
||||
return pumpTradeAccountLayout{
|
||||
FeeRecipient: 1,
|
||||
BaseMint: 2,
|
||||
QuoteMint: -1,
|
||||
BaseTokenProgram: 8,
|
||||
QuoteTokenProgram: -1,
|
||||
Pool: 3,
|
||||
BasePoolToken: 4,
|
||||
QuotePoolToken: -1,
|
||||
User: 6,
|
||||
BaseUserToken: 5,
|
||||
QuoteUserToken: -1,
|
||||
}, true
|
||||
case bytes.Equal(discriminator, pumpSellDiscriminator[:]):
|
||||
if len(instr.Accounts) <= 9 {
|
||||
return pumpTradeAccountLayout{}, false
|
||||
}
|
||||
return pumpTradeAccountLayout{
|
||||
FeeRecipient: 1,
|
||||
BaseMint: 2,
|
||||
QuoteMint: -1,
|
||||
BaseTokenProgram: 9,
|
||||
QuoteTokenProgram: -1,
|
||||
Pool: 3,
|
||||
BasePoolToken: 4,
|
||||
QuotePoolToken: -1,
|
||||
User: 6,
|
||||
BaseUserToken: 5,
|
||||
QuoteUserToken: -1,
|
||||
}, true
|
||||
case bytes.Equal(discriminator, pumpBuyV2Discriminator[:]),
|
||||
bytes.Equal(discriminator, pumpBuyExactQuoteInV2Discriminator[:]),
|
||||
bytes.Equal(discriminator, pumpSellV2Discriminator[:]):
|
||||
if len(instr.Accounts) <= 15 {
|
||||
return pumpTradeAccountLayout{}, false
|
||||
}
|
||||
return pumpTradeAccountLayout{
|
||||
IsV2: true,
|
||||
FeeRecipient: 6,
|
||||
BaseMint: 1,
|
||||
QuoteMint: 2,
|
||||
BaseTokenProgram: 3,
|
||||
QuoteTokenProgram: 4,
|
||||
Pool: 10,
|
||||
BasePoolToken: 11,
|
||||
QuotePoolToken: 12,
|
||||
User: 13,
|
||||
BaseUserToken: 14,
|
||||
QuoteUserToken: 15,
|
||||
}, true
|
||||
default:
|
||||
return pumpTradeAccountLayout{}, false
|
||||
}
|
||||
}
|
||||
|
||||
func pumpInstructionIsSell(data []byte) bool {
|
||||
return len(data) >= 8 && (bytes.Equal(data[:8], pumpSellDiscriminator[:]) || bytes.Equal(data[:8], pumpSellV2Discriminator[:]))
|
||||
}
|
||||
|
||||
func pumpInstructionIsExactQuoteIn(data []byte) bool {
|
||||
return len(data) >= 8 && (bytes.Equal(data[:8], pumpBuyExactSolInDiscriminator[:]) || bytes.Equal(data[:8], pumpBuyExactQuoteInV2Discriminator[:]))
|
||||
}
|
||||
|
||||
func pumpAccount(result *RawTx, instr Instruction, accountIndex int) solana.PublicKey {
|
||||
if accountIndex < 0 || accountIndex >= len(instr.Accounts) {
|
||||
return solana.PublicKey{}
|
||||
}
|
||||
listIndex := instr.Accounts[accountIndex]
|
||||
if listIndex < 0 || listIndex >= len(result.accountList) {
|
||||
return solana.PublicKey{}
|
||||
}
|
||||
return result.accountList[listIndex]
|
||||
}
|
||||
|
||||
func pumpCreateQuoteAccounts(result *RawTx, instr Instruction, createEvent PumpCreateEvent) (solana.PublicKey, solana.PublicKey, uint8) {
|
||||
quoteMint := createEvent.QuoteMint
|
||||
quoteTokenProgram := solana.PublicKey{}
|
||||
optionalStart := -1
|
||||
if len(instr.Data) >= 8 && bytes.Equal(instr.Data[:8], pumpCreateV2Discriminator[:]) {
|
||||
optionalStart = 16
|
||||
}
|
||||
if optionalStart >= 0 && len(instr.Accounts) > optionalStart {
|
||||
accountQuoteMint := pumpAccount(result, instr, optionalStart)
|
||||
if quoteMint.IsZero() && !accountQuoteMint.IsZero() && !accountQuoteMint.Equals(wSolMint) {
|
||||
quoteMint = accountQuoteMint
|
||||
}
|
||||
if len(instr.Accounts) > optionalStart+2 && !quoteMint.IsZero() {
|
||||
quoteTokenProgram = pumpAccount(result, instr, optionalStart+2)
|
||||
}
|
||||
}
|
||||
if quoteMint.Equals(wSolMint) {
|
||||
quoteTokenProgram = solana.TokenProgramID
|
||||
}
|
||||
if quoteTokenProgram.IsZero() && !quoteMint.IsZero() {
|
||||
quoteTokenProgram = solana.TokenProgramID
|
||||
}
|
||||
return quoteMint, quoteTokenProgram, pumpQuoteDecimals(result, quoteMint)
|
||||
}
|
||||
|
||||
func pumpMintDecimalsFromBalances(result *RawTx, mint solana.PublicKey, fallback uint8) uint8 {
|
||||
if mint.IsZero() {
|
||||
return fallback
|
||||
}
|
||||
for _, balance := range result.Meta.PostTokenBalances {
|
||||
balance.ParseAccount()
|
||||
if balance.MintAccount.Equals(mint) {
|
||||
return uint8(balance.UITokenAmount.Decimals)
|
||||
}
|
||||
}
|
||||
for _, balance := range result.Meta.PreTokenBalances {
|
||||
balance.ParseAccount()
|
||||
if balance.MintAccount.Equals(mint) {
|
||||
return uint8(balance.UITokenAmount.Decimals)
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func pumpQuoteDecimals(result *RawTx, quoteMint solana.PublicKey) uint8 {
|
||||
fallback := uint8(9)
|
||||
if quoteMint.Equals(usdcMint) || quoteMint.Equals(usd1Mint) {
|
||||
fallback = 6
|
||||
}
|
||||
return pumpMintDecimalsFromBalances(result, quoteMint, fallback)
|
||||
}
|
||||
|
||||
func pumpQuoteAmount(tradeEvent PumpTradeEvent) uint64 {
|
||||
if tradeEvent.QuoteAmount != 0 {
|
||||
return tradeEvent.QuoteAmount
|
||||
}
|
||||
return tradeEvent.SolAmount
|
||||
}
|
||||
|
||||
func pumpQuoteReserve(tradeEvent PumpTradeEvent) uint64 {
|
||||
if tradeEvent.RealQuoteReserves != 0 {
|
||||
return tradeEvent.RealQuoteReserves
|
||||
}
|
||||
return tradeEvent.RealSolReserves
|
||||
}
|
||||
|
||||
func pumpCompleteMatchesTradeEvent(completeEvent CompleteEvent, tradeEvent PumpTradeEvent, bondingCurve solana.PublicKey) bool {
|
||||
if completeEvent.Mint != tradeEvent.Mint {
|
||||
return false
|
||||
@@ -279,10 +637,16 @@ func failedTxBuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions
|
||||
result := tx.rawTx
|
||||
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
|
||||
user := result.accountList[instruction.Accounts[6]]
|
||||
ataUserIdx := instruction.Accounts[5]
|
||||
userIndex := instruction.Accounts[6]
|
||||
mint := result.accountList[instruction.Accounts[2]]
|
||||
layout, ok := pumpTradeLayout(instruction)
|
||||
if !ok {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("unknown pump trade instruction account layout, offset, %d, %d", offset[0], offset[1])
|
||||
}
|
||||
user := pumpAccount(result, instruction, layout.User)
|
||||
ataUserIdx := instruction.Accounts[layout.BaseUserToken]
|
||||
userIndex := instruction.Accounts[layout.User]
|
||||
mint := pumpAccount(result, instruction, layout.BaseMint)
|
||||
quoteMint := pumpAccount(result, instruction, layout.QuoteMint)
|
||||
quoteTokenProgram := pumpAccount(result, instruction, layout.QuoteTokenProgram)
|
||||
var args PumpTradeArgs
|
||||
err := agbinary.NewBorshDecoder(instruction.Data[:]).Decode(&args)
|
||||
if err != nil {
|
||||
@@ -290,30 +654,27 @@ func failedTxBuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions
|
||||
}
|
||||
var event string
|
||||
var (
|
||||
solAmount, tokenAmount uint64
|
||||
quoteAmount, tokenAmount uint64
|
||||
)
|
||||
if bytes.Equal(args.Discriminator[:], pumpBuyV2Discriminator[:]) {
|
||||
if bytes.Equal(args.Discriminator[:], pumpBuyExactSolInDiscriminator[:]) ||
|
||||
bytes.Equal(args.Discriminator[:], pumpBuyExactQuoteInV2Discriminator[:]) {
|
||||
event = "buy_failed"
|
||||
solAmount = args.Amount1
|
||||
quoteAmount = args.Amount1
|
||||
tokenAmount = args.Amount2
|
||||
} else if bytes.Equal(args.Discriminator[:], pumpBuyDiscriminator[:]) {
|
||||
} else if bytes.Equal(args.Discriminator[:], pumpBuyDiscriminator[:]) ||
|
||||
bytes.Equal(args.Discriminator[:], pumpBuyV2Discriminator[:]) {
|
||||
event = "buy_failed"
|
||||
solAmount = args.Amount2
|
||||
quoteAmount = args.Amount2
|
||||
tokenAmount = args.Amount1
|
||||
} else if bytes.Equal(args.Discriminator[:], pumpSellDiscriminator[:]) {
|
||||
} else if bytes.Equal(args.Discriminator[:], pumpSellDiscriminator[:]) ||
|
||||
bytes.Equal(args.Discriminator[:], pumpSellV2Discriminator[:]) {
|
||||
event = "sell_failed"
|
||||
solAmount = args.Amount2
|
||||
quoteAmount = args.Amount2
|
||||
tokenAmount = args.Amount1
|
||||
} else {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("unknown pump trade instruction discriminator, offset, %d, %d", offset[0], offset[1])
|
||||
}
|
||||
var baseTokenProgram solana.PublicKey
|
||||
|
||||
if event == "buy_failed" {
|
||||
baseTokenProgram = result.accountList[instruction.Accounts[8]]
|
||||
} else {
|
||||
baseTokenProgram = result.accountList[instruction.Accounts[9]]
|
||||
}
|
||||
baseTokenProgram := pumpAccount(result, instruction, layout.BaseTokenProgram)
|
||||
if !user.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
|
||||
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, mint)
|
||||
//&& userBaseAmount.BigInt().Uint64() == tradeEvent.TokenAmount
|
||||
@@ -325,31 +686,43 @@ func failedTxBuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions
|
||||
}
|
||||
|
||||
userBase := getAccountBalanceAfterTx(result, ataUserIdx)
|
||||
userQuote, _ := GetSolAfterTx(result, userIndex)
|
||||
userQuote := decimal.Zero
|
||||
if layout.IsV2 && !quoteMint.Equals(wSolMint) {
|
||||
userQuote = getAccountBalanceAfterTx(result, instruction.Accounts[layout.QuoteUserToken])
|
||||
} else {
|
||||
userQuoteLamports, _ := GetSolAfterTx(result, userIndex)
|
||||
userQuote = decimal.NewFromUint64(userQuoteLamports)
|
||||
}
|
||||
|
||||
bcIdx := instruction.Accounts[3]
|
||||
bcAtaIndex := instruction.Accounts[4]
|
||||
bcIdx := instruction.Accounts[layout.Pool]
|
||||
bcAtaIndex := instruction.Accounts[layout.BasePoolToken]
|
||||
quoteReserves := decimal.Zero
|
||||
if layout.IsV2 && !quoteMint.Equals(wSolMint) {
|
||||
quoteReserves = getAccountBalanceAfterTx(result, instruction.Accounts[layout.QuotePoolToken])
|
||||
} else {
|
||||
solReserves, _ := GetSolAfterTx(result, bcIdx)
|
||||
quoteReserves = decimal.NewFromUint64(solReserves)
|
||||
}
|
||||
tokenReserves := getAccountBalanceAfterTx(result, bcAtaIndex)
|
||||
swaps := []Swap{
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: event,
|
||||
Pool: result.accountList[instruction.Accounts[3]],
|
||||
Pool: pumpAccount(result, instruction, layout.Pool),
|
||||
BaseMint: mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
QuoteMintDecimals: pumpQuoteDecimals(result, quoteMint),
|
||||
User: user,
|
||||
BaseAmount: decimal.NewFromUint64(tokenAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(solAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(quoteAmount),
|
||||
BaseReserve: tokenReserves,
|
||||
QuoteReserve: decimal.NewFromUint64(solReserves),
|
||||
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
||||
QuoteReserve: quoteReserves,
|
||||
Mayhem: isMayhemPump(pumpAccount(result, instruction, layout.FeeRecipient)),
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
UserQuoteBalance: userQuote,
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}
|
||||
@@ -365,6 +738,10 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
var err error
|
||||
var programIndex = instruction.ProgramIDIndex
|
||||
layout, ok := pumpTradeLayout(instruction)
|
||||
if !ok {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("unknown pump trade instruction account layout, offset, %d, %d", offset[0], offset[1])
|
||||
}
|
||||
|
||||
feeEventProgramIndex := 0
|
||||
for i, b := range result.accountList {
|
||||
@@ -411,7 +788,7 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
if tradeFound {
|
||||
break
|
||||
}
|
||||
err = agbinary.NewBorshDecoder(innerInstr.Data[16:]).Decode(&tradeEvent)
|
||||
tradeEvent, err = decodePumpTradeEvent(innerInstr.Data[16:])
|
||||
if offset[1] == 0 {
|
||||
newoffset = [2]uint{offset[0] + 1, offset[1]}
|
||||
} else {
|
||||
@@ -420,7 +797,7 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
if err != nil {
|
||||
return nil, newoffset, fmt.Errorf("pump buy event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
expectedIsBuy := !bytes.Equal(instruction.Data[:8], pumpSellDiscriminator[:])
|
||||
expectedIsBuy := !pumpInstructionIsSell(instruction.Data)
|
||||
if tradeEvent.IsBuy != expectedIsBuy {
|
||||
tradeEvent = PumpTradeEvent{}
|
||||
continue
|
||||
@@ -437,7 +814,7 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
if err != nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("pump completeEvent event decode error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
if !pumpCompleteMatchesTradeEvent(completeEvent, tradeEvent, result.accountList[instruction.Accounts[3]]) {
|
||||
if !pumpCompleteMatchesTradeEvent(completeEvent, tradeEvent, pumpAccount(result, instruction, layout.Pool)) {
|
||||
break
|
||||
}
|
||||
if offset[1] == 0 {
|
||||
@@ -451,7 +828,7 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
|
||||
}
|
||||
}
|
||||
if tradeEvent == (PumpTradeEvent{}) {
|
||||
if !tradeFound {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("pmp buy/sell event not found, offset, %d, %d", offset[0], offset[1])
|
||||
}
|
||||
|
||||
@@ -463,13 +840,16 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
}
|
||||
|
||||
event := ""
|
||||
baseTokenProgram := solana.TokenProgramID
|
||||
baseTokenProgram := pumpAccount(result, instruction, layout.BaseTokenProgram)
|
||||
quoteMint := tradeEvent.QuoteMint
|
||||
if quoteMint.IsZero() {
|
||||
quoteMint = pumpAccount(result, instruction, layout.QuoteMint)
|
||||
}
|
||||
quoteTokenProgram := pumpAccount(result, instruction, layout.QuoteTokenProgram)
|
||||
if tradeEvent.IsBuy {
|
||||
event = "buy"
|
||||
baseTokenProgram = result.accountList[instruction.Accounts[8]]
|
||||
} else {
|
||||
event = "sell"
|
||||
baseTokenProgram = result.accountList[instruction.Accounts[9]]
|
||||
}
|
||||
if _, exists := tx.Token[tradeEvent.Mint]; !exists {
|
||||
tx.Token[tradeEvent.Mint] = TokenMeta{
|
||||
@@ -481,8 +861,8 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
|
||||
var user = tradeEvent.User
|
||||
|
||||
ataUserIdx := instruction.Accounts[5]
|
||||
userIndex := instruction.Accounts[6]
|
||||
ataUserIdx := instruction.Accounts[layout.BaseUserToken]
|
||||
userIndex := instruction.Accounts[layout.User]
|
||||
if !tradeEvent.User.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
|
||||
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, tradeEvent.Mint)
|
||||
//&& userBaseAmount.BigInt().Uint64() == tradeEvent.TokenAmount
|
||||
@@ -494,14 +874,20 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
}
|
||||
|
||||
userBase := getAccountBalanceAfterTx(result, ataUserIdx)
|
||||
userQuote, _ := GetSolAfterTx(result, userIndex)
|
||||
userQuote := decimal.Zero
|
||||
if layout.IsV2 && !quoteMint.Equals(wSolMint) {
|
||||
userQuote = getAccountBalanceAfterTx(result, instruction.Accounts[layout.QuoteUserToken])
|
||||
} else {
|
||||
userQuoteLamports, _ := GetSolAfterTx(result, userIndex)
|
||||
userQuote = decimal.NewFromUint64(userQuoteLamports)
|
||||
}
|
||||
|
||||
solAmount := tradeEvent.SolAmount
|
||||
if tradeEvent.IsBuy && bytes.Equal(instruction.Data[:8], pumpBuyV2Discriminator[:]) {
|
||||
quoteAmount := pumpQuoteAmount(tradeEvent)
|
||||
if tradeEvent.IsBuy && pumpInstructionIsExactQuoteIn(instruction.Data) && !layout.IsV2 {
|
||||
fee := tradeEvent.Fee + tradeEvent.CreatorFee
|
||||
solAmount = tradeFeeArg.TradeSize
|
||||
if solAmount > fee {
|
||||
solAmount = solAmount - fee
|
||||
quoteAmount = tradeFeeArg.TradeSize
|
||||
if quoteAmount > fee {
|
||||
quoteAmount = quoteAmount - fee
|
||||
}
|
||||
}
|
||||
isCashbackCoin := tradeEvent.CashbackFeeBasisPoints > 0 || tradeEvent.Cashback > 0
|
||||
@@ -509,22 +895,22 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
{
|
||||
Program: SolProgramPump,
|
||||
Event: event,
|
||||
Pool: result.accountList[instruction.Accounts[3]],
|
||||
Pool: pumpAccount(result, instruction, layout.Pool),
|
||||
BaseMint: tradeEvent.Mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
Creator: tradeEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
QuoteMintDecimals: pumpQuoteDecimals(result, quoteMint),
|
||||
User: user,
|
||||
BaseAmount: decimal.NewFromUint64(tradeEvent.TokenAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(solAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(quoteAmount),
|
||||
BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves),
|
||||
QuoteReserve: decimal.NewFromUint64(tradeEvent.RealSolReserves),
|
||||
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
||||
QuoteReserve: decimal.NewFromUint64(pumpQuoteReserve(tradeEvent)),
|
||||
Mayhem: tradeEvent.MayhemMode || isMayhemPump(pumpAccount(result, instruction, layout.FeeRecipient)),
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
UserQuoteBalance: userQuote,
|
||||
EntryContract: entryContract,
|
||||
Cashback: isCashbackCoin,
|
||||
},
|
||||
@@ -537,20 +923,20 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
|
||||
swaps = append(swaps, Swap{
|
||||
Program: SolProgramPump,
|
||||
Event: "complete",
|
||||
Pool: result.accountList[instruction.Accounts[3]],
|
||||
Pool: pumpAccount(result, instruction, layout.Pool),
|
||||
BaseMint: tradeEvent.Mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
BaseTokenProgram: result.accountList[instruction.Accounts[8]],
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
Creator: tradeEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
QuoteMintDecimals: pumpQuoteDecimals(result, quoteMint),
|
||||
User: user,
|
||||
BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves),
|
||||
QuoteReserve: decimal.NewFromUint64(tradeEvent.RealSolReserves),
|
||||
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
|
||||
QuoteReserve: decimal.NewFromUint64(pumpQuoteReserve(tradeEvent)),
|
||||
Mayhem: tradeEvent.MayhemMode || isMayhemPump(pumpAccount(result, instruction, layout.FeeRecipient)),
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
UserQuoteBalance: userQuote,
|
||||
EntryContract: entryContract,
|
||||
})
|
||||
}
|
||||
@@ -572,11 +958,74 @@ type MigrateEvent struct {
|
||||
Pool solana.PublicKey
|
||||
}
|
||||
|
||||
type pumpMigrateAccountLayout struct {
|
||||
IsV2 bool
|
||||
BaseMint int
|
||||
QuoteMint int
|
||||
Pool int
|
||||
BasePoolToken int
|
||||
QuotePoolToken int
|
||||
User int
|
||||
BaseTokenProgram int
|
||||
QuoteTokenProgram int
|
||||
}
|
||||
|
||||
func pumpMigrateLayout(instr Instruction) (pumpMigrateAccountLayout, bool) {
|
||||
if len(instr.Data) < 8 {
|
||||
return pumpMigrateAccountLayout{}, false
|
||||
}
|
||||
discriminator := instr.Data[:8]
|
||||
switch {
|
||||
case bytes.Equal(discriminator, pumpMigrateDiscriminator[:]):
|
||||
if len(instr.Accounts) <= 14 {
|
||||
return pumpMigrateAccountLayout{}, false
|
||||
}
|
||||
return pumpMigrateAccountLayout{
|
||||
BaseMint: 2,
|
||||
QuoteMint: 14,
|
||||
Pool: 3,
|
||||
BasePoolToken: 4,
|
||||
QuotePoolToken: -1,
|
||||
User: 5,
|
||||
BaseTokenProgram: 7,
|
||||
QuoteTokenProgram: -1,
|
||||
}, true
|
||||
case bytes.Equal(discriminator, pumpMigrateV2Discriminator[:]):
|
||||
if len(instr.Accounts) <= 20 {
|
||||
return pumpMigrateAccountLayout{}, false
|
||||
}
|
||||
return pumpMigrateAccountLayout{
|
||||
IsV2: true,
|
||||
BaseMint: 2,
|
||||
QuoteMint: 3,
|
||||
Pool: 4,
|
||||
BasePoolToken: 5,
|
||||
QuotePoolToken: 6,
|
||||
User: 7,
|
||||
BaseTokenProgram: 19,
|
||||
QuoteTokenProgram: 20,
|
||||
}, true
|
||||
default:
|
||||
return pumpMigrateAccountLayout{}, false
|
||||
}
|
||||
}
|
||||
|
||||
func decimalFromUint64WithFallback(primary, fallback uint64) decimal.Decimal {
|
||||
if primary != 0 {
|
||||
return decimal.NewFromUint64(primary)
|
||||
}
|
||||
return decimal.NewFromUint64(fallback)
|
||||
}
|
||||
|
||||
func MigrateParser(tx *Tx, instr Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
|
||||
result := tx.rawTx
|
||||
var entryContract = result.accountList[result.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
|
||||
var err error
|
||||
programIndex := instr.ProgramIDIndex
|
||||
layout, ok := pumpMigrateLayout(instr)
|
||||
if !ok {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("unknown pump migrate instruction account layout, offset, %d, %d", offset[0], offset[1])
|
||||
}
|
||||
ammprogramIdx := 0
|
||||
for i, b := range result.accountList {
|
||||
if b.Equals(pumpAmmProgram) {
|
||||
@@ -633,20 +1082,45 @@ func MigrateParser(tx *Tx, instr Instruction, innerInstructions InnerInstruction
|
||||
|
||||
offset = [2]uint{newoffset[0], newoffset[1]}
|
||||
// verify migrate by checking create pool and migrate event
|
||||
userIndex := instr.Accounts[5]
|
||||
ataBondingCurveAccountIndex := instr.Accounts[4]
|
||||
userIndex := instr.Accounts[layout.User]
|
||||
ataBondingCurveAccountIndex := instr.Accounts[layout.BasePoolToken]
|
||||
bc, err := getTokenBalanceAfterTx(result, ataBondingCurveAccountIndex)
|
||||
if err != nil || bc == nil {
|
||||
return nil, increaseOffset(offset), fmt.Errorf("pump migrate get bonding curve balance error: %v, offset, %d, %d", err, offset[0], offset[1])
|
||||
}
|
||||
baseTokenProgram := bc.ProgramIDAccount
|
||||
if layout.IsV2 {
|
||||
baseTokenProgram = pumpAccount(result, instr, layout.BaseTokenProgram)
|
||||
}
|
||||
quoteMint := createEvent.QuoteMint
|
||||
if quoteMint.IsZero() {
|
||||
quoteMint = pumpAccount(result, instr, layout.QuoteMint)
|
||||
}
|
||||
quoteTokenProgram := pumpAccount(result, instr, layout.QuoteTokenProgram)
|
||||
if quoteTokenProgram.IsZero() && !quoteMint.IsZero() {
|
||||
quoteTokenProgram = solana.TokenProgramID
|
||||
}
|
||||
quoteDecimals := createEvent.QuoteMintDecimals
|
||||
if quoteDecimals == 0 {
|
||||
quoteDecimals = pumpQuoteDecimals(result, quoteMint)
|
||||
}
|
||||
var userBase decimal.Decimal
|
||||
if result.accountList[userIndex].Equals(pumpMigrationAccount) {
|
||||
userBase = decimal.Zero
|
||||
} else {
|
||||
userBase = GetTokenBalanceAfterTx(result, userIndex, baseTokenProgram, migrateEvent.Mint)
|
||||
}
|
||||
userQuote, _ := GetSolAfterTx(result, userIndex)
|
||||
userQuote := decimal.Zero
|
||||
if layout.IsV2 && !quoteMint.Equals(wSolMint) {
|
||||
userQuote = GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
|
||||
} else {
|
||||
userQuoteLamports, _ := GetSolAfterTx(result, userIndex)
|
||||
userQuote = decimal.NewFromUint64(userQuoteLamports)
|
||||
}
|
||||
baseAmount := decimalFromUint64WithFallback(createEvent.BaseAmountIn, migrateEvent.MintAmount)
|
||||
quoteAmount := decimalFromUint64WithFallback(createEvent.QuoteAmountIn, migrateEvent.SolAmount)
|
||||
baseReserve := decimalFromUint64WithFallback(createEvent.PoolBaseAmount, migrateEvent.MintAmount)
|
||||
quoteReserve := decimalFromUint64WithFallback(createEvent.PoolQuoteAmount, migrateEvent.SolAmount)
|
||||
|
||||
if _, exists := tx.Token[migrateEvent.Mint]; !exists {
|
||||
tx.Token[migrateEvent.Mint] = TokenMeta{
|
||||
@@ -661,22 +1135,22 @@ func MigrateParser(tx *Tx, instr Instruction, innerInstructions InnerInstruction
|
||||
Event: "migrate",
|
||||
Pool: migrateEvent.BondingCurve,
|
||||
BaseMint: migrateEvent.Mint,
|
||||
QuoteMint: solana.PublicKey{},
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: solana.PublicKey{},
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
Creator: createEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
QuoteMintDecimals: quoteDecimals,
|
||||
User: migrateEvent.User,
|
||||
//BaseAmount: decimal.Decimal{},
|
||||
//QuoteAmount: decimal.Decimal{},
|
||||
BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||
QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||
BaseReserve: baseReserve,
|
||||
QuoteReserve: quoteReserve,
|
||||
Mayhem: createEvent.IsMayhemMode,
|
||||
MigrateTopProgram: pumpAmmProgram,
|
||||
MigrateToPool: migrateEvent.Pool,
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
UserQuoteBalance: userQuote,
|
||||
EntryContract: entryContract,
|
||||
},
|
||||
}
|
||||
@@ -685,20 +1159,20 @@ func MigrateParser(tx *Tx, instr Instruction, innerInstructions InnerInstruction
|
||||
Event: "create",
|
||||
Pool: migrateEvent.Pool,
|
||||
BaseMint: migrateEvent.Mint,
|
||||
QuoteMint: wSolMint,
|
||||
QuoteMint: quoteMint,
|
||||
BaseTokenProgram: baseTokenProgram,
|
||||
QuoteTokenProgram: solana.TokenProgramID,
|
||||
QuoteTokenProgram: quoteTokenProgram,
|
||||
Creator: createEvent.Creator,
|
||||
BaseMintDecimals: 6,
|
||||
QuoteMintDecimals: 9,
|
||||
QuoteMintDecimals: quoteDecimals,
|
||||
User: migrateEvent.User,
|
||||
BaseAmount: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||
QuoteAmount: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||
BaseReserve: decimal.NewFromUint64(migrateEvent.MintAmount),
|
||||
QuoteReserve: decimal.NewFromUint64(migrateEvent.SolAmount),
|
||||
BaseAmount: baseAmount,
|
||||
QuoteAmount: quoteAmount,
|
||||
BaseReserve: baseReserve,
|
||||
QuoteReserve: quoteReserve,
|
||||
Mayhem: createEvent.IsMayhemMode,
|
||||
UserBaseBalance: userBase,
|
||||
UserQuoteBalance: decimal.NewFromUint64(userQuote),
|
||||
UserQuoteBalance: userQuote,
|
||||
EntryContract: entryContract,
|
||||
})
|
||||
|
||||
|
||||
161
pump_test.go
161
pump_test.go
@@ -1,6 +1,7 @@
|
||||
package pump_parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
@@ -100,3 +101,163 @@ func TestPumpCompleteMatchesTradeEvent(t *testing.T) {
|
||||
t.Fatal("pumpCompleteMatchesTradeEvent() = true for mismatched user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPumpV2Discriminators(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
got [8]byte
|
||||
want [8]byte
|
||||
}{
|
||||
{name: "buy_exact_sol_in", got: pumpBuyExactSolInDiscriminator, want: [8]byte{56, 252, 116, 8, 158, 223, 205, 95}},
|
||||
{name: "buy_v2", got: pumpBuyV2Discriminator, want: [8]byte{184, 23, 238, 97, 103, 197, 211, 61}},
|
||||
{name: "buy_exact_quote_in_v2", got: pumpBuyExactQuoteInV2Discriminator, want: [8]byte{194, 171, 28, 70, 104, 77, 91, 47}},
|
||||
{name: "sell_v2", got: pumpSellV2Discriminator, want: [8]byte{93, 246, 130, 60, 231, 233, 64, 178}},
|
||||
{name: "create_v2", got: pumpCreateV2Discriminator, want: [8]byte{214, 144, 76, 236, 95, 139, 49, 180}},
|
||||
{name: "migrate_v2", got: pumpMigrateV2Discriminator, want: [8]byte{187, 203, 18, 31, 206, 237, 254, 41}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if tt.got != tt.want {
|
||||
t.Fatalf("%s discriminator = %v, want %v", tt.name, tt.got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPumpMigrateLayoutV2(t *testing.T) {
|
||||
accounts := make([]int, 27)
|
||||
for i := range accounts {
|
||||
accounts[i] = i
|
||||
}
|
||||
layout, ok := pumpMigrateLayout(Instruction{
|
||||
Data: pumpMigrateV2Discriminator[:],
|
||||
Accounts: accounts,
|
||||
})
|
||||
if !ok {
|
||||
t.Fatal("migrate_v2 layout not recognized")
|
||||
}
|
||||
if !layout.IsV2 ||
|
||||
layout.BaseMint != 2 ||
|
||||
layout.QuoteMint != 3 ||
|
||||
layout.Pool != 4 ||
|
||||
layout.BasePoolToken != 5 ||
|
||||
layout.QuotePoolToken != 6 ||
|
||||
layout.User != 7 ||
|
||||
layout.BaseTokenProgram != 19 ||
|
||||
layout.QuoteTokenProgram != 20 {
|
||||
t.Fatalf("migrate_v2 layout = %+v", layout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPumpTradeAmountInfoV2(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
disc [8]byte
|
||||
wantMode SwapMode
|
||||
}{
|
||||
{name: "legacy exact quote in", disc: pumpBuyExactSolInDiscriminator, wantMode: SwapModeExactIn},
|
||||
{name: "v2 exact quote in", disc: pumpBuyExactQuoteInV2Discriminator, wantMode: SwapModeExactIn},
|
||||
{name: "v2 buy exact out", disc: pumpBuyV2Discriminator, wantMode: SwapModeExactOut},
|
||||
{name: "v2 sell exact in", disc: pumpSellV2Discriminator, wantMode: SwapModeExactIn},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
mode, fixed, limit, ok := pumpTradeAmountInfoFromArgs(PumpTradeArgs{
|
||||
Discriminator: tt.disc,
|
||||
Amount1: 11,
|
||||
Amount2: 22,
|
||||
})
|
||||
if !ok {
|
||||
t.Fatalf("%s not recognized", tt.name)
|
||||
}
|
||||
if mode != tt.wantMode {
|
||||
t.Fatalf("%s mode = %s, want %s", tt.name, mode.String(), tt.wantMode.String())
|
||||
}
|
||||
if fixed.String() != "11" || limit.String() != "22" {
|
||||
t.Fatalf("%s fixed/limit = %s/%s, want 11/22", tt.name, fixed, limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPumpCreateQuoteAccountsOptional(t *testing.T) {
|
||||
createAccounts := make([]int, 17)
|
||||
for i := range createAccounts {
|
||||
createAccounts[i] = i
|
||||
}
|
||||
createV2Accounts := make([]int, 19)
|
||||
for i := range createV2Accounts {
|
||||
createV2Accounts[i] = i
|
||||
}
|
||||
createV2Accounts[16] = 14
|
||||
createV2Accounts[18] = 16
|
||||
accountList := make([]solana.PublicKey, 19)
|
||||
accountList[14] = usdcMint
|
||||
accountList[16] = solana.TokenProgramID
|
||||
accountList[18] = solana.TokenProgramID
|
||||
result := &RawTx{accountList: accountList}
|
||||
|
||||
quoteMint, quoteTokenProgram, quoteDecimals := pumpCreateQuoteAccounts(result, Instruction{
|
||||
Data: pumpCreateDiscriminator[:],
|
||||
Accounts: createAccounts,
|
||||
}, PumpCreateEvent{})
|
||||
if !quoteMint.IsZero() || !quoteTokenProgram.IsZero() || quoteDecimals != 9 {
|
||||
t.Fatalf("create quote accounts = %s/%s/%d, want zero/zero/9", quoteMint, quoteTokenProgram, quoteDecimals)
|
||||
}
|
||||
|
||||
quoteMint, quoteTokenProgram, quoteDecimals = pumpCreateQuoteAccounts(result, Instruction{
|
||||
Data: pumpCreateV2Discriminator[:],
|
||||
Accounts: createV2Accounts,
|
||||
}, PumpCreateEvent{})
|
||||
if !quoteMint.Equals(usdcMint) || !quoteTokenProgram.Equals(solana.TokenProgramID) || quoteDecimals != 6 {
|
||||
t.Fatalf("create_v2 quote accounts = %s/%s/%d, want USDC/token/6", quoteMint, quoteTokenProgram, quoteDecimals)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePumpTradeEventV2QuoteFields(t *testing.T) {
|
||||
user := solana.MustPublicKeyFromBase58("DS95KxqUCCjwQaXhD7fhKatXbivwWDNrJdNV5ZcubGdz")
|
||||
mint := solana.MustPublicKeyFromBase58("8GNGkNnfBuoTP3QRnmdNzSYuuE15M8tvcNvxNsV4pump")
|
||||
want := PumpTradeEvent{
|
||||
Mint: mint,
|
||||
SolAmount: 1,
|
||||
TokenAmount: 2,
|
||||
IsBuy: true,
|
||||
User: user,
|
||||
VirtualTokenReserves: 3,
|
||||
RealTokenReserves: 4,
|
||||
IxName: "buy_v2",
|
||||
Shareholders: []PumpShareholder{{Address: user, ShareBps: 250}},
|
||||
QuoteMint: usdcMint,
|
||||
QuoteAmount: 5,
|
||||
VirtualQuoteReserves: 6,
|
||||
RealQuoteReserves: 7,
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := agbinary.NewBorshEncoder(&buf).Encode(want); err != nil {
|
||||
t.Fatalf("encode v2 trade event: %v", err)
|
||||
}
|
||||
got, err := decodePumpTradeEvent(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("decodePumpTradeEvent() error = %v", err)
|
||||
}
|
||||
if !got.QuoteMint.Equals(usdcMint) || got.QuoteAmount != 5 || got.VirtualQuoteReserves != 6 || got.RealQuoteReserves != 7 {
|
||||
t.Fatalf("decoded quote fields = %s/%d/%d/%d", got.QuoteMint, got.QuoteAmount, got.VirtualQuoteReserves, got.RealQuoteReserves)
|
||||
}
|
||||
if len(got.Shareholders) != 1 || got.Shareholders[0].ShareBps != 250 {
|
||||
t.Fatalf("decoded shareholders = %+v", got.Shareholders)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePumpTradeEventLegacyFallback(t *testing.T) {
|
||||
hexData := "e445a52e51cb9a1dbddb7fd34ee661ee051d1834b36cc6f04cc5bd998d53ab2a566a0ca2415bcfad5f9ed6941a851d3f84ecb200000000006c267d17170000000190d2c525ef0ea205f4b4abfdb6eaaf37fcb5a1b1dec2e2689448eecab6ba93b6c922246900000000c314f11a0d000000be71bf9e22080200c368cd1e06000000bed9ac52910901004ac2f8d0dd5cbc97e3289c197cb5062a54f3d956b9ce6e5115f96567aa5cb3e65f000000000000002c6a010000000000c9e17c171227a50a5b62e3a4a3f8ff4fafe0bca9c332bdf7f32eedbc4229604d1e000000000000005f72000000000000010000000000000000000000000000000000000000000000000000000000000000100000006275795f65786163745f736f6c5f696e"
|
||||
data, err := hex.DecodeString(hexData)
|
||||
if err != nil {
|
||||
t.Fatalf("decode hex: %v", err)
|
||||
}
|
||||
got, err := decodePumpTradeEvent(data[16:])
|
||||
if err != nil {
|
||||
t.Fatalf("decodePumpTradeEvent() legacy error = %v", err)
|
||||
}
|
||||
if got.IxName != "buy_exact_sol_in" || got.SolAmount != 11725956 || !got.IsBuy {
|
||||
t.Fatalf("legacy event = %+v", got)
|
||||
}
|
||||
if !got.QuoteMint.IsZero() || got.QuoteAmount != 0 || got.RealQuoteReserves != 0 {
|
||||
t.Fatalf("legacy quote fields = %s/%d/%d, want zero", got.QuoteMint, got.QuoteAmount, got.RealQuoteReserves)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user