Skip to content

Currency creator Solana program client and utilities #201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions pkg/solana/currencycreator/accounts_currency_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package currencycreator

import (
"bytes"
"crypto/ed25519"
"fmt"

"github.com/mr-tron/base58"
)

const (
DefaultMintMaxSupply = 21_000_000_000_000 // 21mm tokens with 6 decimals
DefaultMintDecimals = 6
)

const (
MaxCurrencyConfigAccountNameLength = 32
MaxCurrencyConfigAccountSymbolLength = 8
)

const (
CurrencyConfigAccountSize = (8 + //discriminator
32 + // authority
32 + // creator
32 + // mint
MaxCurrencyConfigAccountNameLength + // name
MaxCurrencyConfigAccountSymbolLength + // symbol
32 + // seed
8 + // max_supply
8 + // current_supply
1 + // decimal_places
1 + // bump
1 + // mint_bump
5) // padding
)

var CurrencyConfigAccountDiscriminator = []byte{byte(AccountTypeCurrencyConfig), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}

type CurrencyConfigAccount struct {
Authority ed25519.PublicKey
Creator ed25519.PublicKey
Mint ed25519.PublicKey
Name string
Symbol string
Seed ed25519.PublicKey
MaxSupply uint64
CurrentSupply uint64
DecimalPlaces uint8
Bump uint8
MintBump uint8
}

func (obj *CurrencyConfigAccount) Unmarshal(data []byte) error {
if len(data) < CurrencyConfigAccountSize {
return ErrInvalidAccountData
}

var offset int

var discriminator []byte
getDiscriminator(data, &discriminator, &offset)
if !bytes.Equal(discriminator, CurrencyConfigAccountDiscriminator) {
return ErrInvalidAccountData
}

getKey(data, &obj.Authority, &offset)
getKey(data, &obj.Creator, &offset)
getKey(data, &obj.Mint, &offset)
getFixedString(data, &obj.Name, MaxCurrencyConfigAccountNameLength, &offset)
getFixedString(data, &obj.Symbol, MaxCurrencyConfigAccountSymbolLength, &offset)
getKey(data, &obj.Seed, &offset)
getUint64(data, &obj.MaxSupply, &offset)
getUint64(data, &obj.CurrentSupply, &offset)
getUint8(data, &obj.DecimalPlaces, &offset)
getUint8(data, &obj.Bump, &offset)
getUint8(data, &obj.MintBump, &offset)
offset += 5 // padding

return nil
}

func (obj *CurrencyConfigAccount) String() string {
return fmt.Sprintf(
"CurrencyConfig{authority=%s,creator=%s,mint=%s,name=%s,symbol=%s,seed=%s,max_supply=%d,currency_supply=%d,decimal_places=%d,bump=%d,mint_bump=%d}",
base58.Encode(obj.Authority),
base58.Encode(obj.Creator),
base58.Encode(obj.Mint),
obj.Name,
obj.Symbol,
base58.Encode(obj.Seed),
obj.MaxSupply,
obj.CurrentSupply,
obj.DecimalPlaces,
obj.Bump,
obj.MintBump,
)
}
131 changes: 131 additions & 0 deletions pkg/solana/currencycreator/accounts_liquidity_pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package currencycreator

import (
"bytes"
"crypto/ed25519"
"fmt"
"time"

"github.com/mr-tron/base58"
)

const (
DefaultBuyCap = 0 // unlimited base tokens
DefaultSaleCap = 0 // unlimited target tokens

DefaultBuyFeeBps = 0 // 0% fee
DefaultSellFeeBps = 100 // 1% fee
)

const (
LiquidityPoolAccountSize = (8 + //discriminator
32 + // authority
32 + // currency
32 + // target_mint
32 + // base_mint
32 + // vault_target
32 + // vault_base
32 + // fee_target
32 + // fee_base
4 + // buy_fee
4 + // sell_fee
8 + // created_unix_time
8 + // go_live_unix_time
8 + // purchase_cap
8 + // sale_cap
RawExponentialCurveSize + // curve
8 + // supply_from_baseonding
1 + // bump
1 + // currency_baseump
1 + // vault_target_baseump
1 + // vault_base_baseump
4) // padding
)

var LiquidityPoolAccountDiscriminator = []byte{byte(AccountTypeLiquidityPool), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}

type LiquidityPoolAccount struct {
Authority ed25519.PublicKey
Currency ed25519.PublicKey
TargetMint ed25519.PublicKey
BaseMint ed25519.PublicKey
VaultTarget ed25519.PublicKey
VaultBase ed25519.PublicKey
FeeTarget ed25519.PublicKey
FeeBase ed25519.PublicKey
BuyFee uint32
SellFee uint32
CreatedUnixTime int64
GoLiveUnixTime int64
PurchaseCap uint64
SaleCap uint64
Curve RawExponentialCurve
SupplyFromBonding uint64
Bump uint8
CurrencyBump uint8
VaultTargetBump uint8
VaultBaseBump uint8
}

