token

package
v0.5.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 10, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package token implements CIP-56 token operations such as mint, burn, transfer, and balance queries.

Index

Constants

This section is empty.

Variables

View Source
var ErrInsufficientBalance = errors.New("insufficient balance")

ErrInsufficientBalance indicates the owner's total unlocked balance is less than the required amount.

View Source
var ErrTransferFactoryNotFound = errors.New("no active CIP56TransferFactory found")

ErrTransferFactoryNotFound indicates no active CIP56TransferFactory contract exists on the ledger.

Functions

func ConvertChoiceContext

func ConvertChoiceContext(raw json.RawMessage) (map[string]string, error)

ConvertChoiceContext parses the registry's choice context JSON into a map suitable for EncodeExtraArgs. Returns nil for null or empty input. Used for the legacy TextMap-of-Text shape. AllocationFactory-based registries return the richer AnyValue shape, which is parsed by ConvertAnyValueChoiceContext instead.

func ConvertDisclosedContracts

func ConvertDisclosedContracts(raw json.RawMessage, fallbackDomainID string) ([]*lapiv2.DisclosedContract, error)

ConvertDisclosedContracts parses the registry's disclosed contracts JSON into proto messages.

Types

type AcceptChoiceContext

type AcceptChoiceContext struct {
	Values map[string]AnyValue `json:"values"`
}

AcceptChoiceContext holds the TextMap AnyValue for TransferInstruction_Accept.

func ConvertAnyValueChoiceContext

func ConvertAnyValueChoiceContext(raw json.RawMessage) (AcceptChoiceContext, error)

ConvertAnyValueChoiceContext parses an AnyValue-shaped registry choice context (`{"values": {key: {tag, value}, ...}}`) into AcceptChoiceContext. Used when the registry returns context entries for AllocationFactory's TransferFactory_Transfer choice (instrument-configuration as AV_ContractId, sender-credentials as AV_List). Returns an empty context when raw is null or doesn't carry the AnyValue shape.

type AcceptContextResponse

type AcceptContextResponse struct {
	ChoiceContextData  AcceptChoiceContext         `json:"choiceContextData"`
	DisclosedContracts []registryDisclosedContract `json:"disclosedContracts"`
}

AcceptContextResponse is returned by the registrar's accept choice-context endpoint.

type AnyValue

type AnyValue struct {
	Tag   string          `json:"tag"`
	Value json.RawMessage `json:"value"`
}

AnyValue is the JSON {"tag": "...", "value": ...} ADT for a Daml-LF AnyValue. Tags: AV_ContractId | AV_Text | AV_Party | AV_Bool | AV_Int | AV_Decimal | AV_List | AV_Map.

type AnyValueMap

type AnyValueMap struct {
	Values map[string]any `json:"values"`
}

AnyValueMap models the `{"values": {...}}` AnyValue container that wraps metadata and context maps in the Splice token-standard JSON.

type BurnRequest

type BurnRequest struct {
	HoldingCID  string
	Amount      string
	TokenSymbol string            // needed for GetTokenConfigCID lookup
	EventMeta   map[string]string // bridge context; nil for native burns
}

BurnRequest represents an issuer burn request via TokenConfig.

type ChoiceArguments

type ChoiceArguments struct {
	ExpectedAdmin string                 `json:"expectedAdmin"`
	Transfer      RegistryTransferDetail `json:"transfer"`
	ExtraArgs     ExtraArgs              `json:"extraArgs"`
}

ChoiceArguments wraps the on-ledger TransferFactory_Transfer choice arguments alongside the off-ledger extraArgs (context + meta) consumed by the registry.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client implements CIP-56 token operations.

func New

func New(cfg *Config, l ledger.Ledger, id identity.Identity, opts ...Option) (*Client, error)

New creates a new token client.

func (*Client) AcceptTransferInstruction

func (c *Client) AcceptTransferInstruction(ctx context.Context, partyID, instructionCID, instrumentAdmin string) error

func (*Client) Burn

func (c *Client) Burn(ctx context.Context, req *BurnRequest) error

func (*Client) ExecuteTransfer

func (c *Client) ExecuteTransfer(ctx context.Context, req *ExecuteTransferRequest) error

func (*Client) FindPendingInboundTransferInstructions

func (c *Client) FindPendingInboundTransferInstructions(ctx context.Context, partyID string) ([]string, error)

func (*Client) GetAllHoldings

func (c *Client) GetAllHoldings(ctx context.Context) ([]*Holding, error)

func (*Client) GetAllocationFactory

func (c *Client) GetAllocationFactory(ctx context.Context) (*TransferFactoryInfo, error)

