From f3cb6497f3485aab33d8f33207753929803d2a27 Mon Sep 17 00:00:00 2001 From: Jeff Yanta Date: Fri, 1 Aug 2025 15:26:07 -0400 Subject: [PATCH 1/4] Add initial bonding curve implementation --- pkg/solana/currencycreator/curve.go | 99 ++++++++++++++++++++++++ pkg/solana/currencycreator/curve_test.go | 59 ++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 pkg/solana/currencycreator/curve.go create mode 100644 pkg/solana/currencycreator/curve_test.go diff --git a/pkg/solana/currencycreator/curve.go b/pkg/solana/currencycreator/curve.go new file mode 100644 index 00000000..f0799084 --- /dev/null +++ b/pkg/solana/currencycreator/curve.go @@ -0,0 +1,99 @@ +package currencycreator + +import ( + "math" + "math/big" +) + +// Note: Generated with Grok 4 based on curve.rs, and not 100% accurate with on-chain program + +const defaultPrec = 192 + +type ExponentialCurve struct { + a *big.Float + b *big.Float + c *big.Float +} + +func expBig(x *big.Float) *big.Float { + prec := x.Prec() + result := big.NewFloat(1).SetPrec(prec) + term := big.NewFloat(1).SetPrec(prec) + for i := 1; i < 1000; i++ { + term = term.Mul(term, x) + term = term.Quo(term, big.NewFloat(float64(i))) + old := new(big.Float).Copy(result) + result = result.Add(result, term) + if term.Cmp(new(big.Float).SetFloat64(0)) == 0 { + break + } + if old.Cmp(result) == 0 { + break + } + } + return result +} + +func logBig(y *big.Float) *big.Float { + if y.Sign() <= 0 { + panic("log of non-positive number") + } + yf, _ := y.Float64() + z := big.NewFloat(math.Log(yf)).SetPrec(y.Prec()) + for range 50 { + expz := expBig(z) + adjustment := new(big.Float).Quo(y, expz) + z = z.Add(z, adjustment) + z = z.Sub(z, big.NewFloat(1)) + } + return z +} + +func (curve *ExponentialCurve) SpotPriceAtSupply(currentSupply *big.Float) *big.Float { + cTimesS := new(big.Float).Mul(curve.c, currentSupply) + exp := expBig(cTimesS) + return new(big.Float).Mul(new(big.Float).Mul(curve.a, curve.b), exp) +} + +func (curve *ExponentialCurve) TokensToValue(currentSupply, tokens *big.Float) *big.Float { + newSupply := new(big.Float).Add(currentSupply, tokens) + cs := new(big.Float).Mul(curve.c, currentSupply) + ns := new(big.Float).Mul(curve.c, newSupply) + expCS := expBig(cs) + expNS := expBig(ns) + abOverC := new(big.Float).Quo(new(big.Float).Mul(curve.a, curve.b), curve.c) + diff := new(big.Float).Sub(expNS, expCS) + return new(big.Float).Mul(abOverC, diff) +} + +func (curve *ExponentialCurve) ValueToTokens(currentSupply, value *big.Float) *big.Float { + abOverC := new(big.Float).Quo(new(big.Float).Mul(curve.a, curve.b), curve.c) + expCS := expBig(new(big.Float).Mul(curve.c, currentSupply)) + term := new(big.Float).Add(new(big.Float).Quo(value, abOverC), expCS) + lnTerm := logBig(term) + result := new(big.Float).Quo(lnTerm, curve.c) + return new(big.Float).Sub(result, currentSupply) +} + +func DefaultExponentialCurve() ExponentialCurve { + scale, ok := new(big.Float).SetPrec(defaultPrec).SetString("1000000000000000000") // 10^18 + if !ok { + panic("Invalid scale string") + } + + aInt, ok := new(big.Int).SetString("11400230149967394933471", 10) + if !ok { + panic("Invalid CURVE_A string") + } + + bInt, ok := new(big.Int).SetString("877175273521", 10) + if !ok { + panic("Invalid CURVE_B string") + } + + a := new(big.Float).Quo(new(big.Float).SetInt(aInt).SetPrec(defaultPrec), scale) + b := new(big.Float).Quo(new(big.Float).SetInt(bInt).SetPrec(defaultPrec), scale) + c := new(big.Float).Copy(b) + + return ExponentialCurve{a: a, b: b, c: c} +} diff --git a/pkg/solana/currencycreator/curve_test.go b/pkg/solana/currencycreator/curve_test.go new file mode 100644 index 00000000..5424f56a --- /dev/null +++ b/pkg/solana/currencycreator/curve_test.go @@ -0,0 +1,59 @@ +package currencycreator + +import ( + "fmt" + "math/big" + "testing" +) + +func TestCalculateCurveConstants(t *testing.T) { + curve := DefaultExponentialCurve() + + // Check R'(0) with tolerance + spot0 := curve.SpotPriceAtSupply(big.NewFloat(0)) + expectedStart := big.NewFloat(0.01) + diff := new(big.Float).Sub(spot0, expectedStart) + threshold, _ := new(big.Float).SetString("0.0000000001") + if diff.Abs(diff).Cmp(threshold) > 0 { + t.Errorf("Spot at 0: got %s, expected 0.01", spot0.Text('f', 18)) + } + + // Check R'(21000000) with tolerance + supplyEnd := big.NewFloat(21000000) + spotEnd := curve.SpotPriceAtSupply(supplyEnd) + expectedEnd := big.NewFloat(1000000) + diff = new(big.Float).Sub(spotEnd, expectedEnd) + threshold, _ = new(big.Float).SetString("0.0001") + if diff.Abs(diff).Cmp(threshold) > 0 { + t.Errorf("Spot at end: got %s, expected 1000000", spotEnd.Text('f', 18)) + } +} + +func TestGenerateCurveTable(t *testing.T) { + t.Skip() + + curve := DefaultExponentialCurve() + + fmt.Println("|------|----------------|----------------------------------|----------------------------|") + fmt.Println("| % | S | R(S) | R'(S) |") + fmt.Println("|------|----------------|----------------------------------|----------------------------|") + + zero := big.NewFloat(0) + buyAmount := big.NewFloat(210000) + supply := new(big.Float).Copy(zero) + + for i := 0; i <= 100; i++ { + cost := curve.TokensToValue(zero, supply) + spotPrice := curve.SpotPriceAtSupply(supply) + + fmt.Printf("| %3d%% | %14s | %32s | %26s |\n", + i, + supply.Text('f', 0), + cost.Text('f', 18), + spotPrice.Text('f', 18)) + + supply = supply.Add(supply, buyAmount) + } + + fmt.Println("|------|----------------|----------------------------------|----------------------------|") +} From 3b767a8bf40a3426debc2cff89823262ede6768b Mon Sep 17 00:00:00 2001 From: Jeff Yanta Date: Tue, 5 Aug 2025 15:24:04 -0400 Subject: [PATCH 2/4] Implement Go interface to currency creator Solana program --- .../accounts_currency_config.go | 97 +++++++++++++ .../accounts_liquidity_pool.go | 123 ++++++++++++++++ pkg/solana/currencycreator/address.go | 82 +++++++++++ pkg/solana/currencycreator/estimate.go | 81 +++++++++++ .../{curve.go => exponential_curve.go} | 91 ++++++------ ...urve_test.go => exponential_curve_test.go} | 8 +- .../instructions_buy_tokens.go | 116 +++++++++++++++ .../instructions_initialize_currency.go | 111 ++++++++++++++ .../instructions_initialize_pool.go | 137 ++++++++++++++++++ .../instructions_sell_tokens.go | 116 +++++++++++++++ pkg/solana/currencycreator/program.go | 25 ++++ .../currencycreator/types_account_type.go | 9 ++ .../currencycreator/types_instruction_type.go | 18 +++ .../types_raw_exponential_curve.go | 107 ++++++++++++++ pkg/solana/currencycreator/utils.go | 99 +++++++++++++ pkg/solana/cvm/address.go | 64 -------- pkg/solana/cvm/types_account_type.go | 5 - pkg/solana/cvm/utils.go | 31 ---- 18 files changed, 1167 insertions(+), 153 deletions(-) create mode 100644 pkg/solana/currencycreator/accounts_currency_config.go create mode 100644 pkg/solana/currencycreator/accounts_liquidity_pool.go create mode 100644 pkg/solana/currencycreator/address.go create mode 100644 pkg/solana/currencycreator/estimate.go rename pkg/solana/currencycreator/{curve.go => exponential_curve.go} (78%) rename pkg/solana/currencycreator/{curve_test.go => exponential_curve_test.go} (87%) create mode 100644 pkg/solana/currencycreator/instructions_buy_tokens.go create mode 100644 pkg/solana/currencycreator/instructions_initialize_currency.go create mode 100644 pkg/solana/currencycreator/instructions_initialize_pool.go create mode 100644 pkg/solana/currencycreator/instructions_sell_tokens.go create mode 100644 pkg/solana/currencycreator/program.go create mode 100644 pkg/solana/currencycreator/types_account_type.go create mode 100644 pkg/solana/currencycreator/types_instruction_type.go create mode 100644 pkg/solana/currencycreator/types_raw_exponential_curve.go create mode 100644 pkg/solana/currencycreator/utils.go diff --git a/pkg/solana/currencycreator/accounts_currency_config.go b/pkg/solana/currencycreator/accounts_currency_config.go new file mode 100644 index 00000000..ca2afc0f --- /dev/null +++ b/pkg/solana/currencycreator/accounts_currency_config.go @@ -0,0 +1,97 @@ +package currencycreator + +import ( + "bytes" + "crypto/ed25519" + "fmt" + + "github.com/mr-tron/base58" +) + +const ( + DefaultMintMaxSupply = 21_000_000_000_000 + 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, + ) +} diff --git a/pkg/solana/currencycreator/accounts_liquidity_pool.go b/pkg/solana/currencycreator/accounts_liquidity_pool.go new file mode 100644 index 00000000..35714afa --- /dev/null +++ b/pkg/solana/currencycreator/accounts_liquidity_pool.go @@ -0,0 +1,123 @@ +package currencycreator + +import ( + "bytes" + "crypto/ed25519" + "fmt" + "time" + + "github.com/mr-tron/base58" +) + +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, + ) +} diff --git a/pkg/solana/currencycreator/address.go b/pkg/solana/currencycreator/address.go new file mode 100644 index 00000000..2d388607 --- /dev/null +++ b/pkg/solana/currencycreator/address.go @@ -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, + ) +} diff --git a/pkg/solana/currencycreator/estimate.go b/pkg/solana/currencycreator/estimate.go new file mode 100644 index 00000000..63539eb2 --- /dev/null +++ b/pkg/solana/currencycreator/estimate.go @@ -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) +} diff --git a/pkg/solana/currencycreator/curve.go b/pkg/solana/currencycreator/exponential_curve.go similarity index 78% rename from pkg/solana/currencycreator/curve.go rename to pkg/solana/currencycreator/exponential_curve.go index f0799084..1a600555 100644 --- a/pkg/solana/currencycreator/curve.go +++ b/pkg/solana/currencycreator/exponential_curve.go @@ -7,7 +7,7 @@ import ( // Note: Generated with Grok 4 based on curve.rs, and not 100% accurate with on-chain program -const defaultPrec = 192 +const defaultExponentialCurvePrec = 128 type ExponentialCurve struct { a *big.Float @@ -15,40 +15,6 @@ type ExponentialCurve struct { c *big.Float } -func expBig(x *big.Float) *big.Float { - prec := x.Prec() - result := big.NewFloat(1).SetPrec(prec) - term := big.NewFloat(1).SetPrec(prec) - for i := 1; i < 1000; i++ { - term = term.Mul(term, x) - term = term.Quo(term, big.NewFloat(float64(i))) - old := new(big.Float).Copy(result) - result = result.Add(result, term) - if term.Cmp(new(big.Float).SetFloat64(0)) == 0 { - break - } - if old.Cmp(result) == 0 { - break - } - } - return result -} - -func logBig(y *big.Float) *big.Float { - if y.Sign() <= 0 { - panic("log of non-positive number") - } - yf, _ := y.Float64() - z := big.NewFloat(math.Log(yf)).SetPrec(y.Prec()) - for range 50 { - expz := expBig(z) - adjustment := new(big.Float).Quo(y, expz) - z = z.Add(z, adjustment) - z = z.Sub(z, big.NewFloat(1)) - } - return z -} - func (curve *ExponentialCurve) SpotPriceAtSupply(currentSupply *big.Float) *big.Float { cTimesS := new(big.Float).Mul(curve.c, currentSupply) exp := expBig(cTimesS) @@ -75,25 +41,52 @@ func (curve *ExponentialCurve) ValueToTokens(currentSupply, value *big.Float) *b return new(big.Float).Sub(result, currentSupply) } -func DefaultExponentialCurve() ExponentialCurve { - scale, ok := new(big.Float).SetPrec(defaultPrec).SetString("1000000000000000000") // 10^18 +func DefaultExponentialCurve() *ExponentialCurve { + scale, ok := new(big.Float).SetPrec(defaultExponentialCurvePrec).SetString(defaultCurveScaleString) if !ok { panic("Invalid scale string") } - aInt, ok := new(big.Int).SetString("11400230149967394933471", 10) - if !ok { - panic("Invalid CURVE_A string") - } + aInt, _ := new(big.Int).SetString(defaultCurveAString, 10) + bInt, _ := new(big.Int).SetString(defaultCurveBString, 10) - bInt, ok := new(big.Int).SetString("877175273521", 10) - if !ok { - panic("Invalid CURVE_B string") - } - - a := new(big.Float).Quo(new(big.Float).SetInt(aInt).SetPrec(defaultPrec), scale) - b := new(big.Float).Quo(new(big.Float).SetInt(bInt).SetPrec(defaultPrec), scale) + a := new(big.Float).Quo(new(big.Float).SetPrec(defaultExponentialCurvePrec).SetInt(aInt), scale) + b := new(big.Float).Quo(new(big.Float).SetPrec(defaultExponentialCurvePrec).SetInt(bInt), scale) c := new(big.Float).Copy(b) - return ExponentialCurve{a: a, b: b, c: c} + return &ExponentialCurve{a: a, b: b, c: c} +} + +func expBig(x *big.Float) *big.Float { + prec := x.Prec() + result := big.NewFloat(1).SetPrec(prec) + term := big.NewFloat(1).SetPrec(prec) + for i := 1; i < 1000; i++ { + term = term.Mul(term, x) + term = term.Quo(term, big.NewFloat(float64(i))) + old := new(big.Float).Copy(result) + result = result.Add(result, term) + if term.Cmp(new(big.Float).SetFloat64(0)) == 0 { + break + } + if old.Cmp(result) == 0 { + break + } + } + return result +} + +func logBig(y *big.Float) *big.Float { + if y.Sign() <= 0 { + panic("log of non-positive number") + } + yf, _ := y.Float64() + z := big.NewFloat(math.Log(yf)).SetPrec(y.Prec()) + for range 50 { + expz := expBig(z) + adjustment := new(big.Float).Quo(y, expz) + z = z.Add(z, adjustment) + z = z.Sub(z, big.NewFloat(1)) + } + return z } diff --git a/pkg/solana/currencycreator/curve_test.go b/pkg/solana/currencycreator/exponential_curve_test.go similarity index 87% rename from pkg/solana/currencycreator/curve_test.go rename to pkg/solana/currencycreator/exponential_curve_test.go index 5424f56a..ea153480 100644 --- a/pkg/solana/currencycreator/curve_test.go +++ b/pkg/solana/currencycreator/exponential_curve_test.go @@ -15,7 +15,7 @@ func TestCalculateCurveConstants(t *testing.T) { diff := new(big.Float).Sub(spot0, expectedStart) threshold, _ := new(big.Float).SetString("0.0000000001") if diff.Abs(diff).Cmp(threshold) > 0 { - t.Errorf("Spot at 0: got %s, expected 0.01", spot0.Text('f', 18)) + t.Errorf("Spot at 0: got %s, expected 0.01", spot0.Text('f', RawExponentialCurveDecimals)) } // Check R'(21000000) with tolerance @@ -25,7 +25,7 @@ func TestCalculateCurveConstants(t *testing.T) { diff = new(big.Float).Sub(spotEnd, expectedEnd) threshold, _ = new(big.Float).SetString("0.0001") if diff.Abs(diff).Cmp(threshold) > 0 { - t.Errorf("Spot at end: got %s, expected 1000000", spotEnd.Text('f', 18)) + t.Errorf("Spot at end: got %s, expected 1000000", spotEnd.Text('f', RawExponentialCurveDecimals)) } } @@ -49,8 +49,8 @@ func TestGenerateCurveTable(t *testing.T) { fmt.Printf("| %3d%% | %14s | %32s | %26s |\n", i, supply.Text('f', 0), - cost.Text('f', 18), - spotPrice.Text('f', 18)) + cost.Text('f', RawExponentialCurveDecimals), + spotPrice.Text('f', RawExponentialCurveDecimals)) supply = supply.Add(supply, buyAmount) } diff --git a/pkg/solana/currencycreator/instructions_buy_tokens.go b/pkg/solana/currencycreator/instructions_buy_tokens.go new file mode 100644 index 00000000..e6db97d6 --- /dev/null +++ b/pkg/solana/currencycreator/instructions_buy_tokens.go @@ -0,0 +1,116 @@ +package currencycreator + +import ( + "crypto/ed25519" + + "github.com/code-payments/code-server/pkg/solana" +) + +const ( + BuyTokensInstructionArgsSize = (8 + // in_amount + 8) // min_amount_out +) + +type BuyTokensInstructionArgs struct { + InAmount uint64 + MinAmountOut uint64 +} + +type BuyTokensInstructionAccounts struct { + Buyer ed25519.PublicKey + Pool ed25519.PublicKey + Currency ed25519.PublicKey + TargetMint ed25519.PublicKey + BaseMint ed25519.PublicKey + VaultTarget ed25519.PublicKey + VaultBase ed25519.PublicKey + BuyerTarget ed25519.PublicKey + BuyerBase ed25519.PublicKey + FeeTarget ed25519.PublicKey + FeeBase ed25519.PublicKey +} + +func NewBuyTokensInstruction( + accounts *BuyTokensInstructionAccounts, + args *BuyTokensInstructionArgs, +) solana.Instruction { + var offset int + + // Serialize instruction arguments + data := make([]byte, 1+BuyTokensInstructionArgsSize) + + putInstructionType(data, InstructionTypeBuyTokens, &offset) + putUint64(data, args.InAmount, &offset) + putUint64(data, args.MinAmountOut, &offset) + + return solana.Instruction{ + Program: PROGRAM_ADDRESS, + + // Instruction args + Data: data, + + // Instruction accounts + Accounts: []solana.AccountMeta{ + { + PublicKey: accounts.Buyer, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Pool, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Currency, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.TargetMint, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.BaseMint, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: accounts.VaultTarget, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.VaultBase, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.BuyerTarget, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.BuyerBase, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.FeeTarget, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.FeeBase, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: SPL_TOKEN_PROGRAM_ID, + IsWritable: false, + IsSigner: false, + }, + }, + } +} diff --git a/pkg/solana/currencycreator/instructions_initialize_currency.go b/pkg/solana/currencycreator/instructions_initialize_currency.go new file mode 100644 index 00000000..d379da21 --- /dev/null +++ b/pkg/solana/currencycreator/instructions_initialize_currency.go @@ -0,0 +1,111 @@ +package currencycreator + +import ( + "crypto/ed25519" + + "github.com/code-payments/code-server/pkg/solana" +) + +const ( + InitializeCurrencyInstructionArgsSize = (MaxCurrencyConfigAccountNameLength + // name + MaxCurrencyConfigAccountSymbolLength + // symbol + 32 + // seed + 8 + // max_supply + 1 + // decimal_places + 1 + // bump + 1 + // mint_bump + 5) // padding +) + +type InitializeCurrencyInstructionArgs struct { + Name string + Symbol string + Seed ed25519.PublicKey + MaxSupply uint64 + DecimalPlaces uint8 + Bump uint8 + MintBump uint8 +} + +type InitializeCurrencyInstructionAccounts struct { + Authority ed25519.PublicKey + Creator ed25519.PublicKey + Mint ed25519.PublicKey + Currency ed25519.PublicKey + Metadata ed25519.PublicKey +} + +func NewInitializeCurrencyInstruction( + accounts *InitializeCurrencyInstructionAccounts, + args *InitializeCurrencyInstructionArgs, +) solana.Instruction { + var offset int + + // Serialize instruction arguments + data := make([]byte, 1+InitializeCurrencyInstructionArgsSize) + + putInstructionType(data, InstructionTypeInitializeCurrency, &offset) + putFixedString(data, args.Name, MaxCurrencyConfigAccountNameLength, &offset) + putFixedString(data, args.Symbol, MaxCurrencyConfigAccountSymbolLength, &offset) + putKey(data, args.Seed, &offset) + putUint64(data, args.MaxSupply, &offset) + putUint8(data, args.DecimalPlaces, &offset) + putUint8(data, args.Bump, &offset) + putUint8(data, args.MintBump, &offset) + + return solana.Instruction{ + Program: PROGRAM_ADDRESS, + + // Instruction args + Data: data, + + // Instruction accounts + Accounts: []solana.AccountMeta{ + { + PublicKey: accounts.Authority, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Creator, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Mint, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Currency, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Metadata, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: SPL_TOKEN_PROGRAM_ID, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: METADATA_PROGRAM_ID, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: SYSTEM_PROGRAM_ID, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: SYSVAR_RENT_PUBKEY, + IsWritable: false, + IsSigner: false, + }, + }, + } +} diff --git a/pkg/solana/currencycreator/instructions_initialize_pool.go b/pkg/solana/currencycreator/instructions_initialize_pool.go new file mode 100644 index 00000000..320149ff --- /dev/null +++ b/pkg/solana/currencycreator/instructions_initialize_pool.go @@ -0,0 +1,137 @@ +package currencycreator + +import ( + "crypto/ed25519" + + "github.com/code-payments/code-server/pkg/solana" +) + +const ( + InitializePoolInstructionArgsSize = (8 + // supply + RawExponentialCurveSize + // curve + 8 + // purchase_cap + 8 + // sale_cap + 4 + // buy_fee + 4 + // sell_fee + 8 + // go_live_wait_time + 1 + // bump + 1 + // vault_target_bump + 1 + // vault_base_bump + 5) // padding +) + +type InitializePoolInstructionArgs struct { + Supply uint64 + Curve RawExponentialCurve + PurchaseCap uint64 + SaleCap uint64 + BuyFee uint32 + SellFee uint32 + GoLiveWaitTime uint64 + Bump uint8 + VaultTargetBump uint8 + VaultBaseBump uint8 +} + +type InitializePoolInstructionAccounts struct { + Authority ed25519.PublicKey + Currency ed25519.PublicKey + TargetMint ed25519.PublicKey + BaseMint ed25519.PublicKey + Pool ed25519.PublicKey + VaultTarget ed25519.PublicKey + VaultBase ed25519.PublicKey + FeeTarget ed25519.PublicKey + FeeBase ed25519.PublicKey +} + +func NewInitializePoolInstruction( + accounts *InitializePoolInstructionAccounts, + args *InitializePoolInstructionArgs, +) solana.Instruction { + var offset int + + // Serialize instruction arguments + data := make([]byte, 1+InitializePoolInstructionArgsSize) + + putInstructionType(data, InstructionTypeInitializePool, &offset) + putUint64(data, args.Supply, &offset) + putRawExponentialCurve(data, args.Curve, &offset) + putUint64(data, args.PurchaseCap, &offset) + putUint64(data, args.SaleCap, &offset) + putUint32(data, args.BuyFee, &offset) + putUint32(data, args.SellFee, &offset) + putUint64(data, args.GoLiveWaitTime, &offset) + putUint8(data, args.Bump, &offset) + putUint8(data, args.VaultTargetBump, &offset) + putUint8(data, args.VaultBaseBump, &offset) + + return solana.Instruction{ + Program: PROGRAM_ADDRESS, + + // Instruction args + Data: data, + + // Instruction accounts + Accounts: []solana.AccountMeta{ + { + PublicKey: accounts.Authority, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Currency, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.TargetMint, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.BaseMint, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: accounts.Pool, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.VaultTarget, + IsWritable: true, + IsSigner: false, + }, { + PublicKey: accounts.VaultBase, + IsWritable: true, + IsSigner: false, + }, { + PublicKey: accounts.FeeTarget, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: accounts.FeeBase, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: SPL_TOKEN_PROGRAM_ID, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: SYSTEM_PROGRAM_ID, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: SYSVAR_RENT_PUBKEY, + IsWritable: false, + IsSigner: false, + }, + }, + } +} diff --git a/pkg/solana/currencycreator/instructions_sell_tokens.go b/pkg/solana/currencycreator/instructions_sell_tokens.go new file mode 100644 index 00000000..c41f5464 --- /dev/null +++ b/pkg/solana/currencycreator/instructions_sell_tokens.go @@ -0,0 +1,116 @@ +package currencycreator + +import ( + "crypto/ed25519" + + "github.com/code-payments/code-server/pkg/solana" +) + +const ( + SellTokensInstructionArgsSize = (8 + // in_amount + 8) // min_amount_out +) + +type SellTokensInstructionArgs struct { + InAmount uint64 + MinAmountOut uint64 +} + +type SellTokensInstructionAccounts struct { + Seller ed25519.PublicKey + Pool ed25519.PublicKey + Currency ed25519.PublicKey + TargetMint ed25519.PublicKey + BaseMint ed25519.PublicKey + VaultTarget ed25519.PublicKey + VaultBase ed25519.PublicKey + SellerTarget ed25519.PublicKey + SellerBase ed25519.PublicKey + FeeTarget ed25519.PublicKey + FeeBase ed25519.PublicKey +} + +func NewSellTokensInstruction( + accounts *SellTokensInstructionAccounts, + args *SellTokensInstructionArgs, +) solana.Instruction { + var offset int + + // Serialize instruction arguments + data := make([]byte, 1+SellTokensInstructionArgsSize) + + putInstructionType(data, InstructionTypeSellTokens, &offset) + putUint64(data, args.InAmount, &offset) + putUint64(data, args.MinAmountOut, &offset) + + return solana.Instruction{ + Program: PROGRAM_ADDRESS, + + // Instruction args + Data: data, + + // Instruction accounts + Accounts: []solana.AccountMeta{ + { + PublicKey: accounts.Seller, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Pool, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.Currency, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.TargetMint, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.BaseMint, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: accounts.VaultTarget, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.VaultBase, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.SellerTarget, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.SellerBase, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: accounts.FeeTarget, + IsWritable: false, + IsSigner: false, + }, + { + PublicKey: accounts.FeeBase, + IsWritable: true, + IsSigner: false, + }, + { + PublicKey: SPL_TOKEN_PROGRAM_ID, + IsWritable: false, + IsSigner: false, + }, + }, + } +} diff --git a/pkg/solana/currencycreator/program.go b/pkg/solana/currencycreator/program.go new file mode 100644 index 00000000..8a9f637f --- /dev/null +++ b/pkg/solana/currencycreator/program.go @@ -0,0 +1,25 @@ +package currencycreator + +import ( + "crypto/ed25519" + "errors" +) + +var ( + ErrInvalidProgram = errors.New("invalid program id") + ErrInvalidAccountData = errors.New("unexpected account data") + ErrInvalidInstructionData = errors.New("unexpected instruction data") +) + +var ( + PROGRAM_ADDRESS = mustBase58Decode("AR9ehDtGRnqWdCUy5Z2geTqP5VTQwMzQPfx7b7Dd1HDe") // todo: real address + PROGRAM_ID = ed25519.PublicKey(PROGRAM_ADDRESS) +) + +var ( + METADATA_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")) + SYSTEM_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("11111111111111111111111111111111")) + SPL_TOKEN_PROGRAM_ID = ed25519.PublicKey(mustBase58Decode("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")) + + SYSVAR_RENT_PUBKEY = ed25519.PublicKey(mustBase58Decode("SysvarRent111111111111111111111111111111111")) +) diff --git a/pkg/solana/currencycreator/types_account_type.go b/pkg/solana/currencycreator/types_account_type.go new file mode 100644 index 00000000..9a201d2f --- /dev/null +++ b/pkg/solana/currencycreator/types_account_type.go @@ -0,0 +1,9 @@ +package currencycreator + +type AccountType uint8 + +const ( + AccountTypeUnknown AccountType = iota + AccountTypeCurrencyConfig + AccountTypeLiquidityPool +) diff --git a/pkg/solana/currencycreator/types_instruction_type.go b/pkg/solana/currencycreator/types_instruction_type.go new file mode 100644 index 00000000..35856cdd --- /dev/null +++ b/pkg/solana/currencycreator/types_instruction_type.go @@ -0,0 +1,18 @@ +package currencycreator + +type InstructionType uint8 + +const ( + Unknown InstructionType = iota + + InstructionTypeInitializeCurrency + InstructionTypeInitializePool + + InstructionTypeBuyTokens + InstructionTypeSellTokens +) + +func putInstructionType(dst []byte, v InstructionType, offset *int) { + dst[*offset] = uint8(v) + *offset += 1 +} diff --git a/pkg/solana/currencycreator/types_raw_exponential_curve.go b/pkg/solana/currencycreator/types_raw_exponential_curve.go new file mode 100644 index 00000000..6aa7f0c9 --- /dev/null +++ b/pkg/solana/currencycreator/types_raw_exponential_curve.go @@ -0,0 +1,107 @@ +package currencycreator + +import ( + "fmt" + "math/big" +) + +const ( + RawExponentialCurveDecimals = 18 + + defaultCurveAString = "11400230149967394933471" // 11400.230149967394933471 + defaultCurveBString = "877175273521" // 0.000000877175273521 + defaultCurveScaleString = "1000000000000000000" // 10^18 +) + +var ( + DefaultCurveA [24]byte + DefaultCurveB [24]byte + DefaultCurveC [24]byte +) + +const ( + RawExponentialCurveSize = (24 + // a + 24 + // b + 24) // c +) + +func init() { + aInt, _ := new(big.Int).SetString(defaultCurveAString, 10) + bInt, _ := new(big.Int).SetString(defaultCurveBString, 10) + + aInt.FillBytes(DefaultCurveA[:]) + DefaultCurveA = [24]byte(bigToLittleEndian(DefaultCurveA[:])) + + bInt.FillBytes(DefaultCurveB[:]) + DefaultCurveB = [24]byte(bigToLittleEndian(DefaultCurveB[:])) + + DefaultCurveC = DefaultCurveB +} + +type RawExponentialCurve struct { + A [24]byte + B [24]byte + C [24]byte +} + +func DefaultRawExponentialCurve() RawExponentialCurve { + return RawExponentialCurve{ + A: DefaultCurveA, + B: DefaultCurveB, + C: DefaultCurveC, + } +} + +func (obj *RawExponentialCurve) Unmarshal(data []byte) error { + if len(data) < RawExponentialCurveSize { + return ErrInvalidAccountData + } + + copy(obj.A[:], data[0:24]) + copy(obj.B[:], data[24:48]) + copy(obj.C[:], data[48:72]) + + return nil +} + +func (obj *RawExponentialCurve) ToExponentialCurve() *ExponentialCurve { + scale, ok := new(big.Float).SetPrec(defaultExponentialCurvePrec).SetString("1000000000000000000") // 10^18 + if !ok { + panic("Invalid scale string") + } + + aInt := new(big.Int).SetBytes(littleToBigEndian(obj.A[:])) + bInt := new(big.Int).SetBytes(littleToBigEndian(obj.B[:])) + cInt := new(big.Int).SetBytes(littleToBigEndian(obj.C[:])) + + a := new(big.Float).Quo(new(big.Float).SetPrec(defaultExponentialCurvePrec).SetInt(aInt), scale) + b := new(big.Float).Quo(new(big.Float).SetPrec(defaultExponentialCurvePrec).SetInt(bInt), scale) + c := new(big.Float).Quo(new(big.Float).SetPrec(defaultExponentialCurvePrec).SetInt(cInt), scale) + + return &ExponentialCurve{a: a, b: b, c: c} +} + +func (obj *RawExponentialCurve) String() string { + exponentialCurve := obj.ToExponentialCurve() + return fmt.Sprintf( + "RawExponentialCurve{a=%s,b=%s,c=%s}", + exponentialCurve.a.Text('f', RawExponentialCurveDecimals), + exponentialCurve.b.Text('f', RawExponentialCurveDecimals), + exponentialCurve.c.Text('f', RawExponentialCurveDecimals), + ) +} + +func putRawExponentialCurve(dst []byte, v RawExponentialCurve, offset *int) { + copy(dst[*offset:*offset+24], v.A[:]) + copy(dst[*offset+24:*offset+48], v.B[:]) + copy(dst[*offset+48:*offset+72], v.C[:]) + *offset += RawExponentialCurveSize +} +func getRawExponentialCurve(src []byte, dst *RawExponentialCurve, offset *int) error { + err := dst.Unmarshal(src[*offset:]) + if err != nil { + return err + } + *offset += RawExponentialCurveSize + return nil +} diff --git a/pkg/solana/currencycreator/utils.go b/pkg/solana/currencycreator/utils.go new file mode 100644 index 00000000..c643b2bb --- /dev/null +++ b/pkg/solana/currencycreator/utils.go @@ -0,0 +1,99 @@ +package currencycreator + +import ( + "crypto/ed25519" + "encoding/binary" + "strings" + + "github.com/mr-tron/base58" +) + +func getDiscriminator(src []byte, dst *[]byte, offset *int) { + *dst = make([]byte, 8) + copy(*dst, src[*offset:]) + *offset += 8 +} + +func putKey(dst []byte, v ed25519.PublicKey, offset *int) { + copy(dst[*offset:], v) + *offset += ed25519.PublicKeySize +} +func getKey(src []byte, dst *ed25519.PublicKey, offset *int) { + *dst = make([]byte, ed25519.PublicKeySize) + copy(*dst, src[*offset:]) + *offset += ed25519.PublicKeySize +} + +func putFixedString(dst []byte, v string, length int, offset *int) { + copy(dst[*offset:], toFixedString(v, length)) + *offset += length +} +func getFixedString(data []byte, dst *string, length int, offset *int) { + *dst = string(data[*offset : *offset+length]) + *dst = removeFixedStringPadding(*dst) + *offset += length +} + +func putUint8(dst []byte, v uint8, offset *int) { + dst[*offset] = v + *offset += 1 +} +func getUint8(src []byte, dst *uint8, offset *int) { + *dst = src[*offset] + *offset += 1 +} + +func putUint32(dst []byte, v uint32, offset *int) { + binary.LittleEndian.PutUint32(dst[*offset:], v) + *offset += 4 +} +func getUint32(src []byte, dst *uint32, offset *int) { + *dst = binary.LittleEndian.Uint32(src[*offset:]) + *offset += 4 +} + +func putUint64(dst []byte, v uint64, offset *int) { + binary.LittleEndian.PutUint64(dst[*offset:], v) + *offset += 8 +} +func getUint64(src []byte, dst *uint64, offset *int) { + *dst = binary.LittleEndian.Uint64(src[*offset:]) + *offset += 8 +} + +func getInt64(src []byte, dst *int64, offset *int) { + *dst = int64(binary.LittleEndian.Uint64(src[*offset:])) + *offset += 8 +} + +func toFixedString(value string, length int) string { + fixed := make([]byte, length) + copy(fixed, []byte(value)) + return string(fixed) +} +func removeFixedStringPadding(value string) string { + return strings.TrimRight(value, string([]byte{0})) +} + +func mustBase58Decode(value string) []byte { + decoded, err := base58.Decode(value) + if err != nil { + panic(err) + } + return decoded +} + +func littleToBigEndian(b []byte) []byte { + res := make([]byte, len(b)) + for i := range b { + res[i] = b[len(b)-1-i] + } + return res +} +func bigToLittleEndian(b []byte) []byte { + res := make([]byte, len(b)) + for i := range b { + res[i] = b[len(b)-1-i] + } + return res +} diff --git a/pkg/solana/cvm/address.go b/pkg/solana/cvm/address.go index 16bdac8f..b643e584 100644 --- a/pkg/solana/cvm/address.go +++ b/pkg/solana/cvm/address.go @@ -2,7 +2,6 @@ package cvm import ( "crypto/ed25519" - "encoding/binary" "github.com/code-payments/code-server/pkg/solana" ) @@ -87,75 +86,12 @@ func GetStorageAccountAddress(args *GetMemoryAccountAddressArgs) (ed25519.Public ) } -type GetRelayAccountAddressArgs struct { - Name string - Vm ed25519.PublicKey -} - -func GetRelayAccountAddress(args *GetRelayAccountAddressArgs) (ed25519.PublicKey, uint8, error) { - return solana.FindProgramAddressAndBump( - PROGRAM_ID, - CodeVmPrefix, - VmRelayPrefix, - []byte(toFixedString(args.Name, MaxRelayAccountNameSize)), - args.Vm, - ) -} - type GetRelayProofAddressArgs struct { Relay ed25519.PublicKey MerkleRoot Hash Commitment Hash } -func GetRelayProofAddress(args *GetRelayProofAddressArgs) (ed25519.PublicKey, uint8, error) { - return solana.FindProgramAddressAndBump( - PROGRAM_ID, - CodeVmPrefix, - VmRelayProofPrefix, - args.Relay, - args.MerkleRoot[:], - args.Commitment[:], - ) -} - -type GetRelayCommitmentAddressArgs struct { - Relay ed25519.PublicKey - MerkleRoot Hash - Transcript Hash - Destination ed25519.PublicKey - Amount uint64 -} - -func GetRelayCommitmentAddress(args *GetRelayCommitmentAddressArgs) (ed25519.PublicKey, uint8, error) { - amountBytes := make([]byte, 8) - binary.LittleEndian.PutUint64(amountBytes, args.Amount) - - return solana.FindProgramAddressAndBump( - PROGRAM_ID, - CodeVmPrefix, - VmRelayCommitmentPrefix, - args.Relay, - args.MerkleRoot[:], - args.Transcript[:], - args.Destination, - amountBytes, - ) -} - -type GetRelayDestinationAddressArgs struct { - RelayOrProof ed25519.PublicKey -} - -func GetRelayDestinationAddress(args *GetRelayDestinationAddressArgs) (ed25519.PublicKey, uint8, error) { - return solana.FindProgramAddressAndBump( - SPLITTER_PROGRAM_ID, - CodeVmPrefix, - VmRelayVaultPrefix, - args.RelayOrProof, - ) -} - type GetVirtualTimelockAccountAddressArgs struct { Mint ed25519.PublicKey VmAuthority ed25519.PublicKey diff --git a/pkg/solana/cvm/types_account_type.go b/pkg/solana/cvm/types_account_type.go index 6cdd5842..fb65681a 100644 --- a/pkg/solana/cvm/types_account_type.go +++ b/pkg/solana/cvm/types_account_type.go @@ -11,8 +11,3 @@ const ( AccountTypeUnlockState AccountTypeWithdrawReceipt ) - -func putAccountType(dst []byte, v AccountType, offset *int) { - dst[*offset] = uint8(v) - *offset += 1 -} diff --git a/pkg/solana/cvm/utils.go b/pkg/solana/cvm/utils.go index 6681514e..83caa86e 100644 --- a/pkg/solana/cvm/utils.go +++ b/pkg/solana/cvm/utils.go @@ -8,10 +8,6 @@ import ( "github.com/mr-tron/base58" ) -func putDiscriminator(dst []byte, v []byte, offset *int) { - copy(dst[*offset:], v) - *offset += 8 -} func getDiscriminator(src []byte, dst *[]byte, offset *int) { *dst = make([]byte, 8) copy(*dst, src[*offset:]) @@ -28,28 +24,6 @@ func getKey(src []byte, dst *ed25519.PublicKey, offset *int) { *offset += ed25519.PublicKeySize } -func getBool(src []byte, dst *bool, offset *int) { - if src[*offset] == 1 { - *dst = true - } else { - *dst = false - } - *offset += 1 -} - -func putString(dst []byte, v string, offset *int) { - putUint32(dst, uint32(len(v)), offset) - copy(dst[*offset:], v) - *offset += len(v) -} -func getString(data []byte, dst *string, offset *int) { - length := int(binary.LittleEndian.Uint32(data[*offset:])) - *offset += 4 - - *dst = string(data[*offset : *offset+length]) - *offset += length -} - func putFixedString(dst []byte, v string, length int, offset *int) { copy(dst[*offset:], toFixedString(v, length)) *offset += length @@ -60,11 +34,6 @@ func getFixedString(data []byte, dst *string, length int, offset *int) { *offset += length } -func getBytes(src []byte, dst []byte, length int, offset *int) { - copy(dst[:length], src[*offset:*offset+length]) - *offset += length -} - func putUint8(dst []byte, v uint8, offset *int) { dst[*offset] = v *offset += 1 From c4395ef7723113d23e3b5af1a5fbc5894370acbb Mon Sep 17 00:00:00 2001 From: Jeff Yanta Date: Mon, 11 Aug 2025 14:14:48 -0400 Subject: [PATCH 3/4] Update currency creator program public key --- pkg/solana/currencycreator/program.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/solana/currencycreator/program.go b/pkg/solana/currencycreator/program.go index 8a9f637f..1593150a 100644 --- a/pkg/solana/currencycreator/program.go +++ b/pkg/solana/currencycreator/program.go @@ -12,7 +12,7 @@ var ( ) var ( - PROGRAM_ADDRESS = mustBase58Decode("AR9ehDtGRnqWdCUy5Z2geTqP5VTQwMzQPfx7b7Dd1HDe") // todo: real address + PROGRAM_ADDRESS = mustBase58Decode("ccZLx5N31asHhCa7hFmvdC9EGYVam13L8WXPTjPEiJY") PROGRAM_ID = ed25519.PublicKey(PROGRAM_ADDRESS) ) From 2567d0b691c4266ec88a7ee63d54baf57281c117 Mon Sep 17 00:00:00 2001 From: Jeff Yanta Date: Mon, 11 Aug 2025 19:48:27 -0400 Subject: [PATCH 4/4] Minor tweaks and constant additions --- pkg/solana/currencycreator/accounts_currency_config.go | 2 +- pkg/solana/currencycreator/accounts_liquidity_pool.go | 8 ++++++++ .../currencycreator/instructions_initialize_currency.go | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/solana/currencycreator/accounts_currency_config.go b/pkg/solana/currencycreator/accounts_currency_config.go index ca2afc0f..0dada560 100644 --- a/pkg/solana/currencycreator/accounts_currency_config.go +++ b/pkg/solana/currencycreator/accounts_currency_config.go @@ -9,7 +9,7 @@ import ( ) const ( - DefaultMintMaxSupply = 21_000_000_000_000 + DefaultMintMaxSupply = 21_000_000_000_000 // 21mm tokens with 6 decimals DefaultMintDecimals = 6 ) diff --git a/pkg/solana/currencycreator/accounts_liquidity_pool.go b/pkg/solana/currencycreator/accounts_liquidity_pool.go index 35714afa..f8e60720 100644 --- a/pkg/solana/currencycreator/accounts_liquidity_pool.go +++ b/pkg/solana/currencycreator/accounts_liquidity_pool.go @@ -9,6 +9,14 @@ import ( "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 diff --git a/pkg/solana/currencycreator/instructions_initialize_currency.go b/pkg/solana/currencycreator/instructions_initialize_currency.go index d379da21..fb26c3a0 100644 --- a/pkg/solana/currencycreator/instructions_initialize_currency.go +++ b/pkg/solana/currencycreator/instructions_initialize_currency.go @@ -68,7 +68,7 @@ func NewInitializeCurrencyInstruction( }, { PublicKey: accounts.Creator, - IsWritable: true, + IsWritable: false, IsSigner: false, }, {