func (obj *LiquidityPoolAccount) Unmarshal(data []byte) error {
if len(data) < LiquidityPoolAccountSize {
return ErrInvalidAccountData
}

var offset int

var discriminator []byte
getDiscriminator(data, &discriminator, &offset)
if !bytes.Equal(discriminator, LiquidityPoolAccountDiscriminator) {
return ErrInvalidAccountData
}

getKey(data, &obj.Authority, &offset)
getKey(data, &obj.Currency, &offset)
getKey(data, &obj.TargetMint, &offset)
getKey(data, &obj.BaseMint, &offset)
getKey(data, &obj.VaultTarget, &offset)
getKey(data, &obj.VaultBase, &offset)
getKey(data, &obj.FeeTarget, &offset)
getKey(data, &obj.FeeBase, &offset)
getUint32(data, &obj.BuyFee, &offset)
getUint32(data, &obj.SellFee, &offset)
getInt64(data, &obj.CreatedUnixTime, &offset)
getInt64(data, &obj.GoLiveUnixTime, &offset)
getUint64(data, &obj.PurchaseCap, &offset)
getUint64(data, &obj.SaleCap, &offset)
getRawExponentialCurve(data, &obj.Curve, &offset)
getUint64(data, &obj.SupplyFromBonding, &offset)
getUint8(data, &obj.Bump, &offset)
getUint8(data, &obj.CurrencyBump, &offset)
getUint8(data, &obj.VaultTargetBump, &offset)
getUint8(data, &obj.VaultBaseBump, &offset)
offset += 4 // padding

return nil
}

func (obj *LiquidityPoolAccount) String() string {
return fmt.Sprintf(
"LiquidityPool{authority=%s,currency=%s,target_mint=%s,base_mint=%s,vault_target=%s,vault_base=%s,fee_target=%s,fee_base=%s,buy_fee=%d,sell_fee=%d,created_unix_time=%s,go_live_unix_time=%s,purchase_cap=%d,sale_cap=%d,curve=%s,bump=%d,currency_bump=%d,vault_target_bump=%d,vault_base_bump=%d}",
base58.Encode(obj.Authority),
base58.Encode(obj.Currency),
base58.Encode(obj.TargetMint),
base58.Encode(obj.BaseMint),
base58.Encode(obj.VaultTarget),
base58.Encode(obj.VaultBase),
base58.Encode(obj.FeeTarget),
base58.Encode(obj.FeeBase),
obj.BuyFee,
obj.SellFee,
time.Unix(obj.CreatedUnixTime, 0).UTC().String(),
time.Unix(obj.GoLiveUnixTime, 0).UTC().String(),
obj.PurchaseCap,
obj.SaleCap,
obj.Curve.String(),
obj.Bump,
obj.CurrencyBump,
obj.VaultTargetBump,
obj.VaultBaseBump,
)
}
82 changes: 82 additions & 0 deletions pkg/solana/currencycreator/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package currencycreator

import (
"crypto/ed25519"

"github.com/code-payments/code-server/pkg/solana"
)

var (
MintPrefix = []byte("mint")
CurrencyPrefix = []byte("currency")
PoolPrefix = []byte("pool")
TreasuryPrefix = []byte("treasury")
MetadataPrefix = []byte("metadata")
)

type GetMintAddressArgs struct {
Authority ed25519.PublicKey
Name string
Seed ed25519.PublicKey
}

func GetMintAddress(args *GetMintAddressArgs) (ed25519.PublicKey, uint8, error) {
return solana.FindProgramAddressAndBump(
PROGRAM_ID,
MintPrefix,
args.Authority,
[]byte(toFixedString(args.Name, MaxCurrencyConfigAccountNameLength)),
args.Seed,
)
}

type GetCurrencyAddressArgs struct {
Mint ed25519.PublicKey
}

func GetCurrencyAddress(args *GetCurrencyAddressArgs) (ed25519.PublicKey, uint8, error) {
return solana.FindProgramAddressAndBump(
PROGRAM_ID,
CurrencyPrefix,
args.Mint,
)
}

type GetPoolAddressArgs struct {
Currency ed25519.PublicKey
}

func GetPoolAddress(args *GetPoolAddressArgs) (ed25519.PublicKey, uint8, error) {
return solana.FindProgramAddressAndBump(
PROGRAM_ID,
PoolPrefix,
args.Currency,
)
}

type GetVaultAddressArgs struct {
Pool ed25519.PublicKey
Mint ed25519.PublicKey
}

func GetVaultAddress(args *GetVaultAddressArgs) (ed25519.PublicKey, uint8, error) {
return solana.FindProgramAddressAndBump(
PROGRAM_ID,
TreasuryPrefix,
args.Pool,
args.Mint,
)
}

type GetMetadataAddressArgs struct {
Mint ed25519.PublicKey
}