GetAllocationFactory finds any active contract implementing the Splice TransferFactory interface. Used as a fallback when no CIP56TransferFactory exists (e.g. tokens using AllocationFactory from utility_registry_app_v0). Uses an interface filter keyed on SpliceTransferPackageID so the concrete template's package ID never needs to be known.

func (*Client) GetBalanceByFingerprint

func (c *Client) GetBalanceByFingerprint(ctx context.Context, fingerprint string, tokenSymbol string) (string, error)

func (*Client) GetBalanceByPartyID

func (c *Client) GetBalanceByPartyID(ctx context.Context, partyID string, tokenSymbol string) (string, error)

func (*Client) GetHoldings

func (c *Client) GetHoldings(ctx context.Context, ownerParty string, tokenSymbol string) ([]*Holding, error)

func (*Client) GetHoldingsByParty

func (c *Client) GetHoldingsByParty(ctx context.Context, ownerParty, instrumentID string) ([]*Holding, error)

GetHoldingsByParty queries all Splice HoldingV1 holdings visible to the given party. This is the unified query path for all Splice-compliant tokens (CIP-56 and external like USDCx). If instrumentID is non-empty, results are filtered to that instrument. TODO: add unit tests with mocked ledger client (happy path, filtering, errors).

func (*Client) GetTokenConfigCID

func (c *Client) GetTokenConfigCID(ctx context.Context, tokenSymbol string) (string, error)

func (*Client) GetTokenTransferEvents

func (c *Client) GetTokenTransferEvents(ctx context.Context) ([]*TokenTransferEvent, error)

func (*Client) GetTotalSupply

func (c *Client) GetTotalSupply(ctx context.Context, tokenSymbol string) (string, error)

func (*Client) GetTransferFactory

func (c *Client) GetTransferFactory(ctx context.Context) (*TransferFactoryInfo, error)

func (*Client) Mint

func (c *Client) Mint(ctx context.Context, req *MintRequest) (string, error)

func (*Client) PrepareAcceptTransfer

func (c *Client) PrepareAcceptTransfer(
	ctx context.Context, partyID, instructionCID, instrumentAdmin string,
) (*PreparedTransfer, error)

func (*Client) PrepareTransfer

func (c *Client) PrepareTransfer(ctx context.Context, req *PrepareTransferRequest) (*PreparedTransfer, error)

func (*Client) TransferByFingerprint

func (c *Client) TransferByFingerprint(ctx context.Context, idempotencyKey, fromFingerprint,
	toFingerprint, amount, tokenSymbol string) error

func (*Client) TransferByPartyID

func (c *Client) TransferByPartyID(ctx context.Context, idempotencyKey, fromParty, toParty, amount, tokenSymbol string) error

func (*Client) TransferInternalByPartyID

func (c *Client) TransferInternalByPartyID(ctx context.Context, idempotencyKey, fromParty, toParty, amount, tokenSymbol string) error

type Config

type Config struct {
	DomainID    string `yaml:"domain_id"`
	IssuerParty string `yaml:"issuer_party"`
	UserID      string `yaml:"user_id"`

	CIP56PackageID              string `yaml:"cip56_package_id" validate:"required"`
	SpliceTransferPackageID     string `yaml:"splice_transfer_package_id" validate:"required"`
	SpliceHoldingPackageID      string `yaml:"splice_holding_package_id" validate:"required"`
	UtilityRegistryAppPackageID string `yaml:"utility_registry_app_package_id"`
	// UtilityRegistryPackageID is the utility_registry_v0 package ID.
	// Required when using AllocationFactory-based tokens (e.g., USDCx on P2) so that
	// InstrumentConfiguration and TransferRule can be looked up and provided as context
	// for the TransferFactory_Transfer choice.
	UtilityRegistryPackageID string `yaml:"utility_registry_package_id"`

	// ExternalTokens maps InstrumentAdmin party IDs to their registry configuration.
	// Tokens whose InstrumentAdmin matches IssuerParty use local ACS-based factory discovery.
	// Tokens whose InstrumentAdmin is in this map use the external registry API.
	ExternalTokens map[string]ExternalTokenConfig `yaml:"external_tokens"`
}

Config contains the configuration required to initialize the token client.

type EventType

type EventType string

EventType represents the kind of token mutation.

const (
	EventTypeMint     EventType = "MINT"
	EventTypeBurn     EventType = "BURN"
	EventTypeTransfer EventType = "TRANSFER"
)

type ExecuteTransferRequest

