Skip to content

Commit

Permalink
feat: add solana spl token, TODO…
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhangguiguang committed Oct 11, 2023
1 parent 4c23c32 commit 0975550
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 1 deletion.
12 changes: 12 additions & 0 deletions core/base/balance.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
package base

import "strconv"

type Balance struct {
Total string
Usable string
}

// Deprecated: use `NewBalance("0")`
func EmptyBalance() *Balance {
return &Balance{
Total: "0",
Usable: "0",
}
}

func NewBalance(amount string) *Balance {
return &Balance{Total: amount, Usable: amount}
}

func NewBalanceWithInt(amount int64) *Balance {
a := strconv.FormatInt(amount, 10)
return &Balance{Total: a, Usable: a}
}
8 changes: 8 additions & 0 deletions core/base/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ type OptionalString struct {
Value string
}

func NewOptionalString(s string) *OptionalString {
return &OptionalString{Value: s}
}

// Optional bool for easy of writing iOS code
type OptionalBool struct {
Value bool
}

func NewOptionalBool(b bool) *OptionalBool {
return &OptionalBool{Value: b}
}

type safeMap struct {
sync.RWMutex
Map map[interface{}]interface{}
Expand Down
2 changes: 2 additions & 0 deletions core/solana/interface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ var (
_ base.Token = (*Token)(nil)
_ base.Transaction = (*Transaction)(nil)
_ base.SignedTransaction = (*SignedTransaction)(nil)

_ base.Token = (*SPLToken)(nil)
)
170 changes: 170 additions & 0 deletions core/solana/spltoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package solana

import (
"context"
"errors"
"math/big"

"github.com/blocto/solana-go-sdk/common"
"github.com/blocto/solana-go-sdk/program/associated_token_account"
"github.com/blocto/solana-go-sdk/program/token"
"github.com/blocto/solana-go-sdk/types"
"github.com/coming-chat/wallet-SDK/core/base"
)

type SPLToken struct {
chain *Chain
MintAddress string
}

func NewSPLToken(chain *Chain, mintAddress string) *SPLToken {
return &SPLToken{
chain: chain,
MintAddress: mintAddress,
}
}

// MARK - Implement the protocol Token

func (t *SPLToken) Chain() base.Chain {
return t.chain
}

func (t *SPLToken) TokenInfo() (*base.TokenInfo, error) {
return nil, errors.New("TODO func")
}

func (t *SPLToken) BalanceOfAddress(address string) (*base.Balance, error) {
balances, err := t.TokenAccountOfAddress(address)
if err != nil {
return nil, err
}
if len(balances) == 0 {
return nil, errors.New("the owner has not created the token account")
}
total := big.NewInt(0)
for _, bal := range balances {
total.Add(total, big.NewInt(int64(bal.Amount)))
}
return base.NewBalance(total.String()), nil
}
func (t *SPLToken) BalanceOfPublicKey(publicKey string) (*base.Balance, error) {
addr, err := EncodePublicKeyToAddress(publicKey)
if err != nil {
return nil, err
}
return t.BalanceOfAddress(addr)
}
func (t *SPLToken) BalanceOfAccount(account base.Account) (*base.Balance, error) {
return t.BalanceOfAddress(account.Address())
}

// BuildTransfer implements base.Token.
func (t *SPLToken) BuildTransfer(sender string, receiver string, amount string) (txn base.Transaction, err error) {
return nil, errors.New("TODO func")
}

func (t *SPLToken) CanTransferAll() bool {
return false
}
func (t *SPLToken) BuildTransferAll(sender string, receiver string) (txn base.Transaction, err error) {
return nil, base.ErrUnsupportedFunction
}

// MARK - Help func

const (
SPLAccountTypeRandom = "Random"
SPLAccountTypeAssociated = "Associated"
)

type TokenAccount struct {
Address string
Owner string
Amount uint64

AccountType string // "Random" or "Associated"
}

func (t *SPLToken) TokenAccountOfAddress(address string) (res []TokenAccount, err error) {
defer base.CatchPanicAndMapToBasicError(&err)

cli := t.chain.client()

// 先假设该地址是 token 账号地址
tokenAcc, err := cli.GetTokenAccount(context.Background(), address)
if err == nil {
if t.MintAddress != tokenAcc.Mint.ToBase58() {
return []TokenAccount{}, nil
}
return []TokenAccount{TransformTokenAccount(tokenAcc, address)}, nil
}

// 否则假设该地址是普通账号地址
tokenAccs, err := cli.GetTokenAccountsByOwnerByMint(context.Background(), address, t.MintAddress)
if err != nil {
return nil, err
}
res = make([]TokenAccount, len(tokenAccs))
for idx, acc := range tokenAccs {
res[idx] = TransformTokenAccount(acc.TokenAccount, acc.PublicKey.ToBase58())
}
return res, nil
}

// TransformTokenAccount
func TransformTokenAccount(account token.TokenAccount, tokenAddress string) TokenAccount {
res := TokenAccount{
Address: tokenAddress,
Owner: account.Owner.ToBase58(),
Amount: account.Amount,
}
ata, _, err := common.FindAssociatedTokenAddress(account.Owner, account.Mint)
if err == nil && ata.ToBase58() == tokenAddress {
res.AccountType = SPLAccountTypeAssociated
} else {
res.AccountType = SPLAccountTypeRandom
}
return res
}

func (t *SPLToken) HasCreated(ownerAddress string) (b *base.OptionalBool, err error) {
accounts, err := t.TokenAccountOfAddress(ownerAddress)
if err != nil {
return nil, err
}
return base.NewOptionalBool(len(accounts) > 0), nil
}

func (t *SPLToken) CreateTokenAccount(ownerAddress string, signerAddress string) (txn *Transaction, err error) {
defer base.CatchPanicAndMapToBasicError(&err)

ownerPubkey := common.PublicKeyFromString(ownerAddress)
tokenPubkey := common.PublicKeyFromString(t.MintAddress)
signerPubkey := common.PublicKeyFromString(signerAddress)
associateAccount, _, err := common.FindAssociatedTokenAddress(ownerPubkey, tokenPubkey)
if err != nil {
return
}

cli := t.chain.client()
latestBlock, err := cli.GetLatestBlockhash(context.Background())
if err != nil {
return
}
msg := types.NewMessage(types.NewMessageParam{
FeePayer: signerPubkey,
RecentBlockhash: latestBlock.Blockhash,
Instructions: []types.Instruction{
associated_token_account.CreateAssociatedTokenAccount(associated_token_account.CreateAssociatedTokenAccountParam{
Funder: signerPubkey,
Owner: ownerPubkey,
Mint: tokenPubkey,
AssociatedTokenAccount: associateAccount,
}),
},
})
return &Transaction{
Message: msg,
}, nil
}
42 changes: 42 additions & 0 deletions core/solana/spltoken_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package solana

import (
"testing"

"github.com/stretchr/testify/require"
)

const aTokenAddress = "38GJtbJkJJQKkR6CTZwwok4hAXoKnByU2MFAZjgTe114"

func TestSPLToken_GetBalance(t *testing.T) {
chain := DevnetChain()
token := NewSPLToken(chain, "38GJtbJkJJQKkR6CTZwwok4hAXoKnByU2MFAZjgTe114")

balance, err := token.BalanceOfAddress("9B5XszUGdMaxCZ7uSQhPzdks5ZQSmWxrmzCSvtJ6Ns6g")
require.Nil(t, err)
t.Log(balance)
}

func TestSPLToken_CreateTokenAccount(t *testing.T) {
signer := M1Account(t)
owner := "abc"

chain := DevnetChain()
token := NewSPLToken(chain, aTokenAddress)

txn, err := token.CreateTokenAccount(owner, signer.Address())
require.Nil(t, err)

fee, err := chain.EstimateTransactionFee(txn)
require.Nil(t, err)
t.Log(fee.Value)

signedTxn, err := txn.SignedTransactionWithAccount(signer)
require.Nil(t, err)

if false {
txhash, err := chain.SendSignedTransaction(signedTxn)
require.Nil(t, err)
t.Log("create token account for other user success, hash = ", txhash)
}
}
2 changes: 1 addition & 1 deletion core/solana/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (t *Transaction) SignedTransactionWithAccount(account base.Account) (signed
// create tx by message + signer
txn, err := types.NewTransaction(types.NewTransactionParam{
Message: t.Message,
Signers: []types.Account{*solanaAcc.account, *solanaAcc.account},
Signers: []types.Account{*solanaAcc.account},
})
if err != nil {
return nil, err
Expand Down

0 comments on commit 0975550

Please sign in to comment.