func GetMetadataAddress(args *GetMetadataAddressArgs) (ed25519.PublicKey, uint8, error) {
return solana.FindProgramAddressAndBump(
METADATA_PROGRAM_ID,
MetadataPrefix,
METADATA_PROGRAM_ID,
args.Mint,
)
}
81 changes: 81 additions & 0 deletions pkg/solana/currencycreator/estimate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package currencycreator

import (
"math"
"math/big"
)

type EstimateCurrentPriceArgs struct {
Curve *ExponentialCurve
CurrentSupplyInQuarks uint64
MintDecimals uint8
}

func EstimateCurrentPrice(args *EstimateCurrentPriceArgs) *big.Float {
scale := big.NewFloat(math.Pow10(int(args.MintDecimals))).SetPrec(defaultExponentialCurvePrec)
unscaledCurrentSupply := big.NewFloat(float64(args.CurrentSupplyInQuarks)).SetPrec(defaultExponentialCurvePrec)
scaledCurrentSupply := new(big.Float).Quo(unscaledCurrentSupply, scale)
return args.Curve.SpotPriceAtSupply(scaledCurrentSupply)
}

type EstimateBuyArgs struct {
BuyAmountInQuarks uint64
Curve *ExponentialCurve
CurrentSupplyInQuarks uint64
BuyFeeBps uint32
TargetMintDecimals uint8
BaseMintDecimals uint8
}

func EstimateBuy(args *EstimateBuyArgs) (uint64, uint64) {
scale := big.NewFloat(math.Pow10(int(args.BaseMintDecimals))).SetPrec(defaultExponentialCurvePrec)
unscaledBuyAmount := big.NewFloat(float64(args.BuyAmountInQuarks)).SetPrec(defaultExponentialCurvePrec)
scaledBuyAmount := new(big.Float).Quo(unscaledBuyAmount, scale)

scale = big.NewFloat(math.Pow10(int(args.TargetMintDecimals))).SetPrec(defaultExponentialCurvePrec)
unscaledCurrentSupply := big.NewFloat(float64(args.CurrentSupplyInQuarks)).SetPrec(defaultExponentialCurvePrec)
scaledCurrentSupply := new(big.Float).Quo(unscaledCurrentSupply, scale)

scale = big.NewFloat(math.Pow10(int(args.TargetMintDecimals))).SetPrec(defaultExponentialCurvePrec)
scaledTotalValue := args.Curve.ValueToTokens(scaledCurrentSupply, scaledBuyAmount)
unscaledTotalValue := new(big.Float).Mul(scaledTotalValue, scale)

feePctValue := new(big.Float).SetPrec(defaultExponentialCurvePrec).Quo(big.NewFloat(float64(args.BuyFeeBps)), big.NewFloat(10000))
scaledFees := new(big.Float).Mul(scaledTotalValue, feePctValue)
unscaledFees := new(big.Float).Mul(scaledFees, scale)

total, _ := unscaledTotalValue.Int64()
fees, _ := unscaledFees.Int64()
return uint64(total - fees), uint64(fees)
}

type EstimateSaleArgs struct {
SellAmountInQuarks uint64
Curve *ExponentialCurve
CurrentSupplyInQuarks uint64
SellFeeBps uint32
TargetMintDecimals uint8
BaseMintDecimals uint8
}

func EstimateSale(args *EstimateSaleArgs) (uint64, uint64) {
scale := big.NewFloat(math.Pow10(int(args.TargetMintDecimals))).SetPrec(defaultExponentialCurvePrec)
unscaledSellAmount := big.NewFloat(float64(args.SellAmountInQuarks)).SetPrec(defaultExponentialCurvePrec)
scaledSellAmount := new(big.Float).Quo(unscaledSellAmount, scale)

scale = big.NewFloat(math.Pow10(int(args.TargetMintDecimals))).SetPrec(defaultExponentialCurvePrec)
unscaledCurrentSupply := big.NewFloat(float64(args.CurrentSupplyInQuarks)).SetPrec(defaultExponentialCurvePrec)
scaledCurrentSupply := new(big.Float).Quo(unscaledCurrentSupply, scale)

scale = big.NewFloat(math.Pow10(int(args.BaseMintDecimals))).SetPrec(defaultExponentialCurvePrec)
scaledTotalValue := args.Curve.TokensToValue(scaledCurrentSupply, scaledSellAmount)
unscaledTotalValue := new(big.Float).Mul(scaledTotalValue, scale)

feePctValue := new(big.Float).SetPrec(defaultExponentialCurvePrec).Quo(big.NewFloat(float64(args.SellFeeBps)), big.NewFloat(10000))
scaledFees := new(big.Float).Mul(scaledTotalValue, feePctValue)
unscaledFees := new(big.Float).Mul(scaledFees, scale)

total, _ := unscaledTotalValue.Int64()
fees, _ := unscaledFees.Int64()
return uint64(total - fees), uint64(fees)
}
Loading
Loading