type ExecuteTransferRequest struct {
	PreparedTransfer *PreparedTransfer // The prepared transfer to execute
	Signature        []byte            // DER-encoded ECDSA signature of TransactionHash
	SignedBy         string            // Canton multihash fingerprint of signing key
}

ExecuteTransferRequest contains the client-signed data to complete a non-custodial transfer.

type ExternalTokenConfig

type ExternalTokenConfig struct {
	RegistryURL string `yaml:"registry_url" validate:"required"`
}

ExternalTokenConfig holds the registry endpoint for an external token issuer. Key in the map is the InstrumentAdmin party ID (e.g., Circle's Bridge-Operator).

type ExtraArgs

type ExtraArgs struct {
	Context AnyValueMap `json:"context"`
	Meta    AnyValueMap `json:"meta"`
}

ExtraArgs carries the off-ledger context and meta AnyValue maps required by the AllocationFactory's TransferFactory_Transfer choice.

type Holding

type Holding struct {
	ContractID      string
	Issuer          string
	Owner           string
	Amount          string
	Symbol          string // derived from Metadata["splice.chainsafe.io/symbol"]
	InstrumentAdmin string
	InstrumentID    string
	Locked          bool
	Metadata        map[string]string
}

Holding represents a CIP56Holding contract (Splice-compliant).

type InstrumentRef

type InstrumentRef struct {
	Admin string `json:"admin"`
	ID    string `json:"id"`
}

InstrumentRef is the structured instrument identifier the registry expects: the issuer admin party plus the instrument id within that issuer's namespace.

type KeyResolver

type KeyResolver func(partyID string) (Signer, error)

KeyResolver looks up a signer for the given Canton party ID. Used by Interactive Submission to sign transactions on behalf of external parties.

type MintRequest

type MintRequest struct {
	RecipientParty string
	Amount         string
	TokenSymbol    string // needed for GetTokenConfigCID lookup
	ConfigCID      string
	EventMeta      map[string]string // bridge context; nil for native mints
}

MintRequest represents an issuer mint request via TokenConfig.

type Option

type Option func(*settings)

Option configures the token client.

func WithKeyResolver

func WithKeyResolver(kr KeyResolver) Option

WithKeyResolver provides a function to look up signing keys by party ID. Required for transfers involving external parties (Interactive Submission).

func WithLogger

func WithLogger(l *zap.Logger) Option

WithLogger sets a custom logger for the token client.

func WithRegistryClient

func WithRegistryClient(rc *RegistryClient) Option

WithRegistryClient sets the HTTP client for external Transfer Factory Registry API calls. Required for transferring tokens issued by external parties (e.g., USDCx).

type PrepareTransferRequest

type PrepareTransferRequest struct {
	FromPartyID string
	ToPartyID   string
	Amount      string
	TokenSymbol string
}

PrepareTransferRequest contains the parameters for preparing a non-custodial transfer.

type PreparedTransfer

type PreparedTransfer struct {
	TransferID           string                             // UUID
	TransactionHash      []byte                             // PreparedTransactionHash from Canton (what client signs)
	PreparedTransaction  *interactivev2.PreparedTransaction // Opaque protobuf message (passed back on execute)
	HashingSchemeVersion interactivev2.HashingSchemeVersion // Must match on execute
	PartyID              string                             // The acting party
	ExpiresAt            time.Time                          // Cache TTL deadline
}

PreparedTransfer holds the result of a prepare step for non-custodial signing.

type RegistryClient

type RegistryClient struct {
	// contains filtered or unexported fields
}

RegistryClient calls the Splice Transfer Factory Registry API to discover transfer factories for external tokens (e.g., USDCx).

func NewRegistryClient

func NewRegistryClient(httpClient *http.Client) *RegistryClient

NewRegistryClient creates a new registry client.

func (*RegistryClient) GetAcceptChoiceContext

func (rc *RegistryClient) GetAcceptChoiceContext(
	ctx context.Context, registryBaseURL, registrarParty, instructionCID string,
) (*AcceptContextResponse, error)

GetAcceptChoiceContext calls the registrar's accept choice-context endpoint for a pending TransferInstruction. Returns the choiceContextData (AnyValue map) and disclosed contracts needed to exercise TransferInstruction_Accept.

func (*RegistryClient) GetTransferFactory

func (rc *RegistryClient) GetTransferFactory(
	ctx context.Context, registryBaseURL, registrarParty string, req *RegistryRequest,
) (*RegistryResponse, error)

GetTransferFactory calls the registry to discover the transfer factory for an external token. registrarParty is the issuer's party ID under whose namespace the registry is mounted (DA's hosted multi-registrar API multiplexes registrars by URL path).

