Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8694303
CANTON: Wip
PawelBis Mar 12, 2026
ed96efc
CANTON: Prepare CreateAccount method
PawelBis Mar 18, 2026
85f6f1f
CANTON: Refactor create account transacction
PawelBis Mar 19, 2026
56f9a4e
CANTON: Improve logging
PawelBis Mar 20, 2026
d6f0df1
CANTON: Add GetAccountState method
PawelBis Mar 23, 2026
5c43da5
Add AddressBuilder.AddressRegistrationRequired() method
PawelBis Mar 24, 2026
397efa2
CANTON: Refactor inputs
PawelBis Mar 25, 2026
e548a61
CANTON: Fix tx-info lookup
PawelBis Mar 25, 2026
7dc763d
CANTON: Fix tx-info fee report
PawelBis Mar 25, 2026
4a118bc
CANTON: Refactor create account command
PawelBis Mar 25, 2026
d6d6f03
CANTON: Fetch synchronizer id
PawelBis Mar 26, 2026
8bce023
CANTON: Remove hardcoded env vars and implement CustomConfig
PawelBis Mar 26, 2026
ed36c9f
CANTON: Remove hardcoded username, read deduplication window from config
PawelBis Mar 26, 2026
fbfa736
CANTON: Refactor scan api interactions
PawelBis Mar 26, 2026
271876a
CANTON: Test fixes and magic value fix
PawelBis Mar 26, 2026
1caa2fe
CANTON: Fix scan api regression
PawelBis Mar 26, 2026
41a5b3a
CANTON: Remove CustomConfig.validator_party_id
PawelBis Mar 31, 2026
46834f1
CANTON: Add CustomConfig comments
PawelBis Apr 2, 2026
f464fbb
canton: drop using secrets for urls
conorpp Apr 2, 2026
67ac7d2
canton: generate protobuf types locally
conorpp Apr 2, 2026
e5aefb9
canton: some cleanup
conorpp Apr 2, 2026
09f42ce
Canton: Add upload token standard dars script
PawelBis Apr 13, 2026
2b3b82a
CANTON: Token setup scripts + balance implementation
PawelBis Apr 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions address.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type ContractAddress Address
// AddressBuilder is the interface for building addresses
type AddressBuilder interface {
GetAddressFromPublicKey(publicKeyBytes []byte) (Address, error)
AddressRegistrationRequired(address Address) bool
}

