From f162f4c3517dbab6d25a649309f670b9f54f6a5a Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 10:05:46 +0100
Subject: [PATCH 001/235] Update error.go
---
plugin/go/contract/error.go | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/plugin/go/contract/error.go b/plugin/go/contract/error.go
index 30a3ab6277..2690eee0ad 100644
--- a/plugin/go/contract/error.go
+++ b/plugin/go/contract/error.go
@@ -19,6 +19,8 @@ func (p *PluginError) Error() string {
return fmt.Sprintf("\nModule: %s\nCode: %d\nMessage: %s", p.Module, p.Code, p.Msg)
}
+// ── Built-in error codes 1–14 (from Canopy template — do not modify) ─────────
+
func ErrPluginTimeout() *PluginError {
return NewError(1, DefaultModule, "a plugin timeout occurred")
}
@@ -74,3 +76,18 @@ func ErrInvalidAmount() *PluginError {
func ErrTxFeeBelowStateLimit() *PluginError {
return NewError(14, DefaultModule, "tx.fee is below state limit")
}
+
+// ── Praxis-specific error codes — start at 15 to avoid built-in conflicts ─────
+
+// ErrWrongOutcome is returned when a claimer's prediction outcome does not
+// match the market's winning outcome. This is semantically distinct from
+// ErrInsufficientFunds and must not reuse code 9.
+func ErrWrongOutcome() *PluginError {
+ return NewError(15, DefaultModule, "prediction outcome does not match the winning outcome")
+}
+
+// ErrDuplicatePrediction is returned when a forecaster attempts to submit
+// a second prediction on a market they have already predicted on.
+func ErrDuplicatePrediction() *PluginError {
+ return NewError(16, DefaultModule, "a prediction already exists for this forecaster and market")
+}
From 40e8f776d184b3af54254fd2910fa1e2bf760dd6 Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 10:07:02 +0100
Subject: [PATCH 002/235] Update contract.go
---
plugin/go/contract/contract.go | 827 +++++++++++++++++++++++++++++----
1 file changed, 734 insertions(+), 93 deletions(-)
diff --git a/plugin/go/contract/contract.go b/plugin/go/contract/contract.go
index 559b5e2b03..09a5adda39 100644
--- a/plugin/go/contract/contract.go
+++ b/plugin/go/contract/contract.go
@@ -12,30 +12,41 @@ import (
"google.golang.org/protobuf/types/known/anypb"
)
-/* This file contains the base contract implementation that overrides the basic 'transfer' functionality */
+// ── ContractConfig ────────────────────────────────────────────────────────────
+// SupportedTransactions[i] MUST match TransactionTypeUrls[i] exactly —
+// any mismatch causes silent misrouting per the Canopy plugin spec.
-// PluginConfig: the configuration of the contract
var ContractConfig = &PluginConfig{
- Name: "go_plugin_contract",
- Id: 1,
- Version: 1,
- SupportedTransactions: []string{"send"},
+ Name: "go_plugin_contract",
+ Id: 1,
+ Version: 1,
+ SupportedTransactions: []string{
+ "send",
+ "create_market",
+ "submit_prediction",
+ "resolve_market",
+ "claim_winnings",
+ },
TransactionTypeUrls: []string{
"type.googleapis.com/types.MessageSend",
+ "type.googleapis.com/types.MessageCreateMarket",
+ "type.googleapis.com/types.MessageSubmitPrediction",
+ "type.googleapis.com/types.MessageResolveMarket",
+ "type.googleapis.com/types.MessageClaimWinnings",
},
EventTypeUrls: nil,
}
-// init sets FileDescriptorProtos after ensuring .pb.go files are initialized
+// init registers all protobuf file descriptors with the FSM during the
+// startup handshake. Do not modify — order and contents must stay in sync
+// with the .proto files compiled into this package.
func init() {
- // Explicitly initialize the proto files first to ensure File_*_proto are set
file_account_proto_init()
file_event_proto_init()
file_plugin_proto_init()
file_tx_proto_init()
var fds [][]byte
- // Include google/protobuf/any.proto first as it's a dependency of event.proto and tx.proto
for _, file := range []protoreflect.FileDescriptor{
anypb.File_google_protobuf_any_proto,
File_account_proto, File_event_proto, File_plugin_proto, File_tx_proto,
@@ -46,152 +57,237 @@ func init() {
ContractConfig.FileDescriptorProtos = fds
}
-// Contract() defines the smart contract that implements the extended logic of the nested chain
+// ── Contract struct ───────────────────────────────────────────────────────────
+
+// Contract implements the Praxis prediction-market application logic.
+// currentHeight is captured in BeginBlock so DeliverTx handlers can
+// reference the current block height (PluginDeliverRequest does not carry it).
type Contract struct {
- Config Config
- FSMConfig *PluginFSMConfig // fsm configuration
- plugin *Plugin // plugin connection
- fsmId uint64 // the id of the requesting fsm
+ Config Config
+ FSMConfig *PluginFSMConfig
+ plugin *Plugin
+ fsmId uint64
+ currentHeight uint64 // set in BeginBlock; read in DeliverTx handlers
}
-// Genesis() implements logic to import a json file to create the state at height 0 and export the state at any height
+// ── Lifecycle ─────────────────────────────────────────────────────────────────
+
+// Genesis imports initial state from a JSON file at chain launch (height 0).
func (c *Contract) Genesis(_ *PluginGenesisRequest) *PluginGenesisResponse {
- return &PluginGenesisResponse{} // TODO map out original token holders
+ return &PluginGenesisResponse{}
}
-// BeginBlock() is code that is executed at the start of `applying` the block
-func (c *Contract) BeginBlock(_ *PluginBeginRequest) *PluginBeginResponse {
+// BeginBlock is called at the start of every block before any transactions
+// are processed. We capture the block height here so DeliverTx handlers
+// can reference it — PluginDeliverRequest does not carry a height field.
+func (c *Contract) BeginBlock(req *PluginBeginRequest) *PluginBeginResponse {
+ c.currentHeight = req.Height
return &PluginBeginResponse{}
}
-// CheckTx() is code that is executed to statelessly validate a transaction
+// EndBlock is called at the end of every block after all transactions have
+// been applied.
+func (c *Contract) EndBlock(_ *PluginEndRequest) *PluginEndResponse {
+ return &PluginEndResponse{}
+}
+
+// ── CheckTx ───────────────────────────────────────────────────────────────────
+// CheckTx is stateless validation. It runs when a transaction enters the
+// mempool on every node. It CANNOT write state. State reads should be
+// minimal (the default template reads fee params here, which is acceptable
+// per the Canopy plugin spec).
+
func (c *Contract) CheckTx(request *PluginCheckRequest) *PluginCheckResponse {
- // validate fee
+ // Read minimum fee parameters from state — this is the one state read
+ // permitted in CheckTx per the default template pattern.
resp, err := c.plugin.StateRead(c, &PluginStateReadRequest{
Keys: []*PluginKeyRead{
{QueryId: rand.Uint64(), Key: KeyForFeeParams()},
- }})
+ },
+ })
+ // StateRead returns transport error as second value AND may embed error
+ // in resp.Error — check both per the plugin spec.
if err == nil {
err = resp.Error
}
- // handle error
if err != nil {
return &PluginCheckResponse{Error: err}
}
- // convert bytes into fee parameters
minFees := new(FeeParams)
if err = Unmarshal(resp.Results[0].Entries[0].Value, minFees); err != nil {
return &PluginCheckResponse{Error: err}
}
- // check for the minimum fee
if request.Tx.Fee < minFees.SendFee {
return &PluginCheckResponse{Error: ErrTxFeeBelowStateLimit()}
}
- // get the message
+
msg, err := FromAny(request.Tx.Msg)
if err != nil {
return &PluginCheckResponse{Error: err}
}
- // handle the message
switch x := msg.(type) {
case *MessageSend:
return c.CheckMessageSend(x)
+ case *MessageCreateMarket:
+ return c.CheckCreateMarket(x)
+ case *MessageSubmitPrediction:
+ return c.CheckSubmitPrediction(x)
+ case *MessageResolveMarket:
+ return c.CheckResolveMarket(x)
+ case *MessageClaimWinnings:
+ return c.CheckClaimWinnings(x)
default:
return &PluginCheckResponse{Error: ErrInvalidMessageCast()}
}
}
-// DeliverTx() is code that is executed to apply a transaction
+// ── DeliverTx ─────────────────────────────────────────────────────────────────
+// DeliverTx is called exactly once per transaction when a block is applied.
+// It can read and write state. If it returns an error the transaction is
+// still recorded in the block as failed and the fee is still charged —
+// CheckTx must catch everything recoverable before a tx enters a block.
+
func (c *Contract) DeliverTx(request *PluginDeliverRequest) *PluginDeliverResponse {
- // get the message
msg, err := FromAny(request.Tx.Msg)
if err != nil {
return &PluginDeliverResponse{Error: err}
}
- // handle the message
switch x := msg.(type) {
case *MessageSend:
return c.DeliverMessageSend(x, request.Tx.Fee)
+ case *MessageCreateMarket:
+ return c.DeliverCreateMarket(x, request.Tx.Fee)
+ case *MessageSubmitPrediction:
+ return c.DeliverSubmitPrediction(x, request.Tx.Fee)
+ case *MessageResolveMarket:
+ return c.DeliverResolveMarket(x, request.Tx.Fee)
+ case *MessageClaimWinnings:
+ return c.DeliverClaimWinnings(x, request.Tx.Fee)
default:
return &PluginDeliverResponse{Error: ErrInvalidMessageCast()}
}
}
-// EndBlock() is code that is executed at the end of 'applying' a block
-func (c *Contract) EndBlock(_ *PluginEndRequest) *PluginEndResponse {
- return &PluginEndResponse{}
-}
+// ── CheckTx handlers ──────────────────────────────────────────────────────────
+// Each handler validates the message stateless-ly and returns the
+// AuthorizedSigners whose BLS signatures the FSM must verify.
-// CheckMessageSend() statelessly validates a 'send' message
func (c *Contract) CheckMessageSend(msg *MessageSend) *PluginCheckResponse {
- // check sender address
if len(msg.FromAddress) != 20 {
return &PluginCheckResponse{Error: ErrInvalidAddress()}
}
- // check recipient address
if len(msg.ToAddress) != 20 {
return &PluginCheckResponse{Error: ErrInvalidAddress()}
}
- // check amount
if msg.Amount == 0 {
return &PluginCheckResponse{Error: ErrInvalidAmount()}
}
- // return the authorized signers
- return &PluginCheckResponse{Recipient: msg.ToAddress, AuthorizedSigners: [][]byte{msg.FromAddress}}
+ return &PluginCheckResponse{
+ Recipient: msg.ToAddress,
+ AuthorizedSigners: [][]byte{msg.FromAddress},
+ }
+}
+
+func (c *Contract) CheckCreateMarket(msg *MessageCreateMarket) *PluginCheckResponse {
+ if len(msg.CreatorAddress) != 20 {
+ return &PluginCheckResponse{Error: ErrInvalidAddress()}
+ }
+ if len(msg.ResolverAddress) != 20 {
+ return &PluginCheckResponse{Error: ErrInvalidAddress()}
+ }
+ if msg.Question == "" {
+ return &PluginCheckResponse{Error: ErrInvalidAmount()}
+ }
+ if msg.StakeAmount == 0 {
+ return &PluginCheckResponse{Error: ErrInvalidAmount()}
+ }
+ // resolutionHeight > currentHeight is enforced in DeliverTx because
+ // CheckTx is stateless and currentHeight is not available here.
+ return &PluginCheckResponse{AuthorizedSigners: [][]byte{msg.CreatorAddress}}
+}
+
+func (c *Contract) CheckSubmitPrediction(msg *MessageSubmitPrediction) *PluginCheckResponse {
+ if len(msg.ForecasterAddress) != 20 {
+ return &PluginCheckResponse{Error: ErrInvalidAddress()}
+ }
+ if msg.MarketId == 0 {
+ return &PluginCheckResponse{Error: ErrInvalidAmount()}
+ }
+ if msg.Outcome != 1 && msg.Outcome != 2 {
+ return &PluginCheckResponse{Error: ErrInvalidAmount()}
+ }
+ if msg.Amount == 0 {
+ return &PluginCheckResponse{Error: ErrInvalidAmount()}
+ }
+ return &PluginCheckResponse{AuthorizedSigners: [][]byte{msg.ForecasterAddress}}
+}
+
+func (c *Contract) CheckResolveMarket(msg *MessageResolveMarket) *PluginCheckResponse {
+ if len(msg.ResolverAddress) != 20 {
+ return &PluginCheckResponse{Error: ErrInvalidAddress()}
+ }
+ if msg.MarketId == 0 {
+ return &PluginCheckResponse{Error: ErrInvalidAmount()}
+ }
+ if msg.WinningOutcome != 1 && msg.WinningOutcome != 2 {
+ return &PluginCheckResponse{Error: ErrInvalidAmount()}
+ }
+ return &PluginCheckResponse{AuthorizedSigners: [][]byte{msg.ResolverAddress}}
}
-// DeliverMessageSend() handles a 'send' message
+func (c *Contract) CheckClaimWinnings(msg *MessageClaimWinnings) *PluginCheckResponse {
+ if len(msg.ClaimerAddress) != 20 {
+ return &PluginCheckResponse{Error: ErrInvalidAddress()}
+ }
+ if msg.MarketId == 0 {
+ return &PluginCheckResponse{Error: ErrInvalidAmount()}
+ }
+ return &PluginCheckResponse{AuthorizedSigners: [][]byte{msg.ClaimerAddress}}
+}
+
+// ── DeliverTx: send ───────────────────────────────────────────────────────────
+
func (c *Contract) DeliverMessageSend(msg *MessageSend, fee uint64) *PluginDeliverResponse {
- log.Printf("DeliverMessageSend called: from=%x to=%x amount=%d fee=%d", msg.FromAddress, msg.ToAddress, msg.Amount, fee)
+ log.Printf("DeliverMessageSend: from=%x to=%x amount=%d fee=%d",
+ msg.FromAddress, msg.ToAddress, msg.Amount, fee)
+
var (
- fromKey, toKey, feePoolKey []byte
- fromBytes, toBytes, feePoolBytes []byte
fromQueryId, toQueryId, feeQueryId = rand.Uint64(), rand.Uint64(), rand.Uint64()
+ fromKey = KeyForAccount(msg.FromAddress)
+ toKey = KeyForAccount(msg.ToAddress)
+ feePoolKey = KeyForFeePool(c.Config.ChainId)
from, to, feePool = new(Account), new(Account), new(Pool)
+ fromBytes, toBytes, feePoolBytes []byte
)
- // calculate the from key and to key
- fromKey, toKey, feePoolKey = KeyForAccount(msg.FromAddress), KeyForAccount(msg.ToAddress), KeyForFeePool(c.Config.ChainId)
- log.Printf("Keys: fromKey=%x toKey=%x feePoolKey=%x", fromKey, toKey, feePoolKey)
- // get the from and to account
+
response, err := c.plugin.StateRead(c, &PluginStateReadRequest{
Keys: []*PluginKeyRead{
{QueryId: feeQueryId, Key: feePoolKey},
{QueryId: fromQueryId, Key: fromKey},
{QueryId: toQueryId, Key: toKey},
- }})
- // check for internal error
+ },
+ })
if err != nil {
- log.Printf("StateRead error: %v", err)
return &PluginDeliverResponse{Error: err}
}
- // ensure no error fsm error
if response.Error != nil {
- log.Printf("StateRead FSM error: %v", response.Error)
return &PluginDeliverResponse{Error: response.Error}
}
- log.Printf("StateRead returned %d results", len(response.Results))
- // get the from bytes and to bytes
- for _, resp := range response.Results {
- log.Printf("Result QueryId=%d Entries=%d", resp.QueryId, len(resp.Entries))
- if len(resp.Entries) == 0 {
- log.Printf("WARNING: No entries for QueryId=%d", resp.QueryId)
+
+ for _, r := range response.Results {
+ if len(r.Entries) == 0 {
continue
}
- switch resp.QueryId {
+ switch r.QueryId {
case fromQueryId:
- fromBytes = resp.Entries[0].Value
- log.Printf("fromBytes len=%d", len(fromBytes))
+ fromBytes = r.Entries[0].Value
case toQueryId:
- toBytes = resp.Entries[0].Value
- log.Printf("toBytes len=%d", len(toBytes))
+ toBytes = r.Entries[0].Value
case feeQueryId:
- feePoolBytes = resp.Entries[0].Value
- log.Printf("feePoolBytes len=%d", len(feePoolBytes))
+ feePoolBytes = r.Entries[0].Value
}
}
- // add fee to 'amount to deduct'
- amountToDeduct := msg.Amount + fee
- // convert the bytes to account structures
+
if err = Unmarshal(fromBytes, from); err != nil {
return &PluginDeliverResponse{Error: err}
}
@@ -201,24 +297,19 @@ func (c *Contract) DeliverMessageSend(msg *MessageSend, fee uint64) *PluginDeliv
if err = Unmarshal(feePoolBytes, feePool); err != nil {
return &PluginDeliverResponse{Error: err}
}
- log.Printf("from.Amount=%d to.Amount=%d feePool.Amount=%d", from.Amount, to.Amount, feePool.Amount)
- // if the account amount is less than the amount to subtract; return insufficient funds
+
+ amountToDeduct := msg.Amount + fee
if from.Amount < amountToDeduct {
- log.Printf("ERROR: Insufficient funds: from.Amount=%d amountToDeduct=%d", from.Amount, amountToDeduct)
return &PluginDeliverResponse{Error: ErrInsufficientFunds()}
}
- // for self-transfer, use same account data
+ // Self-transfer: use the same account object for both sides.
if bytes.Equal(fromKey, toKey) {
to = from
}
- // subtract from sender
from.Amount -= amountToDeduct
- // add the fee to the 'fee pool'
feePool.Amount += fee
- // add to recipient
to.Amount += msg.Amount
- log.Printf("AFTER: from.Amount=%d to.Amount=%d feePool.Amount=%d", from.Amount, to.Amount, feePool.Amount)
- // convert the accounts to bytes
+
fromBytes, err = Marshal(from)
if err != nil {
return &PluginDeliverResponse{Error: err}
@@ -231,11 +322,11 @@ func (c *Contract) DeliverMessageSend(msg *MessageSend, fee uint64) *PluginDeliv
if err != nil {
return &PluginDeliverResponse{Error: err}
}
- // execute writes to the database
- var resp *PluginStateWriteResponse
- // if the from account is drained - delete the from account
+
+ // If the sender account is drained, delete it to keep state minimal.
+ var writeResp *PluginStateWriteResponse
if from.Amount == 0 {
- resp, err = c.plugin.StateWrite(c, &PluginStateWriteRequest{
+ writeResp, err = c.plugin.StateWrite(c, &PluginStateWriteRequest{
Sets: []*PluginSetOp{
{Key: feePoolKey, Value: feePoolBytes},
{Key: toKey, Value: toBytes},
@@ -243,7 +334,7 @@ func (c *Contract) DeliverMessageSend(msg *MessageSend, fee uint64) *PluginDeliv
Deletes: []*PluginDeleteOp{{Key: fromKey}},
})
} else {
- resp, err = c.plugin.StateWrite(c, &PluginStateWriteRequest{
+ writeResp, err = c.plugin.StateWrite(c, &PluginStateWriteRequest{
Sets: []*PluginSetOp{
{Key: feePoolKey, Value: feePoolBytes},
{Key: toKey, Value: toBytes},
@@ -252,38 +343,588 @@ func (c *Contract) DeliverMessageSend(msg *MessageSend, fee uint64) *PluginDeliv
})
}
if err != nil {
- log.Printf("StateWrite internal error: %v", err)
return &PluginDeliverResponse{Error: err}
}
- if resp.Error != nil {
- log.Printf("StateWrite FSM error: %v", resp.Error)
- return &PluginDeliverResponse{Error: resp.Error}
+ if writeResp.Error != nil {
+ return &PluginDeliverResponse{Error: writeResp.Error}
+ }
+ return &PluginDeliverResponse{}
+}
+
+// ── DeliverTx: create_market ──────────────────────────────────────────────────
+
+func (c *Contract) DeliverCreateMarket(msg *MessageCreateMarket, fee uint64) *PluginDeliverResponse {
+ log.Printf("DeliverCreateMarket: creator=%x question=%q stakeAmount=%d height=%d",
+ msg.CreatorAddress, msg.Question, msg.StakeAmount, c.currentHeight)
+
+ // Enforce that the resolution height is in the future.
+ // This cannot be done in CheckTx because currentHeight is not available there.
+ if msg.ResolutionHeight <= c.currentHeight {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+
+ counterQId := rand.Uint64()
+ creatorQId := rand.Uint64()
+ feeQId := rand.Uint64()
+
+ counterKey := KeyForMarketCounter()
+ creatorKey := KeyForAccount(msg.CreatorAddress)
+ feePoolKey := KeyForFeePool(c.Config.ChainId)
+
+ readResp, err := c.plugin.StateRead(c, &PluginStateReadRequest{
+ Keys: []*PluginKeyRead{
+ {QueryId: counterQId, Key: counterKey},
+ {QueryId: creatorQId, Key: creatorKey},
+ {QueryId: feeQId, Key: feePoolKey},
+ },
+ })
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if readResp.Error != nil {
+ return &PluginDeliverResponse{Error: readResp.Error}
+ }
+
+ counter := new(MarketCounter)
+ creator := new(Account)
+ feePool := new(Pool)
+ var counterBytes, creatorBytes, feePoolBytes []byte
+
+ for _, r := range readResp.Results {
+ if len(r.Entries) == 0 {
+ continue
+ }
+ switch r.QueryId {
+ case counterQId:
+ counterBytes = r.Entries[0].Value
+ case creatorQId:
+ creatorBytes = r.Entries[0].Value
+ case feeQId:
+ feePoolBytes = r.Entries[0].Value
+ }
+ }
+
+ if err = Unmarshal(counterBytes, counter); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if err = Unmarshal(creatorBytes, creator); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if err = Unmarshal(feePoolBytes, feePool); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+
+ // Creator must be able to afford the stake bond plus the transaction fee.
+ totalCost := msg.StakeAmount + fee
+ if creator.Amount < totalCost {
+ return &PluginDeliverResponse{Error: ErrInsufficientFunds()}
+ }
+
+ // Assign the next market ID by incrementing the counter singleton.
+ counter.Count++
+ newMarket := &Market{
+ Id: counter.Count,
+ CreatorAddress: msg.CreatorAddress,
+ Question: msg.Question,
+ Description: msg.Description,
+ ResolverAddress: msg.ResolverAddress,
+ ResolutionHeight: msg.ResolutionHeight,
+ StakeAmount: msg.StakeAmount,
+ YesPool: 0,
+ NoPool: 0,
+ Status: 0, // 0 = open
+ WinningOutcome: 0,
+ }
+
+ creator.Amount -= totalCost
+ feePool.Amount += fee
+
+ marketBytes, err := Marshal(newMarket)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ counterBytes, err = Marshal(counter)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ creatorBytes, err = Marshal(creator)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ feePoolBytes, err = Marshal(feePool)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+
+ writeResp, err := c.plugin.StateWrite(c, &PluginStateWriteRequest{
+ Sets: []*PluginSetOp{
+ {Key: KeyForMarket(newMarket.Id), Value: marketBytes},
+ {Key: counterKey, Value: counterBytes},
+ {Key: creatorKey, Value: creatorBytes},
+ {Key: feePoolKey, Value: feePoolBytes},
+ },
+ })
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if writeResp.Error != nil {
+ return &PluginDeliverResponse{Error: writeResp.Error}
}
- log.Printf("StateWrite SUCCESS!")
+ log.Printf("Market #%d created: %q", newMarket.Id, newMarket.Question)
return &PluginDeliverResponse{}
}
+// ── DeliverTx: submit_prediction ─────────────────────────────────────────────
+
+func (c *Contract) DeliverSubmitPrediction(msg *MessageSubmitPrediction, fee uint64) *PluginDeliverResponse {
+ log.Printf("DeliverSubmitPrediction: forecaster=%x marketId=%d outcome=%d amount=%d",
+ msg.ForecasterAddress, msg.MarketId, msg.Outcome, msg.Amount)
+
+ marketQId := rand.Uint64()
+ forecasterQId := rand.Uint64()
+ feeQId := rand.Uint64()
+ predQId := rand.Uint64() // read existing prediction to prevent duplicates
+
+ marketKey := KeyForMarket(msg.MarketId)
+ forecasterKey := KeyForAccount(msg.ForecasterAddress)
+ feePoolKey := KeyForFeePool(c.Config.ChainId)
+ predKey := KeyForPrediction(msg.ForecasterAddress, msg.MarketId)
+
+ // Batch-read all relevant keys in one call per the plugin spec pattern.
+ readResp, err := c.plugin.StateRead(c, &PluginStateReadRequest{
+ Keys: []*PluginKeyRead{
+ {QueryId: marketQId, Key: marketKey},
+ {QueryId: forecasterQId, Key: forecasterKey},
+ {QueryId: feeQId, Key: feePoolKey},
+ {QueryId: predQId, Key: predKey},
+ },
+ })
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if readResp.Error != nil {
+ return &PluginDeliverResponse{Error: readResp.Error}
+ }
+
+ market := new(Market)
+ forecaster := new(Account)
+ feePool := new(Pool)
+ existingPred := new(Prediction)
+ var marketBytes, forecasterBytes, feePoolBytes, existingPredBytes []byte
+
+ for _, r := range readResp.Results {
+ if len(r.Entries) == 0 {
+ continue
+ }
+ switch r.QueryId {
+ case marketQId:
+ marketBytes = r.Entries[0].Value
+ case forecasterQId:
+ forecasterBytes = r.Entries[0].Value
+ case feeQId:
+ feePoolBytes = r.Entries[0].Value
+ case predQId:
+ existingPredBytes = r.Entries[0].Value
+ }
+ }
+
+ if err = Unmarshal(marketBytes, market); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ // Market must exist.
+ if market.Id == 0 {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+ // Market must still be open (status 0).
+ if market.Status != 0 {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+
+ // FIX: Prevent duplicate predictions. A forecaster may only submit once
+ // per market. Without this guard the second submission silently overwrites
+ // the first, losing the original stake from the pool accounting.
+ if err = Unmarshal(existingPredBytes, existingPred); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if existingPred.MarketId != 0 {
+ return &PluginDeliverResponse{Error: ErrDuplicatePrediction()}
+ }
+
+ if err = Unmarshal(forecasterBytes, forecaster); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if err = Unmarshal(feePoolBytes, feePool); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+
+ totalCost := msg.Amount + fee
+ if forecaster.Amount < totalCost {
+ return &PluginDeliverResponse{Error: ErrInsufficientFunds()}
+ }
+
+ forecaster.Amount -= totalCost
+ feePool.Amount += fee
+
+ if msg.Outcome == 1 {
+ market.YesPool += msg.Amount
+ } else {
+ market.NoPool += msg.Amount
+ }
+
+ newPrediction := &Prediction{
+ ForecasterAddress: msg.ForecasterAddress,
+ MarketId: msg.MarketId,
+ Outcome: msg.Outcome,
+ Amount: msg.Amount,
+ Claimed: false,
+ }
+
+ marketBytes, err = Marshal(market)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ forecasterBytes, err = Marshal(forecaster)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ feePoolBytes, err = Marshal(feePool)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ predBytes, err := Marshal(newPrediction)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+
+ writeResp, err := c.plugin.StateWrite(c, &PluginStateWriteRequest{
+ Sets: []*PluginSetOp{
+ {Key: marketKey, Value: marketBytes},
+ {Key: forecasterKey, Value: forecasterBytes},
+ {Key: feePoolKey, Value: feePoolBytes},
+ {Key: predKey, Value: predBytes},
+ },
+ })
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if writeResp.Error != nil {
+ return &PluginDeliverResponse{Error: writeResp.Error}
+ }
+ log.Printf("Prediction stored: market=%d outcome=%d amount=%d",
+ msg.MarketId, msg.Outcome, msg.Amount)
+ return &PluginDeliverResponse{}
+}
+
+// ── DeliverTx: resolve_market ─────────────────────────────────────────────────
+
+func (c *Contract) DeliverResolveMarket(msg *MessageResolveMarket, fee uint64) *PluginDeliverResponse {
+ log.Printf("DeliverResolveMarket: resolver=%x marketId=%d winningOutcome=%d",
+ msg.ResolverAddress, msg.MarketId, msg.WinningOutcome)
+
+ marketQId := rand.Uint64()
+ resolverQId := rand.Uint64()
+ feeQId := rand.Uint64()
+
+ marketKey := KeyForMarket(msg.MarketId)
+ resolverKey := KeyForAccount(msg.ResolverAddress)
+ feePoolKey := KeyForFeePool(c.Config.ChainId)
+
+ readResp, err := c.plugin.StateRead(c, &PluginStateReadRequest{
+ Keys: []*PluginKeyRead{
+ {QueryId: marketQId, Key: marketKey},
+ {QueryId: resolverQId, Key: resolverKey},
+ {QueryId: feeQId, Key: feePoolKey},
+ },
+ })
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if readResp.Error != nil {
+ return &PluginDeliverResponse{Error: readResp.Error}
+ }
+
+ market := new(Market)
+ resolver := new(Account)
+ feePool := new(Pool)
+ var marketBytes, resolverBytes, feePoolBytes []byte
+
+ for _, r := range readResp.Results {
+ if len(r.Entries) == 0 {
+ continue
+ }
+ switch r.QueryId {
+ case marketQId:
+ marketBytes = r.Entries[0].Value
+ case resolverQId:
+ resolverBytes = r.Entries[0].Value
+ case feeQId:
+ feePoolBytes = r.Entries[0].Value
+ }
+ }
+
+ if err = Unmarshal(marketBytes, market); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ // Market must exist.
+ if market.Id == 0 {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+ // Market must still be open.
+ if market.Status != 0 {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+ // Only the designated resolver address may call resolve.
+ if !bytes.Equal(market.ResolverAddress, msg.ResolverAddress) {
+ return &PluginDeliverResponse{Error: ErrInvalidAddress()}
+ }
+
+ if err = Unmarshal(resolverBytes, resolver); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if err = Unmarshal(feePoolBytes, feePool); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if resolver.Amount < fee {
+ return &PluginDeliverResponse{Error: ErrInsufficientFunds()}
+ }
+
+ resolver.Amount -= fee
+ feePool.Amount += fee
+ market.Status = 1 // 1 = resolved
+ market.WinningOutcome = msg.WinningOutcome
+
+ marketBytes, err = Marshal(market)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ resolverBytes, err = Marshal(resolver)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ feePoolBytes, err = Marshal(feePool)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+
+ writeResp, err := c.plugin.StateWrite(c, &PluginStateWriteRequest{
+ Sets: []*PluginSetOp{
+ {Key: marketKey, Value: marketBytes},
+ {Key: resolverKey, Value: resolverBytes},
+ {Key: feePoolKey, Value: feePoolBytes},
+ },
+ })
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if writeResp.Error != nil {
+ return &PluginDeliverResponse{Error: writeResp.Error}
+ }
+ log.Printf("Market #%d resolved: winningOutcome=%d", market.Id, market.WinningOutcome)
+ return &PluginDeliverResponse{}
+}
+
+// ── DeliverTx: claim_winnings ─────────────────────────────────────────────────
+
+func (c *Contract) DeliverClaimWinnings(msg *MessageClaimWinnings, fee uint64) *PluginDeliverResponse {
+ log.Printf("DeliverClaimWinnings: claimer=%x marketId=%d", msg.ClaimerAddress, msg.MarketId)
+
+ marketQId := rand.Uint64()
+ claimerQId := rand.Uint64()
+ feeQId := rand.Uint64()
+ predQId := rand.Uint64()
+
+ marketKey := KeyForMarket(msg.MarketId)
+ claimerKey := KeyForAccount(msg.ClaimerAddress)
+ feePoolKey := KeyForFeePool(c.Config.ChainId)
+ predKey := KeyForPrediction(msg.ClaimerAddress, msg.MarketId)
+
+ readResp, err := c.plugin.StateRead(c, &PluginStateReadRequest{
+ Keys: []*PluginKeyRead{
+ {QueryId: marketQId, Key: marketKey},
+ {QueryId: claimerQId, Key: claimerKey},
+ {QueryId: feeQId, Key: feePoolKey},
+ {QueryId: predQId, Key: predKey},
+ },
+ })
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if readResp.Error != nil {
+ return &PluginDeliverResponse{Error: readResp.Error}
+ }
+
+ market := new(Market)
+ claimer := new(Account)
+ feePool := new(Pool)
+ prediction := new(Prediction)
+ var marketBytes, claimerBytes, feePoolBytes, predBytes []byte
+
+ for _, r := range readResp.Results {
+ if len(r.Entries) == 0 {
+ continue
+ }
+ switch r.QueryId {
+ case marketQId:
+ marketBytes = r.Entries[0].Value
+ case claimerQId:
+ claimerBytes = r.Entries[0].Value
+ case feeQId:
+ feePoolBytes = r.Entries[0].Value
+ case predQId:
+ predBytes = r.Entries[0].Value
+ }
+ }
+
+ if err = Unmarshal(marketBytes, market); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ // Market must exist.
+ if market.Id == 0 {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+ // Market must be resolved (status 1) before anyone can claim.
+ if market.Status != 1 {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+
+ if err = Unmarshal(predBytes, prediction); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ // Claimer must have a prediction recorded for this market.
+ if prediction.MarketId == 0 {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+ // Cannot claim more than once.
+ if prediction.Claimed {
+ return &PluginDeliverResponse{Error: ErrInvalidAmount()}
+ }
+ // FIX: use ErrWrongOutcome instead of ErrInsufficientFunds for semantic
+ // correctness — the claimer picked the losing side, not a funds issue.
+ if prediction.Outcome != market.WinningOutcome {
+ return &PluginDeliverResponse{Error: ErrWrongOutcome()}
+ }
+
+ if err = Unmarshal(claimerBytes, claimer); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if err = Unmarshal(feePoolBytes, feePool); err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if claimer.Amount < fee {
+ return &PluginDeliverResponse{Error: ErrInsufficientFunds()}
+ }
+
+ // Proportional payout: winner gets their stake back plus a share of
+ // the losing pool proportional to their contribution to the winning pool.
+ var winPool, losePool uint64
+ if market.WinningOutcome == 1 {
+ winPool = market.YesPool
+ losePool = market.NoPool
+ } else {
+ winPool = market.NoPool
+ losePool = market.YesPool
+ }
+
+ var payout uint64
+ if winPool > 0 {
+ payout = prediction.Amount + (prediction.Amount*losePool)/winPool
+ } else {
+ // Edge case: no one bet on the losing side — return original stake.
+ payout = prediction.Amount
+ }
+
+ claimer.Amount = claimer.Amount - fee + payout
+ feePool.Amount += fee
+ prediction.Claimed = true
+
+ claimerBytes, err = Marshal(claimer)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ feePoolBytes, err = Marshal(feePool)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ predBytes, err = Marshal(prediction)
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+
+ writeResp, err := c.plugin.StateWrite(c, &PluginStateWriteRequest{
+ Sets: []*PluginSetOp{
+ {Key: claimerKey, Value: claimerBytes},
+ {Key: feePoolKey, Value: feePoolBytes},
+ {Key: predKey, Value: predBytes},
+ },
+ })
+ if err != nil {
+ return &PluginDeliverResponse{Error: err}
+ }
+ if writeResp.Error != nil {
+ return &PluginDeliverResponse{Error: writeResp.Error}
+ }
+ log.Printf("Winnings claimed: market=%d claimer=%x payout=%d",
+ msg.MarketId, msg.ClaimerAddress, payout)
+ return &PluginDeliverResponse{}
+}
+
+// ── State key prefixes ────────────────────────────────────────────────────────
+//
+// Byte prefixes namespace each state type in the shared key-value store.
+// All keys are constructed with JoinLenPrefix which length-prefixes each
+// segment, so the raw key bytes always start with a length byte (0x01 for
+// a 1-byte prefix segment) followed by the prefix value.
+//
+// Built-in prefixes from the Canopy template — do not reuse:
+// 0x01 Account
+// 0x02 Pool (fee pool)
+// 0x07 FeeParams
+//
+// Praxis-specific prefixes — must not conflict with built-ins:
+// 0x10 Market
+// 0x11 MarketCounter (singleton)
+// 0x12 Prediction
+
var (
- accountPrefix = []byte{1} // store key prefix for accounts
- poolPrefix = []byte{2} // store key prefix for pools
- paramsPrefix = []byte{7} // store key prefix for governance parameters
+ accountPrefix = []byte{0x01}
+ poolPrefix = []byte{0x02}
+ paramsPrefix = []byte{0x07}
+ marketPrefix = []byte{0x10}
+ marketCounterPrefix = []byte{0x11}
+ predictionPrefix = []byte{0x12}
)
-// KeyForAccount() returns the state database key for an account
func KeyForAccount(addr []byte) []byte {
return JoinLenPrefix(accountPrefix, addr)
}
-// KeyForFeeParams() returns the state database key for governance controlled 'fee parameters'
func KeyForFeeParams() []byte {
return JoinLenPrefix(paramsPrefix, []byte("/f/"))
}
-// KeyForFeeParams() returns the state database key for governance controlled 'fee parameters'
func KeyForFeePool(chainId uint64) []byte {
return JoinLenPrefix(poolPrefix, formatUint64(chainId))
}
+func KeyForMarket(id uint64) []byte {
+ return JoinLenPrefix(marketPrefix, formatUint64(id))
+}
+
+func KeyForMarketCounter() []byte {
+ return JoinLenPrefix(marketCounterPrefix, []byte("/mc/"))
+}
+
+// KeyForPrediction builds a composite key from the forecaster address and
+// market ID. We allocate a new slice explicitly to avoid mutating the
+// underlying array of the addr argument (a common Go slice-append pitfall).
+func KeyForPrediction(addr []byte, marketId uint64) []byte {
+ idBytes := formatUint64(marketId)
+ composite := make([]byte, len(addr)+8)
+ copy(composite, addr)
+ copy(composite[len(addr):], idBytes)
+ return JoinLenPrefix(predictionPrefix, composite)
+}
+
func formatUint64(u uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, u)
From 23764e946799724c71bde9b479ddc4fbe7eabbca Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 10:24:09 +0100
Subject: [PATCH 003/235] index.html
---
frontend/folder | 1172 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1172 insertions(+)
create mode 100644 frontend/folder
diff --git a/frontend/folder b/frontend/folder
new file mode 100644
index 0000000000..84d9b4ddb5
--- /dev/null
+++ b/frontend/folder
@@ -0,0 +1,1172 @@
+
+
+
+
+
+Praxis — Prediction Markets on Canopy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
All Markets
+
Live prediction markets on this Canopy Nested Chain
+
+
+
+
+
+
+
+
+
+
+
Create Market
+
Open a new YES/NO prediction market
+
+
+
+
Market Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Must be greater than current height
+
+
+
+
+
Min 1,000,000 μPRX = 1 $PRX
+
+
+
+
+
+
+
+
+
+
+
+
Unsigned Payload (copy to sign manually)
+
+
+
+
+
+
+
+
+
+
Submit Prediction
+
Stake on a YES or NO outcome
+
+
+
+
Prediction Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Resolve Market
+
Finalise a market with the winning outcome
+
+
+
+
Resolution Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Claim Winnings
+
Collect your proportional payout from a resolved market
+
+
+
+
Claim Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Wallet
+
Query any account on this chain
+
+
+
+
Account Lookup
+
+
+
+
+
+
+
+
+
0
+
μPRX liquid
+
+ Staked: 0 μPRX
+
+
+
+
+
+
+
+
Send Tokens
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Signer
+
Load a BLS12-381 private key to sign transactions
+
+
+
+
Private Key
+
No key loaded — transactions cannot be signed
+
+
+
+
⚠ Never enter keys on untrusted sites. This key is held in memory only and never stored.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Node Info
+
Connection settings and chain status
+
+
+
+
RPC Connection
+
+
+
+
Node public RPC will be http://host:50002 — admin RPC on :50003
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 40fa8d3344a9ff97610e3a9cf8356944d4711375 Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 10:26:15 +0100
Subject: [PATCH 004/235] Update AGENTS.md
---
plugin/go/AGENTS.md | 272 ++++++++++++++------------------------------
1 file changed, 84 insertions(+), 188 deletions(-)
diff --git a/plugin/go/AGENTS.md b/plugin/go/AGENTS.md
index e89a413e14..ead3e0bd75 100644
--- a/plugin/go/AGENTS.md
+++ b/plugin/go/AGENTS.md
@@ -1,214 +1,110 @@
-# Agent Instructions for Go Plugin
+# Praxis Plugin — AI Agent Context
-This document provides context for AI agents working with the Canopy Go plugin codebase.
+This file provides context for AI coding assistants working on the Praxis
+prediction-market plugin for the Canopy Network.
-## Overview
+## What This Is
-This is a **Go plugin for the Canopy blockchain** that communicates with the Canopy FSM (Finite State Machine) via Unix sockets. The plugin implements custom transaction types and state management for a nested blockchain.
+Praxis is an on-chain YES/NO prediction market protocol built as a Canopy
+Nested Chain plugin. It is written in Go and implements the Canopy plugin
+interface (the five lifecycle methods: Genesis, BeginBlock, CheckTx,
+DeliverTx, EndBlock).
-## Architecture
+## Repository Layout
-### Communication Pattern
-- **Protocol**: Length-prefixed protobuf messages over Unix socket (`/tmp/plugin/plugin.sock`)
-- **Flow**: FSM ↔ Plugin via `FSMToPlugin` and `PluginToFSM` protobuf messages
-- **Lifecycle**: Plugin connects → Handshake → Receives tx requests → Responds with results
-
-### Key Components
-
-| Directory | Purpose |
-|-----------|---------|
-| `contract/` | Core contract logic, protobuf generated code, plugin communication |
-| `crypto/` | BLS12-381 signing utilities |
-| `proto/` | Protobuf definitions (`.proto` files) |
-| `tutorial/` | Test project with pre-built faucet/reward transaction examples |
-
-### Important Files
-
-- `contract/contract.go` - Main contract logic with `CheckTx` and `DeliverTx` handlers
-- `contract/plugin.go` - Socket communication and plugin lifecycle
-- `contract/*.pb.go` - Generated protobuf code (do not edit manually)
-- `proto/tx.proto` - Transaction message definitions
-- `main.go` - Entry point
-
-## Transaction Flow
-
-1. **CheckTx**: Stateless validation (fee check, address validation, returns authorized signers)
-2. **DeliverTx**: Stateful execution (reads state, applies changes, writes state)
-
-## Adding New Transaction Types
-
-Follow the pattern in `TUTORIAL.md`:
-
-1. Add message to `proto/tx.proto`
-2. Run `proto/_generate.sh` to regenerate Go code
-3. Register in `ContractConfig.SupportedTransactions` and `TransactionTypeUrls`
-4. Add `case` in `CheckTx` switch → implement `CheckMessage`
-5. Add `case` in `DeliverTx` switch → implement `DeliverMessage`
-
-## State Management
-
-### Key Prefixes
-- `[]byte{1}` - Account storage
-- `[]byte{2}` - Pool storage
-- `[]byte{7}` - Governance parameters
-
-### State Operations
-```go
-// Read state
-c.plugin.StateRead(c, &PluginStateReadRequest{Keys: []*PluginKeyRead{...}})
-
-// Write state
-c.plugin.StateWrite(c, &PluginStateWriteRequest{Sets: [...], Deletes: [...]})
```
-
-## Cryptography
-
-- **Signature scheme**: BLS12-381 (not Ed25519)
-- **Address derivation**: First 20 bytes of SHA256(publicKey)
-- **Sign bytes**: Deterministic protobuf marshaling of Transaction (without signature field)
-
-## Building
-
-```bash
-cd plugin/go
-make build # Builds to plugin/go/go-plugin
+plugin/go/
+├── main.go # Entry point — calls contract.StartPlugin(). Do not modify.
+├── chain.json # Chain metadata (name, symbol, chainId, networkId)
+├── Makefile # Build targets — run `make build` before `canopy start`
+├── pluginctl.sh # Plugin lifecycle script (start/stop/restart/status)
+│
+├── contract/
+│ ├── plugin.go # Socket protocol, StartPlugin(), StateRead/StateWrite. Do not modify.
+│ ├── contract.go # ALL application logic lives here — this is the file to edit
+│ └── error.go # Plugin error codes. Built-in: 1–14. Praxis: 15–16.
+│
+└── proto/
+ ├── tx.proto # Transaction and state message definitions
+ ├── account.proto # Account and Pool types (built-in, do not modify)
+ ├── plugin.proto # FSM communication protocol (do not modify)
+ ├── event.proto # Event types (do not modify)
+ └── _generate.sh # Run this after editing tx.proto to regenerate Go structs
```
-## Running with Docker
+## Transaction Types
-The Go plugin can be run in a Docker container that includes both Canopy and the plugin.
+| Name | Type URL | Signer |
+|---------------------|---------------------------------------------------|-------------------|
+| `send` | `type.googleapis.com/types.MessageSend` | from_address |
+| `create_market` | `type.googleapis.com/types.MessageCreateMarket` | creator_address |
+| `submit_prediction` | `type.googleapis.com/types.MessageSubmitPrediction`| forecaster_address|
+| `resolve_market` | `type.googleapis.com/types.MessageResolveMarket` | resolver_address |
+| `claim_winnings` | `type.googleapis.com/types.MessageClaimWinnings` | claimer_address |
-### Build the Docker Image
+## State Key Prefixes
-From the repository root:
+| Prefix | Type | Key function |
+|--------|---------------|-------------------------|
+| 0x01 | Account | KeyForAccount(addr) |
+| 0x02 | Pool | KeyForFeePool(chainId) |
+| 0x07 | FeeParams | KeyForFeeParams() |
+| 0x10 | Market | KeyForMarket(id) |
+| 0x11 | MarketCounter | KeyForMarketCounter() |
+| 0x12 | Prediction | KeyForPrediction(addr, marketId) |
-```bash
-make docker/plugin PLUGIN=go
-```
+**Important**: All keys are built with `JoinLenPrefix` which length-prefixes
+each segment. This means `KeyForMarket(1)` starts with bytes `[0x01, 0x10, ...]`
+not `[0x10, ...]`. When querying state by prefix, use `0110` (hex), not `10`.
-This builds a Docker image named `canopy-go` that contains:
-- The Canopy binary
-- The Go plugin binary and control script
-- Pre-configured `config.json` with `"plugin": "go"`
+## Critical Rules (from Canopy Plugin Spec)
-### Run the Container
+1. `SupportedTransactions[i]` MUST match `TransactionTypeUrls[i]` exactly.
+ A mismatch causes silent transaction misrouting.
-```bash
-make docker/run-go
-```
-
-Or manually with volume mount for persistent data:
+2. `CheckTx` is stateless. It CANNOT write state. It may read state minimally
+ (the fee params read is acceptable). Do not add state reads for business
+ logic — those belong in `DeliverTx`.
-```bash
-docker run -v ~/.canopy:/root/.canopy canopy-go
-```
+3. Sign bytes = `proto.Marshal(txWithSignatureFieldNil)`. Never sign JSON.
-### Expose Ports for Testing
+4. If `DeliverTx` returns an error, the transaction is still included in the
+ block and the fee is still charged. `CheckTx` must catch everything
+ recoverable.
-To run tests against the containerized Canopy, expose the RPC ports:
-
-```bash
-docker run -p 50002:50002 -p 50003:50003 -v ~/.canopy:/root/.canopy canopy-go
-```
+5. `PluginDeliverRequest` does not carry a block height. Capture it in
+ `BeginBlock` via `c.currentHeight = req.Height`.
-| Port | Service |
-|------|---------|
-| 50002 | RPC API (transactions, queries) |
-| 50003 | Admin RPC (keystore operations) |
+6. Always batch-read all required keys in a single `StateRead` call, then
+ batch-write all changes in a single `StateWrite` call.
-Now you can run tests from your host machine that connect to `localhost:50002`.
+7. When building composite state keys with `append`, always allocate a new
+ slice. `append(existingSlice, ...)` may mutate the original if it has
+ spare capacity.
-### View Logs
-
-```bash
-# Get the container ID
-docker ps
-
-# View Canopy logs
-docker exec -it tail -f /root/.canopy/logs/log
-
-# View plugin logs
-docker exec -it tail -f /tmp/plugin/go-plugin.log
-```
+## Adding a New Transaction Type
-## Running with Canopy
-
-1. Add `"plugin": "go"` to `~/.canopy/config.json`
-2. Start Canopy: `~/go/bin/canopy start`
-3. Plugin auto-starts and connects via Unix socket
-
-## Testing
-
-```bash
-cd plugin/go/tutorial
-go test -v -run TestPluginTransactions -timeout 120s
-```
-
-Requires Canopy running with the plugin enabled and faucet/reward transactions implemented.
-
-## Code Conventions
-
-- **Error handling**: Return `*PluginError` structs, use error functions from `error.go`
-- **Protobuf**: Use `Marshal`/`Unmarshal` helpers from `contract/plugin.go`
-- **Logging**: Use `log.Printf` for debugging (logs to `/tmp/plugin/go-plugin.log`)
-- **QueryIds**: Use `rand.Uint64()` to correlate batch state read requests
-
-## Common Patterns
-
-### CheckTx Template
-```go
-func (c *Contract) CheckMessage(msg *Message) *PluginCheckResponse {
- // Validate addresses (must be 20 bytes)
- if len(msg.Address) != 20 {
- return &PluginCheckResponse{Error: ErrInvalidAddress()}
- }
- // Validate amount
- if msg.Amount == 0 {
- return &PluginCheckResponse{Error: ErrInvalidAmount()}
- }
- // Return authorized signers
- return &PluginCheckResponse{
- Recipient: msg.RecipientAddress,
- AuthorizedSigners: [][]byte{msg.SignerAddress},
- }
-}
-```
-
-### DeliverTx Template
-```go
-func (c *Contract) DeliverMessage(msg *Message, fee uint64) *PluginDeliverResponse {
- // 1. Generate query IDs
- queryId := rand.Uint64()
-
- // 2. Read state
- response, err := c.plugin.StateRead(c, &PluginStateReadRequest{...})
-
- // 3. Unmarshal accounts
- account := new(Account)
- Unmarshal(bytes, account)
-
- // 4. Apply business logic
- account.Amount += msg.Amount
-
- // 5. Marshal and write state
- bytes, _ = Marshal(account)
- c.plugin.StateWrite(c, &PluginStateWriteRequest{...})
-
- return &PluginDeliverResponse{}
-}
-```
+1. Add the message definition to `proto/tx.proto`
+2. Run `proto/_generate.sh` to regenerate Go structs
+3. Add the name to `ContractConfig.SupportedTransactions` at index N
+4. Add the type URL to `ContractConfig.TransactionTypeUrls` at index N (same index)
+5. Add a `case *MessageXxx:` to the `CheckTx` switch and implement `CheckMessageXxx`
+6. Add a `case *MessageXxx:` to the `DeliverTx` switch and implement `DeliverMessageXxx`
+7. Add a `KeyForXxx` function using an unused byte prefix (> 0x12 for Praxis)
+8. If you need a new error, add it to `error.go` starting at code 17+
-## Debugging
+## Prompts That Work Well
-- **Plugin logs**: `tail -f /tmp/plugin/go-plugin.log`
-- **Canopy logs**: `tail -f ~/.canopy/logs/log`
-- **Common errors**:
- - `"message name X is unknown"` → Transaction not registered in `ContractConfig`
- - `"invalid signature"` → Sign bytes mismatch, check protobuf serialization
- - Balance not updating → Check `DeliverTx` is being called, wait for block finalization
+**Adding a transaction type:**
+"Using the Canopy Go plugin pattern in contract.go, add a [tx_name] transaction.
+The proto message fields are: [list fields]. Validate [conditions] in CheckTx.
+In DeliverTx, read [keys], apply [logic], write [keys] back. Use prefix 0x13."
-## Dependencies
+**State schema:**
+"I need state keys for [type] in a Canopy plugin. Existing prefixes in use:
+0x01–0x02, 0x07, 0x10–0x12. Design JoinLenPrefix-based keys that don't collide."
-Key external packages:
-- `google.golang.org/protobuf` - Protobuf serialization
-- `github.com/drand/kyber` + `github.com/drand/kyber-bls12381` - BLS signing
+**Frontend encoding:**
+"Write a JavaScript hand-encoder for [MessageName] with fields [list with types].
+Field numbers must match tx.proto exactly. Use the varintField/bytesField/stringField
+helpers already in the frontend."
From 3e9a01504ca8d4108d76240fb6209444794005da Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 10:27:30 +0100
Subject: [PATCH 005/235] Update chain.json
---
plugin/go/chain.json | 18 ++++--------------
1 file changed, 4 insertions(+), 14 deletions(-)
diff --git a/plugin/go/chain.json b/plugin/go/chain.json
index c702b8cdb3..7069fbf8a5 100644
--- a/plugin/go/chain.json
+++ b/plugin/go/chain.json
@@ -1,16 +1,6 @@
{
+ "name": "Praxis",
+ "symbol": "PRX",
"chainId": 1,
- "name": "canopy",
- "symbol": "CNPY",
- "website": "https://canopynetwork.org",
- "logoURI": "https://",
- "bannerURI": "https://",
- "explorerLogoURI": "https://",
- "walletLogoURI": "https://",
- "color1Hex": "#2c9b5a",
- "color2Hex": "#16502e",
- "description": "Canopy is a recursive, progressive-sovereignty framework for blockchains",
- "consensusPreset": 1,
- "initialMintPerBlock": 80000000,
- "blocksPerHalvening": 3150000
-}
\ No newline at end of file
+ "networkId": 1
+}
From e7276d5e19d3791672f3284d2e789d973747a16f Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 10:35:57 +0100
Subject: [PATCH 006/235] Update tx.proto
---
plugin/go/proto/tx.proto | 109 +++++++++++++++++++++++++++++----------
1 file changed, 83 insertions(+), 26 deletions(-)
diff --git a/plugin/go/proto/tx.proto b/plugin/go/proto/tx.proto
index 8c064be9d6..d55e8fe403 100644
--- a/plugin/go/proto/tx.proto
+++ b/plugin/go/proto/tx.proto
@@ -1,56 +1,113 @@
syntax = "proto3";
package types;
+// IMPORTANT: this go_package path must match the go_package in your go.mod.
+// If your module is github.com/hope93-commits/canopy, change this to:
+// github.com/hope93-commits/canopy/plugin/go/contract
+// If you are working from the official canopy-network fork, keep as-is.
option go_package = "github.com/canopy-network/go-plugin/contract";
import "google/protobuf/any.proto";
-// Transaction represents a request or action submitted to the network like transfer assets or perform other operations
-// within the blockchain system - THIS MUST MATCH lib.Transaction EXACTLY for signing
+
+// Transaction represents a request or action submitted to the network.
+// THIS MUST MATCH lib.Transaction EXACTLY for signing — field numbers,
+// types, and json tags must not be changed.
message Transaction {
- // message_type: The type of the transaction like 'send' or 'stake'
string message_type = 1; // @gotags: json:"messageType"
- // msg: The actual transaction message payload, which is encapsulated in a generic message format
google.protobuf.Any msg = 2;
- // signature: The cryptographic signature used to verify the authenticity of the transaction
Signature signature = 3;
- // created_height: The height when the transaction was created - allows 'safe pruning'
uint64 created_height = 4; // @gotags: json:"createdHeight"
- // time: The timestamp when the transaction was created - used as temporal entropy to prevent hash collisions in txs
uint64 time = 5;
- // fee: The fee associated with processing the transaction
uint64 fee = 6;
- // memo: An optional message or note attached to the transaction
string memo = 7;
- // network_id: The identity of the network the transaction is intended for
uint64 network_id = 8; // @gotags: json:"networkID"
- // chain_id: The identity of the committee the transaction is intended for
- uint64 chain_id = 9; // @gotags: json:"chainID"
- // NOTE: CAN EXTEND FUNCTIONALITY BY ADDING ADDITIONAL FIELDS
- // NO CONFLICT STARTING AT FIELD = 10
+ uint64 chain_id = 9; // @gotags: json:"chainID"
}
-// MessageSend is a standard transfer transaction, taking tokens from the sender and transferring
-// them to the recipient
+// MessageSend is a standard transfer transaction
message MessageSend {
- // from_address: is the sender of the funds
bytes from_address = 1; // @gotags: json:"fromAddress"
- // to_address: is the recipient of the funds
- bytes to_address = 2; // @gotags: json:"toAddress"
- // amount: is the amount of tokens in micro-denomination (uCNPY)
+ bytes to_address = 2; // @gotags: json:"toAddress"
uint64 amount = 3;
}
-// FeeParams is the parameter space that defines various amounts for transaction fees
+// FeeParams defines minimum fees for each transaction type.
+// Add Praxis fee fields starting at field 14 to avoid conflicts.
message FeeParams {
- // send_fee: is the fee amount (in uCNPY) for Message Send
uint64 send_fee = 1; // @gotags: json:"sendFee"
// NO CONFLICT STARTING AT FIELD = 14
}
-// A Signature is a digital signature is a cryptographic "fingerprint" created with a private key,
-// allowing others to verify the authenticity and integrity of a message using the corresponding public key
+// Signature holds the BLS12-381 public key and signature bytes
message Signature {
- // public_key: is a cryptographic code shared openly, used to verify digital signatures
bytes public_key = 1; // @gotags: json:"publicKey"
- // signature: the bytes of the signature output from a private key which may be verified with the message and public
bytes signature = 2;
}
+
+// ── Praxis Prediction Market Transaction Messages ─────────────────────────────
+
+// MessageCreateMarket opens a new YES/NO prediction market.
+// Fields 1–6 — do not reorder; frontend hand-encoder depends on this order.
+message MessageCreateMarket {
+ bytes creator_address = 1; // @gotags: json:"creatorAddress"
+ string question = 2;
+ string description = 3;
+ bytes resolver_address = 4; // @gotags: json:"resolverAddress"
+ uint64 resolution_height = 5; // @gotags: json:"resolutionHeight"
+ uint64 stake_amount = 6; // @gotags: json:"stakeAmount"
+}
+
+// MessageSubmitPrediction places a stake on an outcome.
+// outcome: 1 = YES, 2 = NO
+message MessageSubmitPrediction {
+ bytes forecaster_address = 1; // @gotags: json:"forecasterAddress"
+ uint64 market_id = 2; // @gotags: json:"marketId"
+ uint32 outcome = 3; // 1 = YES, 2 = NO
+ uint64 amount = 4;
+}
+
+// MessageResolveMarket finalises a market with the winning outcome.
+// winning_outcome: 1 = YES, 2 = NO
+message MessageResolveMarket {
+ bytes resolver_address = 1; // @gotags: json:"resolverAddress"
+ uint64 market_id = 2; // @gotags: json:"marketId"
+ uint32 winning_outcome = 3; // @gotags: json:"winningOutcome"
+}
+
+// MessageClaimWinnings pays out a winner's stake and proportional winnings.
+message MessageClaimWinnings {
+ bytes claimer_address = 1; // @gotags: json:"claimerAddress"
+ uint64 market_id = 2; // @gotags: json:"marketId"
+}
+
+// ── Praxis State Objects ──────────────────────────────────────────────────────
+
+// Market holds all on-chain state for one prediction market.
+// status: 0 = open, 1 = resolved, 2 = cancelled
+message Market {
+ uint64 id = 1;
+ bytes creator_address = 2; // @gotags: json:"creatorAddress"
+ string question = 3;
+ string description = 4;
+ bytes resolver_address = 5; // @gotags: json:"resolverAddress"
+ uint64 resolution_height = 6; // @gotags: json:"resolutionHeight"
+ uint64 stake_amount = 7; // @gotags: json:"stakeAmount"
+ uint64 yes_pool = 8; // @gotags: json:"yesPool"
+ uint64 no_pool = 9; // @gotags: json:"noPool"
+ uint32 status = 10; // 0=open 1=resolved 2=cancelled
+ uint32 winning_outcome = 11; // @gotags: json:"winningOutcome"
+}
+
+// Prediction holds one forecaster's stake on a specific market.
+message Prediction {
+ bytes forecaster_address = 1; // @gotags: json:"forecasterAddress"
+ uint64 market_id = 2; // @gotags: json:"marketId"
+ uint32 outcome = 3; // 1 = YES, 2 = NO
+ uint64 amount = 4;
+ bool claimed = 5;
+}
+
+// MarketCounter is a singleton that tracks the next market ID.
+// Key: KeyForMarketCounter() — only one instance exists in state.
+message MarketCounter {
+ uint64 count = 1;
+}
From f024387437a7bdab270ea848e4d4ffbf67c5118e Mon Sep 17 00:00:00 2001
From: Makaveli912
Date: Sat, 25 Apr 2026 11:01:00 +0000
Subject: [PATCH 007/235] fix: correct module paths and regenerate tx.pb.go for
Praxis
---
plugin/go/contract/tx.pb.go | 673 +++++++++++++++++++++++++++++++---
plugin/go/crypto/signing.go | 2 +-
plugin/go/go.mod | 7 +-
plugin/go/go.sum | 4 +
plugin/go/main.go | 2 +-
plugin/go/proto/account.proto | 2 +-
plugin/go/proto/event.proto | 2 +-
plugin/go/proto/plugin.proto | 2 +-
plugin/go/proto/tx.proto | 2 +-
9 files changed, 627 insertions(+), 69 deletions(-)
diff --git a/plugin/go/contract/tx.pb.go b/plugin/go/contract/tx.pb.go
index 0c637ce802..6d097d8964 100644
--- a/plugin/go/contract/tx.pb.go
+++ b/plugin/go/contract/tx.pb.go
@@ -1,15 +1,15 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
-// protoc-gen-go v1.36.6
-// protoc v5.29.3
+// protoc-gen-go v1.36.11
+// protoc v3.12.4
// source: tx.proto
package contract
import (
+ any1 "github.com/golang/protobuf/ptypes/any"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
- anypb "google.golang.org/protobuf/types/known/anypb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
@@ -22,28 +22,20 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
-// Transaction represents a request or action submitted to the network like transfer assets or perform other operations
-// within the blockchain system - THIS MUST MATCH lib.Transaction EXACTLY for signing
+// Transaction represents a request or action submitted to the network.
+// THIS MUST MATCH lib.Transaction EXACTLY for signing — field numbers,
+// types, and json tags must not be changed.
type Transaction struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // message_type: The type of the transaction like 'send' or 'stake'
- MessageType string `protobuf:"bytes,1,opt,name=message_type,json=messageType,proto3" json:"messageType"` // @gotags: json:"messageType"
- // msg: The actual transaction message payload, which is encapsulated in a generic message format
- Msg *anypb.Any `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`
- // signature: The cryptographic signature used to verify the authenticity of the transaction
- Signature *Signature `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"`
- // created_height: The height when the transaction was created - allows 'safe pruning'
- CreatedHeight uint64 `protobuf:"varint,4,opt,name=created_height,json=createdHeight,proto3" json:"createdHeight"` // @gotags: json:"createdHeight"
- // time: The timestamp when the transaction was created - used as temporal entropy to prevent hash collisions in txs
- Time uint64 `protobuf:"varint,5,opt,name=time,proto3" json:"time,omitempty"`
- // fee: The fee associated with processing the transaction
- Fee uint64 `protobuf:"varint,6,opt,name=fee,proto3" json:"fee,omitempty"`
- // memo: An optional message or note attached to the transaction
- Memo string `protobuf:"bytes,7,opt,name=memo,proto3" json:"memo,omitempty"`
- // network_id: The identity of the network the transaction is intended for
- NetworkId uint64 `protobuf:"varint,8,opt,name=network_id,json=networkId,proto3" json:"networkID"` // @gotags: json:"networkID"
- // chain_id: The identity of the committee the transaction is intended for
- ChainId uint64 `protobuf:"varint,9,opt,name=chain_id,json=chainId,proto3" json:"chainID"` // @gotags: json:"chainID"
+ state protoimpl.MessageState `protogen:"open.v1"`
+ MessageType string `protobuf:"bytes,1,opt,name=message_type,json=messageType,proto3" json:"messageType"` // @gotags: json:"messageType"
+ Msg *any1.Any `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`
+ Signature *Signature `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"`
+ CreatedHeight uint64 `protobuf:"varint,4,opt,name=created_height,json=createdHeight,proto3" json:"createdHeight"` // @gotags: json:"createdHeight"
+ Time uint64 `protobuf:"varint,5,opt,name=time,proto3" json:"time,omitempty"`
+ Fee uint64 `protobuf:"varint,6,opt,name=fee,proto3" json:"fee,omitempty"`
+ Memo string `protobuf:"bytes,7,opt,name=memo,proto3" json:"memo,omitempty"`
+ NetworkId uint64 `protobuf:"varint,8,opt,name=network_id,json=networkId,proto3" json:"networkID"` // @gotags: json:"networkID"
+ ChainId uint64 `protobuf:"varint,9,opt,name=chain_id,json=chainId,proto3" json:"chainID"` // @gotags: json:"chainID"
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -85,7 +77,7 @@ func (x *Transaction) GetMessageType() string {
return ""
}
-func (x *Transaction) GetMsg() *anypb.Any {
+func (x *Transaction) GetMsg() *any1.Any {
if x != nil {
return x.Msg
}
@@ -141,16 +133,12 @@ func (x *Transaction) GetChainId() uint64 {
return 0
}
-// MessageSend is a standard transfer transaction, taking tokens from the sender and transferring
-// them to the recipient
+// MessageSend is a standard transfer transaction
type MessageSend struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // from_address: is the sender of the funds
- FromAddress []byte `protobuf:"bytes,1,opt,name=from_address,json=fromAddress,proto3" json:"fromAddress"` // @gotags: json:"fromAddress"
- // to_address: is the recipient of the funds
- ToAddress []byte `protobuf:"bytes,2,opt,name=to_address,json=toAddress,proto3" json:"toAddress"` // @gotags: json:"toAddress"
- // amount: is the amount of tokens in micro-denomination (uCNPY)
- Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"`
+ state protoimpl.MessageState `protogen:"open.v1"`
+ FromAddress []byte `protobuf:"bytes,1,opt,name=from_address,json=fromAddress,proto3" json:"fromAddress"` // @gotags: json:"fromAddress"
+ ToAddress []byte `protobuf:"bytes,2,opt,name=to_address,json=toAddress,proto3" json:"toAddress"` // @gotags: json:"toAddress"
+ Amount uint64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -206,11 +194,11 @@ func (x *MessageSend) GetAmount() uint64 {
return 0
}
-// FeeParams is the parameter space that defines various amounts for transaction fees
+// FeeParams defines minimum fees for each transaction type.
+// Add Praxis fee fields starting at field 14 to avoid conflicts.
type FeeParams struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // send_fee: is the fee amount (in uCNPY) for Message Send
- SendFee uint64 `protobuf:"varint,1,opt,name=send_fee,json=sendFee,proto3" json:"sendFee"` // @gotags: json:"sendFee"
+ state protoimpl.MessageState `protogen:"open.v1"`
+ SendFee uint64 `protobuf:"varint,1,opt,name=send_fee,json=sendFee,proto3" json:"sendFee"` // @gotags: json:"sendFee"
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -252,14 +240,11 @@ func (x *FeeParams) GetSendFee() uint64 {
return 0
}
-// A Signature is a digital signature is a cryptographic "fingerprint" created with a private key,
-// allowing others to verify the authenticity and integrity of a message using the corresponding public key
+// Signature holds the BLS12-381 public key and signature bytes
type Signature struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // public_key: is a cryptographic code shared openly, used to verify digital signatures
- PublicKey []byte `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"publicKey"` // @gotags: json:"publicKey"
- // signature: the bytes of the signature output from a private key which may be verified with the message and public
- Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
+ state protoimpl.MessageState `protogen:"open.v1"`
+ PublicKey []byte `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"publicKey"` // @gotags: json:"publicKey"
+ Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -308,6 +293,526 @@ func (x *Signature) GetSignature() []byte {
return nil
}
+// MessageCreateMarket opens a new YES/NO prediction market.
+// Fields 1–6 — do not reorder; frontend hand-encoder depends on this order.
+type MessageCreateMarket struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ CreatorAddress []byte `protobuf:"bytes,1,opt,name=creator_address,json=creatorAddress,proto3" json:"creatorAddress"` // @gotags: json:"creatorAddress"
+ Question string `protobuf:"bytes,2,opt,name=question,proto3" json:"question,omitempty"`
+ Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
+ ResolverAddress []byte `protobuf:"bytes,4,opt,name=resolver_address,json=resolverAddress,proto3" json:"resolverAddress"` // @gotags: json:"resolverAddress"
+ ResolutionHeight uint64 `protobuf:"varint,5,opt,name=resolution_height,json=resolutionHeight,proto3" json:"resolutionHeight"` // @gotags: json:"resolutionHeight"
+ StakeAmount uint64 `protobuf:"varint,6,opt,name=stake_amount,json=stakeAmount,proto3" json:"stakeAmount"` // @gotags: json:"stakeAmount"
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MessageCreateMarket) Reset() {
+ *x = MessageCreateMarket{}
+ mi := &file_tx_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MessageCreateMarket) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MessageCreateMarket) ProtoMessage() {}
+
+func (x *MessageCreateMarket) ProtoReflect() protoreflect.Message {
+ mi := &file_tx_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MessageCreateMarket.ProtoReflect.Descriptor instead.
+func (*MessageCreateMarket) Descriptor() ([]byte, []int) {
+ return file_tx_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *MessageCreateMarket) GetCreatorAddress() []byte {
+ if x != nil {
+ return x.CreatorAddress
+ }
+ return nil
+}
+
+func (x *MessageCreateMarket) GetQuestion() string {
+ if x != nil {
+ return x.Question
+ }
+ return ""
+}
+
+func (x *MessageCreateMarket) GetDescription() string {
+ if x != nil {
+ return x.Description
+ }
+ return ""
+}
+
+func (x *MessageCreateMarket) GetResolverAddress() []byte {
+ if x != nil {
+ return x.ResolverAddress
+ }
+ return nil
+}
+
+func (x *MessageCreateMarket) GetResolutionHeight() uint64 {
+ if x != nil {
+ return x.ResolutionHeight
+ }
+ return 0
+}
+
+func (x *MessageCreateMarket) GetStakeAmount() uint64 {
+ if x != nil {
+ return x.StakeAmount
+ }
+ return 0
+}
+
+// MessageSubmitPrediction places a stake on an outcome.
+// outcome: 1 = YES, 2 = NO
+type MessageSubmitPrediction struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ForecasterAddress []byte `protobuf:"bytes,1,opt,name=forecaster_address,json=forecasterAddress,proto3" json:"forecasterAddress"` // @gotags: json:"forecasterAddress"
+ MarketId uint64 `protobuf:"varint,2,opt,name=market_id,json=marketId,proto3" json:"marketId"` // @gotags: json:"marketId"
+ Outcome uint32 `protobuf:"varint,3,opt,name=outcome,proto3" json:"outcome,omitempty"` // 1 = YES, 2 = NO
+ Amount uint64 `protobuf:"varint,4,opt,name=amount,proto3" json:"amount,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MessageSubmitPrediction) Reset() {
+ *x = MessageSubmitPrediction{}
+ mi := &file_tx_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MessageSubmitPrediction) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MessageSubmitPrediction) ProtoMessage() {}
+
+func (x *MessageSubmitPrediction) ProtoReflect() protoreflect.Message {
+ mi := &file_tx_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MessageSubmitPrediction.ProtoReflect.Descriptor instead.
+func (*MessageSubmitPrediction) Descriptor() ([]byte, []int) {
+ return file_tx_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *MessageSubmitPrediction) GetForecasterAddress() []byte {
+ if x != nil {
+ return x.ForecasterAddress
+ }
+ return nil
+}
+
+func (x *MessageSubmitPrediction) GetMarketId() uint64 {
+ if x != nil {
+ return x.MarketId
+ }
+ return 0
+}
+
+func (x *MessageSubmitPrediction) GetOutcome() uint32 {
+ if x != nil {
+ return x.Outcome
+ }
+ return 0
+}
+
+func (x *MessageSubmitPrediction) GetAmount() uint64 {
+ if x != nil {
+ return x.Amount
+ }
+ return 0
+}
+
+// MessageResolveMarket finalises a market with the winning outcome.
+// winning_outcome: 1 = YES, 2 = NO
+type MessageResolveMarket struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ResolverAddress []byte `protobuf:"bytes,1,opt,name=resolver_address,json=resolverAddress,proto3" json:"resolverAddress"` // @gotags: json:"resolverAddress"
+ MarketId uint64 `protobuf:"varint,2,opt,name=market_id,json=marketId,proto3" json:"marketId"` // @gotags: json:"marketId"
+ WinningOutcome uint32 `protobuf:"varint,3,opt,name=winning_outcome,json=winningOutcome,proto3" json:"winningOutcome"` // @gotags: json:"winningOutcome"
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MessageResolveMarket) Reset() {
+ *x = MessageResolveMarket{}
+ mi := &file_tx_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MessageResolveMarket) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MessageResolveMarket) ProtoMessage() {}
+
+func (x *MessageResolveMarket) ProtoReflect() protoreflect.Message {
+ mi := &file_tx_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MessageResolveMarket.ProtoReflect.Descriptor instead.
+func (*MessageResolveMarket) Descriptor() ([]byte, []int) {
+ return file_tx_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *MessageResolveMarket) GetResolverAddress() []byte {
+ if x != nil {
+ return x.ResolverAddress
+ }
+ return nil
+}
+
+func (x *MessageResolveMarket) GetMarketId() uint64 {
+ if x != nil {
+ return x.MarketId
+ }
+ return 0
+}
+
+func (x *MessageResolveMarket) GetWinningOutcome() uint32 {
+ if x != nil {
+ return x.WinningOutcome
+ }
+ return 0
+}
+
+// MessageClaimWinnings pays out a winner's stake and proportional winnings.
+type MessageClaimWinnings struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ClaimerAddress []byte `protobuf:"bytes,1,opt,name=claimer_address,json=claimerAddress,proto3" json:"claimerAddress"` // @gotags: json:"claimerAddress"
+ MarketId uint64 `protobuf:"varint,2,opt,name=market_id,json=marketId,proto3" json:"marketId"` // @gotags: json:"marketId"
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MessageClaimWinnings) Reset() {
+ *x = MessageClaimWinnings{}
+ mi := &file_tx_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MessageClaimWinnings) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MessageClaimWinnings) ProtoMessage() {}
+
+func (x *MessageClaimWinnings) ProtoReflect() protoreflect.Message {
+ mi := &file_tx_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MessageClaimWinnings.ProtoReflect.Descriptor instead.
+func (*MessageClaimWinnings) Descriptor() ([]byte, []int) {
+ return file_tx_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *MessageClaimWinnings) GetClaimerAddress() []byte {
+ if x != nil {
+ return x.ClaimerAddress
+ }
+ return nil
+}
+
+func (x *MessageClaimWinnings) GetMarketId() uint64 {
+ if x != nil {
+ return x.MarketId
+ }
+ return 0
+}
+
+// Market holds all on-chain state for one prediction market.
+// status: 0 = open, 1 = resolved, 2 = cancelled
+type Market struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+ CreatorAddress []byte `protobuf:"bytes,2,opt,name=creator_address,json=creatorAddress,proto3" json:"creatorAddress"` // @gotags: json:"creatorAddress"
+ Question string `protobuf:"bytes,3,opt,name=question,proto3" json:"question,omitempty"`
+ Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
+ ResolverAddress []byte `protobuf:"bytes,5,opt,name=resolver_address,json=resolverAddress,proto3" json:"resolverAddress"` // @gotags: json:"resolverAddress"
+ ResolutionHeight uint64 `protobuf:"varint,6,opt,name=resolution_height,json=resolutionHeight,proto3" json:"resolutionHeight"` // @gotags: json:"resolutionHeight"
+ StakeAmount uint64 `protobuf:"varint,7,opt,name=stake_amount,json=stakeAmount,proto3" json:"stakeAmount"` // @gotags: json:"stakeAmount"
+ YesPool uint64 `protobuf:"varint,8,opt,name=yes_pool,json=yesPool,proto3" json:"yesPool"` // @gotags: json:"yesPool"
+ NoPool uint64 `protobuf:"varint,9,opt,name=no_pool,json=noPool,proto3" json:"noPool"` // @gotags: json:"noPool"
+ Status uint32 `protobuf:"varint,10,opt,name=status,proto3" json:"status,omitempty"` // 0=open 1=resolved 2=cancelled
+ WinningOutcome uint32 `protobuf:"varint,11,opt,name=winning_outcome,json=winningOutcome,proto3" json:"winningOutcome"` // @gotags: json:"winningOutcome"
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Market) Reset() {
+ *x = Market{}
+ mi := &file_tx_proto_msgTypes[8]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Market) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Market) ProtoMessage() {}
+
+func (x *Market) ProtoReflect() protoreflect.Message {
+ mi := &file_tx_proto_msgTypes[8]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Market.ProtoReflect.Descriptor instead.
+func (*Market) Descriptor() ([]byte, []int) {
+ return file_tx_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *Market) GetId() uint64 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+func (x *Market) GetCreatorAddress() []byte {
+ if x != nil {
+ return x.CreatorAddress
+ }
+ return nil
+}
+
+func (x *Market) GetQuestion() string {
+ if x != nil {
+ return x.Question
+ }
+ return ""
+}
+
+func (x *Market) GetDescription() string {
+ if x != nil {
+ return x.Description
+ }
+ return ""
+}
+
+func (x *Market) GetResolverAddress() []byte {
+ if x != nil {
+ return x.ResolverAddress
+ }
+ return nil
+}
+
+func (x *Market) GetResolutionHeight() uint64 {
+ if x != nil {
+ return x.ResolutionHeight
+ }
+ return 0
+}
+
+func (x *Market) GetStakeAmount() uint64 {
+ if x != nil {
+ return x.StakeAmount
+ }
+ return 0
+}
+
+func (x *Market) GetYesPool() uint64 {
+ if x != nil {
+ return x.YesPool
+ }
+ return 0
+}
+
+func (x *Market) GetNoPool() uint64 {
+ if x != nil {
+ return x.NoPool
+ }
+ return 0
+}
+
+func (x *Market) GetStatus() uint32 {
+ if x != nil {
+ return x.Status
+ }
+ return 0
+}
+
+func (x *Market) GetWinningOutcome() uint32 {
+ if x != nil {
+ return x.WinningOutcome
+ }
+ return 0
+}
+
+// Prediction holds one forecaster's stake on a specific market.
+type Prediction struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ForecasterAddress []byte `protobuf:"bytes,1,opt,name=forecaster_address,json=forecasterAddress,proto3" json:"forecasterAddress"` // @gotags: json:"forecasterAddress"
+ MarketId uint64 `protobuf:"varint,2,opt,name=market_id,json=marketId,proto3" json:"marketId"` // @gotags: json:"marketId"
+ Outcome uint32 `protobuf:"varint,3,opt,name=outcome,proto3" json:"outcome,omitempty"` // 1 = YES, 2 = NO
+ Amount uint64 `protobuf:"varint,4,opt,name=amount,proto3" json:"amount,omitempty"`
+ Claimed bool `protobuf:"varint,5,opt,name=claimed,proto3" json:"claimed,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Prediction) Reset() {
+ *x = Prediction{}
+ mi := &file_tx_proto_msgTypes[9]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Prediction) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Prediction) ProtoMessage() {}
+
+func (x *Prediction) ProtoReflect() protoreflect.Message {
+ mi := &file_tx_proto_msgTypes[9]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Prediction.ProtoReflect.Descriptor instead.
+func (*Prediction) Descriptor() ([]byte, []int) {
+ return file_tx_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *Prediction) GetForecasterAddress() []byte {
+ if x != nil {
+ return x.ForecasterAddress
+ }
+ return nil
+}
+
+func (x *Prediction) GetMarketId() uint64 {
+ if x != nil {
+ return x.MarketId
+ }
+ return 0
+}
+
+func (x *Prediction) GetOutcome() uint32 {
+ if x != nil {
+ return x.Outcome
+ }
+ return 0
+}
+
+func (x *Prediction) GetAmount() uint64 {
+ if x != nil {
+ return x.Amount
+ }
+ return 0
+}
+
+func (x *Prediction) GetClaimed() bool {
+ if x != nil {
+ return x.Claimed
+ }
+ return false
+}
+
+// MarketCounter is a singleton that tracks the next market ID.
+// Key: KeyForMarketCounter() — only one instance exists in state.
+type MarketCounter struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Count uint64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MarketCounter) Reset() {
+ *x = MarketCounter{}
+ mi := &file_tx_proto_msgTypes[10]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MarketCounter) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MarketCounter) ProtoMessage() {}
+
+func (x *MarketCounter) ProtoReflect() protoreflect.Message {
+ mi := &file_tx_proto_msgTypes[10]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MarketCounter.ProtoReflect.Descriptor instead.
+func (*MarketCounter) Descriptor() ([]byte, []int) {
+ return file_tx_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *MarketCounter) GetCount() uint64 {
+ if x != nil {
+ return x.Count
+ }
+ return 0
+}
+
var File_tx_proto protoreflect.FileDescriptor
const file_tx_proto_rawDesc = "" +
@@ -334,7 +839,48 @@ const file_tx_proto_rawDesc = "" +
"\tSignature\x12\x1d\n" +
"\n" +
"public_key\x18\x01 \x01(\fR\tpublicKey\x12\x1c\n" +
- "\tsignature\x18\x02 \x01(\fR\tsignatureB.Z,github.com/canopy-network/go-plugin/contractb\x06proto3"
+ "\tsignature\x18\x02 \x01(\fR\tsignature\"\xf7\x01\n" +
+ "\x13MessageCreateMarket\x12'\n" +
+ "\x0fcreator_address\x18\x01 \x01(\fR\x0ecreatorAddress\x12\x1a\n" +
+ "\bquestion\x18\x02 \x01(\tR\bquestion\x12 \n" +
+ "\vdescription\x18\x03 \x01(\tR\vdescription\x12)\n" +
+ "\x10resolver_address\x18\x04 \x01(\fR\x0fresolverAddress\x12+\n" +
+ "\x11resolution_height\x18\x05 \x01(\x04R\x10resolutionHeight\x12!\n" +
+ "\fstake_amount\x18\x06 \x01(\x04R\vstakeAmount\"\x97\x01\n" +
+ "\x17MessageSubmitPrediction\x12-\n" +
+ "\x12forecaster_address\x18\x01 \x01(\fR\x11forecasterAddress\x12\x1b\n" +
+ "\tmarket_id\x18\x02 \x01(\x04R\bmarketId\x12\x18\n" +
+ "\aoutcome\x18\x03 \x01(\rR\aoutcome\x12\x16\n" +
+ "\x06amount\x18\x04 \x01(\x04R\x06amount\"\x87\x01\n" +
+ "\x14MessageResolveMarket\x12)\n" +
+ "\x10resolver_address\x18\x01 \x01(\fR\x0fresolverAddress\x12\x1b\n" +
+ "\tmarket_id\x18\x02 \x01(\x04R\bmarketId\x12'\n" +
+ "\x0fwinning_outcome\x18\x03 \x01(\rR\x0ewinningOutcome\"\\\n" +
+ "\x14MessageClaimWinnings\x12'\n" +
+ "\x0fclaimer_address\x18\x01 \x01(\fR\x0eclaimerAddress\x12\x1b\n" +
+ "\tmarket_id\x18\x02 \x01(\x04R\bmarketId\"\xef\x02\n" +
+ "\x06Market\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\x04R\x02id\x12'\n" +
+ "\x0fcreator_address\x18\x02 \x01(\fR\x0ecreatorAddress\x12\x1a\n" +
+ "\bquestion\x18\x03 \x01(\tR\bquestion\x12 \n" +
+ "\vdescription\x18\x04 \x01(\tR\vdescription\x12)\n" +
+ "\x10resolver_address\x18\x05 \x01(\fR\x0fresolverAddress\x12+\n" +
+ "\x11resolution_height\x18\x06 \x01(\x04R\x10resolutionHeight\x12!\n" +
+ "\fstake_amount\x18\a \x01(\x04R\vstakeAmount\x12\x19\n" +
+ "\byes_pool\x18\b \x01(\x04R\ayesPool\x12\x17\n" +
+ "\ano_pool\x18\t \x01(\x04R\x06noPool\x12\x16\n" +
+ "\x06status\x18\n" +
+ " \x01(\rR\x06status\x12'\n" +
+ "\x0fwinning_outcome\x18\v \x01(\rR\x0ewinningOutcome\"\xa4\x01\n" +
+ "\n" +
+ "Prediction\x12-\n" +
+ "\x12forecaster_address\x18\x01 \x01(\fR\x11forecasterAddress\x12\x1b\n" +
+ "\tmarket_id\x18\x02 \x01(\x04R\bmarketId\x12\x18\n" +
+ "\aoutcome\x18\x03 \x01(\rR\aoutcome\x12\x16\n" +
+ "\x06amount\x18\x04 \x01(\x04R\x06amount\x12\x18\n" +
+ "\aclaimed\x18\x05 \x01(\bR\aclaimed\"%\n" +
+ "\rMarketCounter\x12\x14\n" +
+ "\x05count\x18\x01 \x01(\x04R\x05countB5Z3github.com/canopy-network/canopy/plugin/go/contractb\x06proto3"
var (
file_tx_proto_rawDescOnce sync.Once
@@ -348,22 +894,29 @@ func file_tx_proto_rawDescGZIP() []byte {
return file_tx_proto_rawDescData
}
-var file_tx_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_tx_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_tx_proto_goTypes = []any{
- (*Transaction)(nil), // 0: types.Transaction
- (*MessageSend)(nil), // 1: types.MessageSend
- (*FeeParams)(nil), // 2: types.FeeParams
- (*Signature)(nil), // 3: types.Signature
- (*anypb.Any)(nil), // 4: google.protobuf.Any
+ (*Transaction)(nil), // 0: types.Transaction
+ (*MessageSend)(nil), // 1: types.MessageSend
+ (*FeeParams)(nil), // 2: types.FeeParams
+ (*Signature)(nil), // 3: types.Signature
+ (*MessageCreateMarket)(nil), // 4: types.MessageCreateMarket
+ (*MessageSubmitPrediction)(nil), // 5: types.MessageSubmitPrediction
+ (*MessageResolveMarket)(nil), // 6: types.MessageResolveMarket
+ (*MessageClaimWinnings)(nil), // 7: types.MessageClaimWinnings
+ (*Market)(nil), // 8: types.Market
+ (*Prediction)(nil), // 9: types.Prediction
+ (*MarketCounter)(nil), // 10: types.MarketCounter
+ (*any1.Any)(nil), // 11: google.protobuf.Any
}
var file_tx_proto_depIdxs = []int32{
- 4, // 0: types.Transaction.msg:type_name -> google.protobuf.Any
- 3, // 1: types.Transaction.signature:type_name -> types.Signature
- 2, // [2:2] is the sub-list for method output_type
- 2, // [2:2] is the sub-list for method input_type
- 2, // [2:2] is the sub-list for extension type_name
- 2, // [2:2] is the sub-list for extension extendee
- 0, // [0:2] is the sub-list for field type_name
+ 11, // 0: types.Transaction.msg:type_name -> google.protobuf.Any
+ 3, // 1: types.Transaction.signature:type_name -> types.Signature
+ 2, // [2:2] is the sub-list for method output_type
+ 2, // [2:2] is the sub-list for method input_type
+ 2, // [2:2] is the sub-list for extension type_name
+ 2, // [2:2] is the sub-list for extension extendee
+ 0, // [0:2] is the sub-list for field type_name
}
func init() { file_tx_proto_init() }
@@ -377,7 +930,7 @@ func file_tx_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_tx_proto_rawDesc), len(file_tx_proto_rawDesc)),
NumEnums: 0,
- NumMessages: 4,
+ NumMessages: 11,
NumExtensions: 0,
NumServices: 0,
},
diff --git a/plugin/go/crypto/signing.go b/plugin/go/crypto/signing.go
index 9d46d0bc5f..af31da6eb8 100644
--- a/plugin/go/crypto/signing.go
+++ b/plugin/go/crypto/signing.go
@@ -1,7 +1,7 @@
package crypto
import (
- "github.com/canopy-network/go-plugin/contract"
+ "github.com/canopy-network/canopy/plugin/go/contract"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
)
diff --git a/plugin/go/go.mod b/plugin/go/go.mod
index 06bd10edf1..cf53f456d4 100644
--- a/plugin/go/go.mod
+++ b/plugin/go/go.mod
@@ -1,14 +1,15 @@
-module github.com/canopy-network/go-plugin
+module github.com/canopy-network/canopy/plugin/go
-go 1.25
+go 1.24
require (
- github.com/drand/kyber v1.3.2
+ github.com/drand/kyber v1.3.1
github.com/drand/kyber-bls12381 v0.3.4
google.golang.org/protobuf v1.36.6
)
require (
+ github.com/golang/protobuf v1.5.4 // indirect
github.com/kilic/bls12-381 v0.1.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/sys v0.39.0 // indirect
diff --git a/plugin/go/go.sum b/plugin/go/go.sum
index 624ad4f981..74edb111e0 100644
--- a/plugin/go/go.sum
+++ b/plugin/go/go.sum
@@ -1,9 +1,13 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/drand/kyber v1.3.1 h1:E0p6M3II+loMVwTlAp5zu4+GGZFNiRfq02qZxzw2T+Y=
+github.com/drand/kyber v1.3.1/go.mod h1:f+mNHjiGT++CuueBrpeMhFNdKZAsy0tu03bKq9D5LPA=
github.com/drand/kyber v1.3.2 h1:Cf3NNcb5bV3eODopr3XVHzImjDK40GiObhFUFG93Zeo=
github.com/drand/kyber v1.3.2/go.mod h1:ciDFWoC7ajb89niGJnS4C1Xeo4lSJMmbi+km5w8juAI=
github.com/drand/kyber-bls12381 v0.3.4 h1:rrmYcRcXmtOAvKWVBxRQxi22qNMVcS2Jz7MAebZQJxI=
github.com/drand/kyber-bls12381 v0.3.4/go.mod h1:jh3IGIAQfdLrdNKYz1HWZ3YdfJM0DWlN1TxXkh60utk=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4=
diff --git a/plugin/go/main.go b/plugin/go/main.go
index 314e054485..898e0e0181 100644
--- a/plugin/go/main.go
+++ b/plugin/go/main.go
@@ -2,7 +2,7 @@ package main
import (
"context"
- "github.com/canopy-network/go-plugin/contract"
+ "github.com/canopy-network/canopy/plugin/go/contract"
"os"
"os/signal"
"syscall"
diff --git a/plugin/go/proto/account.proto b/plugin/go/proto/account.proto
index b0ad929c70..f97e7fd8d6 100644
--- a/plugin/go/proto/account.proto
+++ b/plugin/go/proto/account.proto
@@ -1,6 +1,6 @@
syntax = "proto3";
package types;
-option go_package = "github.com/canopy-network/go-plugin/contract";
+option go_package = "github.com/canopy-network/canopy/plugin/go/contract";
// An account is a structure that holds funds and can send or receive transactions using a crypto key pair
// Each account has a unique address and a balance, think a bank account - but managed by the blockchain
diff --git a/plugin/go/proto/event.proto b/plugin/go/proto/event.proto
index b2c9772db8..9080b56fed 100644
--- a/plugin/go/proto/event.proto
+++ b/plugin/go/proto/event.proto
@@ -1,7 +1,7 @@
syntax = "proto3";
package types;
-option go_package = "github.com/canopy-network/go-plugin/contract";
+option go_package = "github.com/canopy-network/canopy/plugin/go/contract";
import "google/protobuf/any.proto";
diff --git a/plugin/go/proto/plugin.proto b/plugin/go/proto/plugin.proto
index 513e34f154..356e7d3bd3 100644
--- a/plugin/go/proto/plugin.proto
+++ b/plugin/go/proto/plugin.proto
@@ -1,6 +1,6 @@
syntax = "proto3";
package types;
-option go_package = "github.com/canopy-network/go-plugin/contract";
+option go_package = "github.com/canopy-network/canopy/plugin/go/contract";
import "event.proto";
import "tx.proto";
diff --git a/plugin/go/proto/tx.proto b/plugin/go/proto/tx.proto
index d55e8fe403..019eeaff92 100644
--- a/plugin/go/proto/tx.proto
+++ b/plugin/go/proto/tx.proto
@@ -4,7 +4,7 @@ package types;
// If your module is github.com/hope93-commits/canopy, change this to:
// github.com/hope93-commits/canopy/plugin/go/contract
// If you are working from the official canopy-network fork, keep as-is.
-option go_package = "github.com/canopy-network/go-plugin/contract";
+option go_package = "github.com/canopy-network/canopy/plugin/go/contract";
import "google/protobuf/any.proto";
From 0bfc446aa9efd450af82195c58d3df474d000324 Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 12:25:24 +0100
Subject: [PATCH 008/235] praxis-prediction-markets
---
frontend/{folder => index.html} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename frontend/{folder => index.html} (100%)
diff --git a/frontend/folder b/frontend/index.html
similarity index 100%
rename from frontend/folder
rename to frontend/index.html
From 0b54ef13bf6a04f51ba168c3316c58771fc5ae2a Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 12:55:04 +0100
Subject: [PATCH 009/235] Update index.html
---
frontend/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/index.html b/frontend/index.html
index 84d9b4ddb5..ff170541e9 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -924,7 +924,7 @@
try {
signerPrivKey = hexToBytes(hex);
// derive public key (G1 point, 48 bytes compressed)
- signerPubKey = bls12_381.getPublicKey(signerPrivKey);
+ signerPubKey = bls12_381.G1.ProjectivePoint.fromPrivateKey(signerPrivKey).toRawBytes(true);
const pubHex = bytesToHex(signerPubKey);
// derive 20-byte address: last 20 bytes of SHA-256(pubkey)
const hashBuf = await crypto.subtle.digest('SHA-256', signerPubKey);
From 1dca8d0b02a6bf6656a6a13a162a562850fbcab1 Mon Sep 17 00:00:00 2001
From: Makaveli912 <119263017+Makaveli912@users.noreply.github.com>
Date: Sat, 25 Apr 2026 13:03:31 +0100
Subject: [PATCH 010/235] Update index.html
---
frontend/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/index.html b/frontend/index.html
index ff170541e9..0d0d453dda 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -572,7 +572,7 @@
BLS12-381 via @noble/curves (ESM CDN — no build step required)
════════════════════════════════════════════════════════════════════ -->
-