type RegistryRequest

type RegistryRequest struct {
	ChoiceArguments    ChoiceArguments `json:"choiceArguments"`
	ExcludeDebugFields bool            `json:"excludeDebugFields"`
}

RegistryRequest is the POST body for the Transfer Factory Registry API. Shape matches the Splice token-standard spec as hosted by DA's utilities API (e.g. api.utilities.digitalasset-dev.com): choice arguments are wrapped in `choiceArguments`, with a sibling `excludeDebugFields` flag.

type RegistryResponse

type RegistryResponse struct {
	FactoryID          string          `json:"factoryId"`
	TransferKind       string          `json:"transferKind"`
	ChoiceContext      json.RawMessage `json:"choiceContext"`
	DisclosedContracts json.RawMessage `json:"disclosedContracts"`
}

RegistryResponse is the registry response as consumed by the SDK. `ChoiceContext` holds the AnyValue map (i.e. `choiceContext.choiceContextData` from the wire shape) and `DisclosedContracts` holds the array — both lifted out of the wire envelope by GetTransferFactory so the downstream converters (ConvertAnyValueChoiceContext / ConvertDisclosedContracts) can consume them directly.

type RegistryTransferDetail

type RegistryTransferDetail struct {
	Sender           string        `json:"sender"`
	Receiver         string        `json:"receiver"`
	Amount           string        `json:"amount"`
	InstrumentID     InstrumentRef `json:"instrumentId"`
	InputHoldingCIDs []string      `json:"inputHoldingCids"`
	Meta             AnyValueMap   `json:"meta"`
	ExecuteBefore    string        `json:"executeBefore"`
	RequestedAt      string        `json:"requestedAt"`
}

RegistryTransferDetail contains the transfer parameters for registry lookup. `executeBefore` and `requestedAt` are RFC3339 timestamps; the registry uses them to assert the transfer falls within the issuer's validity window.

type Signer

type Signer interface {
	SignDER(message []byte) ([]byte, error)
	Fingerprint() (string, error)
}

Signer can produce DER-encoded ECDSA signatures for Canton Interactive Submission. SignDER hashes the message with SHA-256 before signing (Canton returns multihash data). Fingerprint returns the Canton key fingerprint (multihash of SPKI public key).

type TemplateIdentifier

type TemplateIdentifier struct {
	PackageID  string `json:"package_id"`
	ModuleName string `json:"module_name"`
	EntityName string `json:"entity_name"`
}

TemplateIdentifier is a portable representation of a Daml template/interface reference.

type Token