type AddressBuilderWithFormats interface {
Expand Down
16 changes: 14 additions & 2 deletions asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
BCH = NativeAsset("BCH") // Bitcoin Cash
BNB = NativeAsset("BNB") // Binance Coin
BTC = NativeAsset("BTC") // Bitcoin
CANTON = NativeAsset("CANTON") // Canton
CELO = NativeAsset("CELO") // Celo
CHZ = NativeAsset("CHZ") // Chiliz
CHZ2 = NativeAsset("CHZ2") // Chiliz 2.0
Expand Down Expand Up @@ -114,6 +115,7 @@ var NativeAssetList []NativeAsset = []NativeAsset{
BABY,
BCH,
BTC,
CANTON,
DASH,
DOGE,
LTC,
Expand Down Expand Up @@ -196,6 +198,7 @@ const (
DriverBitcoin = Driver("bitcoin")
DriverBitcoinCash = Driver("bitcoin-cash")
DriverBitcoinLegacy = Driver("bitcoin-legacy")
DriverCanton = Driver("canton")
DriverCardano = Driver("cardano")
DriverCosmos = Driver("cosmos")
DriverCosmosEvmos = Driver("evmos")
Expand Down Expand Up @@ -228,6 +231,7 @@ var SupportedDrivers = []Driver{
DriverBitcoin,
DriverBitcoinCash,
DriverBitcoinLegacy,
DriverCanton,
DriverCosmos,
DriverCosmosEvmos,
DriverEGLD,
Expand Down Expand Up @@ -286,6 +290,10 @@ func NewWithdrawingInputType(driver Driver, variant string) TxVariantInputType {
return TxVariantInputType(fmt.Sprintf("drivers/%s/withdrawing/%s", driver, variant))
}

func NewCreateAccountInputType(driver Driver, variant string) TxVariantInputType {
return TxVariantInputType(fmt.Sprintf("drivers/%s/create-account/%s", driver, variant))
}

func NewCallingInputType(driver Driver) TxVariantInputType {
return TxVariantInputType(fmt.Sprintf("drivers/%s/calling/%s", driver, driver))
}
Expand Down Expand Up @@ -314,6 +322,8 @@ func (native NativeAsset) Driver() Driver {
return DriverBitcoin
case BCH:
return DriverBitcoinCash
case CANTON:
return DriverCanton
case DOGE, LTC, DASH:
return DriverBitcoinLegacy
case ZEC, FLUX:
Expand Down Expand Up @@ -376,7 +386,7 @@ func (driver Driver) SignatureAlgorithms() []SignatureType {
return []SignatureType{K256Sha256}
case DriverEVM, DriverEVMLegacy, DriverCosmosEvmos, DriverTron, DriverHyperliquid, DriverHedera, DriverTempo:
return []SignatureType{K256Keccak}
case DriverAptos, DriverSolana, DriverSui, DriverTon, DriverSubstrate, DriverXlm, DriverCardano, DriverInternetComputerProtocol, DriverNear, DriverEGLD:
case DriverAptos, DriverSolana, DriverSui, DriverTon, DriverSubstrate, DriverXlm, DriverCardano, DriverInternetComputerProtocol, DriverNear, DriverEGLD, DriverCanton:
return []SignatureType{Ed255}
case DriverDusk:
return []SignatureType{Bls12_381G2Blake2}
Expand All @@ -401,7 +411,7 @@ func (driver Driver) PublicKeyFormat() PublicKeyFormat {
case DriverEVM, DriverEVMLegacy, DriverTron, DriverFilecoin, DriverHyperliquid, DriverHedera, DriverTempo:
return Uncompressed
case DriverAptos, DriverSolana, DriverSui, DriverTon, DriverSubstrate, DriverDusk,
DriverKaspa, DriverInternetComputerProtocol, DriverNear, DriverEGLD:
DriverKaspa, DriverInternetComputerProtocol, DriverNear, DriverEGLD, DriverCanton:
return Raw
}
return ""
Expand Down Expand Up @@ -517,6 +527,7 @@ func NewChainConfig(nativeAsset NativeAsset, driverMaybe ...Driver) *ChainConfig
Driver: driver,
},
ChainClientConfig: &ChainClientConfig{},
CustomConfig: map[string]any{},
}
cfg.Configure(0)
return cfg
Expand Down Expand Up @@ -619,6 +630,7 @@ func (chain *ChainConfig) DefaultHttpClient() *http.Client {
type ChainConfig struct {
*ChainBaseConfig `yaml:",inline"`
*ChainClientConfig `yaml:",inline"`
CustomConfig map[string]any `yaml:"custom_config,omitempty"`
}

type MemoSupport string
Expand Down
26 changes: 26 additions & 0 deletions asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/cordialsys/crosschain/address"
xcbuilder "github.com/cordialsys/crosschain/builder"
"github.com/cordialsys/crosschain/call"
"github.com/cordialsys/crosschain/chain/canton"
"github.com/cordialsys/crosschain/chain/cosmos"
"github.com/cordialsys/crosschain/chain/egld"
"github.com/cordialsys/crosschain/chain/eos"
Expand Down Expand Up @@ -70,6 +71,8 @@ func TestChains(t *testing.T) {
hedera.Validate(t, chain)
case DriverNear:
near.Validate(t, chain)
case DriverCanton:
canton.Validate(t, chain)
case "":
require.Fail(t, "unknown driver", chain.Driver)
default:
Expand Down Expand Up @@ -308,6 +311,29 @@ func TestSupportedAddressFormats(t *testing.T) {
}
}

func TestCustomConfigValidation(t *testing.T) {
xcf1 := factory.NewDefaultFactory()
xcf2 := factory.NewNotMainnetsFactory(&factory.FactoryOptions{})
for _, xcf := range []*factory.Factory{xcf1, xcf2} {
for _, chain := range xcf.GetAllChains() {
t.Run(fmt.Sprintf("%s_%s", chain.Chain, xcf.Config.Network), func(t *testing.T) {
if len(chain.CustomConfig) == 0 {
return
}

switch chain.Driver {
case DriverCanton:
require.NoError(t, canton.ValidateCustomConfig(chain))
case "":
require.Fail(t, "unknown driver", chain.Driver)
default:
require.Fail(t, fmt.Sprintf("missing ValidateCustomConfig() for %s driver", chain.Driver))
}
})
}
}
}

func TestExternalIdsAreSet(t *testing.T) {
xcf1 := factory.NewDefaultFactory()
for _, xcf := range []*factory.Factory{xcf1} {
Expand Down
4 changes: 4 additions & 0 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ type Staking interface {
// Informational only
MethodsUsed() []xc.StakingMethod
}

type AccountCreation interface {
CreateAccount(createAccountArgs CreateAccountArgs, input xc.CreateAccountTxInput) (xc.Tx, error)
}
44 changes: 44 additions & 0 deletions builder/create_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package builder

import xc "github.com/cordialsys/crosschain"

type CreateAccountArgs struct {
appliedOptions []BuilderOption
options builderOptions
chain xc.NativeAsset
address xc.Address
publicKey []byte
}

var _ TransactionOptions = &CreateAccountArgs{}

func NewCreateAccountArgs(chain xc.NativeAsset, address xc.Address, publicKey []byte, options ...BuilderOption) (CreateAccountArgs, error) {
builderOptions := newBuilderOptions()
args := CreateAccountArgs{
appliedOptions: options,
options: builderOptions,
chain: chain,
address: address,
publicKey: append([]byte(nil), publicKey...),
}
for _, opt := range options {
if err := opt(&args.options); err != nil {
return args, err
}
}
return args, nil
}

func (args *CreateAccountArgs) GetChain() xc.NativeAsset { return args.chain }
func (args *CreateAccountArgs) GetAddress() xc.Address { return args.address }
func (args *CreateAccountArgs) PublicKeyBytes() []byte { return append([]byte(nil), args.publicKey...) }
func (args *CreateAccountArgs) GetMemo() (string, bool) { return args.options.GetMemo() }
func (args *CreateAccountArgs) GetTimestamp() (int64, bool) {
return args.options.GetTimestamp()
}
func (args *CreateAccountArgs) GetPriority() (xc.GasFeePriority, bool) {
return args.options.GetPriority()
}
func (args *CreateAccountArgs) GetPublicKey() ([]byte, bool) {
return append([]byte(nil), args.publicKey...), len(args.publicKey) > 0
}
4 changes: 4 additions & 0 deletions chain/aptos/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ func (ab AddressBuilder) GetAddressFromPublicKey(publicKeyBytes []byte) (xc.Addr
address := "0x" + hex.EncodeToString(authKey[:])
return xc.Address(address), nil
}

func (ab AddressBuilder) AddressRegistrationRequired(address xc.Address) bool {
return false
}
4 changes: 4 additions & 0 deletions chain/bitcoin/address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,7 @@ func (ab AddressBuilder) GetAddressFromPublicKey(publicKeyBytes []byte) (xc.Addr
return "", errors.New("failed to determine bitcoin address type")
}
}

func (ab AddressBuilder) AddressRegistrationRequired(address xc.Address) bool {
return false
}
4 changes: 4 additions & 0 deletions chain/bitcoin_cash/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ func (ab AddressBuilder) GetAddressFromPublicKey(publicKeyBytes []byte) (xc.Addr
return xc.Address(prefix + ":" + encoded), nil
}

func (ab AddressBuilder) AddressRegistrationRequired(address xc.Address) bool {
return false
}

func (ab AddressBuilder) GetLegacyAddressFromPublicKey(publicKeyBytes []byte) (xc.Address, error) {
addressPubKey, err := NewBchAddressPubKey(publicKeyBytes, ab.params)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions chain/canton/Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Regenerate protobuf types
pb:
rm -rf types/com/daml/ledger/api/v2 types/google/rpc
cd proto && buf mod update && buf generate
# Use google.golang.org/genproto/googleapis/rpc/status rather than any local types.
for file in `rg -l 'github.com/cordialsys/crosschain/chain/canton/types/google/rpc' types/com/daml/ledger/api/v2 -g '*.go'`; do perl -0pi -e 's#github.com/cordialsys/crosschain/chain/canton/types/google/rpc#google.golang.org/genproto/googleapis/rpc/status#g' "$file"; done
105 changes: 105 additions & 0 deletions chain/canton/address/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package address

import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
"strings"

xc "github.com/cordialsys/crosschain"
)

// AddressBuilder for Canton
type AddressBuilder struct{}

var _ xc.AddressBuilder = AddressBuilder{}

func NewAddressBuilder(cfgI *xc.ChainBaseConfig) (xc.AddressBuilder, error) {
return AddressBuilder{}, nil
}

// GetAddressFromPublicKey returns a Canton party ID from a raw Ed25519 public key (32 bytes).
//
// Canton party IDs have the form: <name>::<fingerprint>
//
// Where:
// - <name> = hex-encoded public key (64 hex chars)
// - <fingerprint> = "1220" + hex(SHA-256(purposeBytes || rawPubKey))
//
// The purpose prefix is big-endian uint32(12), matching Canton's internal
// HashPurpose.PublicKeyFingerprint (id=12) in Hash.digest().
//
// The "1220" multihash prefix encodes:
// - 0x12 = SHA-256 algorithm code (varint)
// - 0x20 = 32-byte digest length (varint)
func (ab AddressBuilder) GetAddressFromPublicKey(publicKeyBytes []byte) (xc.Address, error) {
fingerprint, err := FingerprintFromPublicKey(publicKeyBytes)
if err != nil {
return "", err
}
name := hex.EncodeToString(publicKeyBytes)
addr := xc.Address(name + "::" + fingerprint)

return addr, nil
}

func (ab AddressBuilder) AddressRegistrationRequired(address xc.Address) bool {
return true
}

// FingerprintFromPublicKey returns the Canton key fingerprint for a raw Ed25519 public key.
func FingerprintFromPublicKey(rawPubKey []byte) (string, error) {
if len(rawPubKey) != 32 {
return "", fmt.Errorf("invalid ed25519 public key length: expected 32 bytes, got %d", len(rawPubKey))
}
// HashPurpose.PublicKeyFingerprint id=12 encoded as big-endian int32 (4 bytes)
var purposeBytes [4]byte
binary.BigEndian.PutUint32(purposeBytes[:], 12)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to see a defined constant for this "12" purpose and any comment or link to what the purpose means, or if there are others.


h := sha256.New()
h.Write(purposeBytes[:])
h.Write(rawPubKey)
digest := h.Sum(nil)

// Multihash: varint(0x12=SHA-256) || varint(0x20=32) || digest
return "1220" + hex.EncodeToString(digest), nil
}

// FingerprintFromPartyID recomputes the Canton key fingerprint from a party ID
// whose name segment is the hex-encoded Ed25519 public key produced by AddressBuilder.
func FingerprintFromPartyID(addr xc.Address) (string, error) {
name, fingerprint, err := ParsePartyID(addr)
if err != nil {
return "", err
}

publicKeyBytes, err := hex.DecodeString(name)
if err != nil {
return "", fmt.Errorf("invalid Canton party name %q: expected hex-encoded public key: %w", name, err)
}
computed, err := FingerprintFromPublicKey(publicKeyBytes)
if err != nil {
return "", err
}
if computed != fingerprint {
return "", fmt.Errorf("canton party fingerprint mismatch: computed %q from address public key, got %q", computed, fingerprint)
}
return computed, nil
}

// ParsePartyID splits a Canton party ID into its name and fingerprint components.
// Expected format: "<name>::<fingerprint>" where fingerprint starts with "1220".
func ParsePartyID(addr xc.Address) (name string, fingerprint string, err error) {
s := string(addr)
idx := strings.Index(s, "::")
if idx < 0 {
return "", "", fmt.Errorf("invalid Canton party ID %q: missing '::' separator", s)
}
name = s[:idx]
fingerprint = s[idx+2:]
if len(fingerprint) < 4 || fingerprint[:4] != "1220" {
return "", "", fmt.Errorf("invalid Canton fingerprint %q: must start with '1220' (SHA-256 multihash prefix)", fingerprint)
}
return name, fingerprint, nil
}
Loading
Loading