type Token interface {
	// GetTokenConfigCID returns the active TokenConfig contract ID for the given token symbol.
	GetTokenConfigCID(ctx context.Context, tokenSymbol string) (string, error)

	// Mint mints tokens using TokenConfig.IssuerMint and returns the created holding contract ID.
	Mint(ctx context.Context, req *MintRequest) (string, error)

	// Burn burns tokens using TokenConfig.IssuerBurn.
	Burn(ctx context.Context, req *BurnRequest) error

	// GetHoldings returns holdings for the owner and token symbol.
	// Delegates to GetHoldingsByParty using the Splice HoldingV1 interface.
	GetHoldings(ctx context.Context, ownerParty string, tokenSymbol string) ([]*Holding, error)

	// GetHoldingsByParty queries all Splice HoldingV1 holdings visible to the given party,
	// optionally filtered by instrumentID (empty string returns all instruments).
	// This is the unified query path for all Splice-compliant tokens (CIP-56 and external).
	GetHoldingsByParty(ctx context.Context, ownerParty, instrumentID string) ([]*Holding, error)

	// GetAllHoldings returns all CIP56Holding contracts queried as IssuerParty.
	// Used by the indexer and totalSupply — does NOT use the unified HoldingV1 path.
	GetAllHoldings(ctx context.Context) ([]*Holding, error) // TODO: use pagination

	// GetBalanceByFingerprint returns the owner's total balance (sum of holdings) for the token symbol.
	GetBalanceByFingerprint(ctx context.Context, fingerprint string, tokenSymbol string) (string, error)

	// GetBalanceByPartyID returns the owner's total balance (sum of holdings) for the token symbol.
	GetBalanceByPartyID(ctx context.Context, partyID string, tokenSymbol string) (string, error)

	// GetTotalSupply returns the total supply (sum across all holdings) for the token symbol.
	GetTotalSupply(ctx context.Context, tokenSymbol string) (string, error)

	// TransferByFingerprint transfers tokens by resolving fingerprints to parties.
	// idempotencyKey is used as the Canton CommandId for idempotent submission.
	TransferByFingerprint(ctx context.Context, idempotencyKey, fromFingerprint, toFingerprint, amount, tokenSymbol string) error

	// TransferByPartyID transfers tokens by party IDs using Interactive Submission.
	// idempotencyKey is used as the Canton CommandId for idempotent submission.
	// Requires a KeyResolver configured via WithKeyResolver (external/secp256k1 parties only).
	TransferByPartyID(ctx context.Context, idempotencyKey, fromParty, toParty, amount, tokenSymbol string) error

	// TransferInternalByPartyID transfers tokens for an internal Canton party using
	// regular command submission (SubmitAndWaitForTransaction). Unlike TransferByPartyID,
	// no KeyResolver is required — the Canton node holds the signing key. Use this for
	// issuer or bridge parties that are not registered with an external secp256k1 key.
	// idempotencyKey is used as the Canton CommandId for idempotent submission.
	TransferInternalByPartyID(ctx context.Context, idempotencyKey, fromParty, toParty, amount, tokenSymbol string) error

	// GetTokenTransferEvents returns all active CIP56.Events.TokenTransferEvent contracts visible to relayerParty.
	GetTokenTransferEvents(ctx context.Context) ([]*TokenTransferEvent, error)

	// GetTransferFactory returns the active CIP56TransferFactory contract ID and its
	// CreatedEventBlob for explicit contract disclosure by external wallets (Splice Registry API).
	GetTransferFactory(ctx context.Context) (*TransferFactoryInfo, error)

	// PrepareTransfer builds a Canton transaction for a non-custodial transfer and returns
	// the hash that the client must sign externally.
	PrepareTransfer(ctx context.Context, req *PrepareTransferRequest) (*PreparedTransfer, error)

	// ExecuteTransfer completes a previously prepared transfer using the client's DER signature.
	ExecuteTransfer(ctx context.Context, req *ExecuteTransferRequest) error

	// FindPendingInboundTransferInstructions returns contract IDs of active TransferOffer contracts
	// where partyID is an observer (i.e. the receiver). Used by the accept worker to find offers
	// to accept on behalf of custodial parties.
	FindPendingInboundTransferInstructions(ctx context.Context, partyID string) ([]string, error)

	// AcceptTransferInstruction accepts a pending inbound transfer for a custodial party.
	// Calls the registrar's accept choice-context endpoint (keyed by instrumentAdmin), encodes
	// the AnyValue choiceContext, and exercises TransferInstruction_Accept via SubmitAndWait.
	AcceptTransferInstruction(ctx context.Context, partyID, instructionCID, instrumentAdmin string) error

	// PrepareAcceptTransfer builds a Canton transaction for accepting a pending inbound
	// transfer instruction and returns the hash that the client must sign externally.
	// Use ExecuteTransfer to complete the accept once the client has signed.
	PrepareAcceptTransfer(
		ctx context.Context, partyID, instructionCID, instrumentAdmin string,
	) (*PreparedTransfer, error)
}

Token defines CIP-56 token operations.

type TokenTransferEvent

type TokenTransferEvent struct {
	ContractID      string
	Issuer          string
	FromParty       string // empty = mint (no sender)
	ToParty         string // empty = burn (no receiver)
	Amount          string
	InstrumentAdmin string
	InstrumentID    string
	Timestamp       time.Time
	Meta            map[string]string // bridge context; nil for native ops
	AuditObservers  []string
}

TokenTransferEvent is a decoded representation of a CIP56.Events.TokenTransferEvent. Unified event for all token mutations (mint, burn, transfer). See canton-erc20 repository: daml/cip56-token/src/CIP56/Events.daml

func (*TokenTransferEvent) EventType

func (e *TokenTransferEvent) EventType() EventType

EventType derives the event type from fromParty/toParty.

func (*TokenTransferEvent) EvmDestination

func (e *TokenTransferEvent) EvmDestination() string

EvmDestination returns the bridge withdrawal address from metadata.

func (*TokenTransferEvent) EvmTxHash

func (e *TokenTransferEvent) EvmTxHash() string

EvmTxHash returns the bridge deposit tx hash from metadata.

func (*TokenTransferEvent) UserFingerprint

func (e *TokenTransferEvent) UserFingerprint() string

UserFingerprint returns the bridge audit fingerprint from metadata.

type TransferFactoryInfo

type TransferFactoryInfo struct {
	ContractID       string
	CreatedEventBlob []byte
	TemplateID       TemplateIdentifier
}

TransferFactoryInfo contains the CIP56TransferFactory contract details required by external wallets for Splice-compliant explicit contract disclosure.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL