transfer

package
v0.5.1 Latest Latest
Warning

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

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

Documentation

Overview

Package transfer implements the non-custodial prepare/execute transfer API.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrTransferNotFound = errors.New("transfer not found")
	ErrTransferExpired  = errors.New("transfer expired")
	ErrCacheFull        = errors.New("cache is full")
)

Functions

func RegisterRoutes

func RegisterRoutes(r chi.Router, svc Service, logger *zap.Logger)

RegisterRoutes registers the non-custodial prepare/execute transfer endpoints.

Types

type CacheMetrics

type CacheMetrics struct {
	// PutsTotal counts Put calls by result: "ok" or "full".
	PutsTotal *prometheus.CounterVec

	// GetsTotal counts GetAndDelete calls by result: "ok", "not_found", or "expired".
	GetsTotal *prometheus.CounterVec
}

CacheMetrics holds Prometheus collectors for the prepared-transfer cache.

func NewCacheMetrics

func NewCacheMetrics(reg sharedmetrics.NamespacedRegisterer) *CacheMetrics

NewCacheMetrics registers transfer cache metrics against the given registerer.

func NewNopCacheMetrics

func NewNopCacheMetrics() *CacheMetrics

NewNopCacheMetrics returns a CacheMetrics instance backed by a throwaway registry. Use in tests where metric values are not asserted.

type ExecuteRequest

type ExecuteRequest struct {
	TransferID string `json:"transfer_id"`
	Signature  string `json:"signature"` // hex-encoded DER signature
	SignedBy   string `json:"signed_by"` // Canton multihash fingerprint
}

ExecuteRequest is the HTTP request body for executing a prepared transfer.

type ExecuteResponse

type ExecuteResponse struct {
	Status string `json:"status"` // "completed"
}

ExecuteResponse is the HTTP response body for a completed transfer.

type IncomingTransfer

type IncomingTransfer struct {
	ContractID      string `json:"contract_id"`
	SenderPartyID   string `json:"sender_party_id"`
	ReceiverPartyID string `json:"receiver_party_id"`
	Amount          string `json:"amount"`
	InstrumentAdmin string `json:"instrument_admin"`
	InstrumentID    string `json:"instrument_id"`
	Symbol          string `json:"symbol,omitempty"`
	Decimals        int    `json:"decimals,omitempty"`
	Name            string `json:"name,omitempty"`
	ContractAddress string `json:"contract_address,omitempty"`
}

IncomingTransfer represents a single pending inbound transfer offer. Fields downstream of the on-ledger TransferOffer are always populated; token-metadata fields (Symbol, Decimals, ContractAddress, Name) are populated when the instrument is in the api-server's supported_tokens config and omitted otherwise.

type IncomingTransfersList

type IncomingTransfersList struct {
	Items   []IncomingTransfer `json:"items"`
	Total   int64              `json:"total"`
	Page    int                `json:"page"`
	Limit   int                `json:"limit"`
	HasMore bool               `json:"has_more"`
}

IncomingTransfersList is the HTTP response body for GET /api/v2/transfer/incoming. Pagination is page/limit-based to match the indexer's underlying envelope (`pkg/indexer.Page[T]`): callers ask for a specific page rather than carrying an opaque cursor, since the indexer is keyed by ledger_offset and a stable numeric offset is the natural cursor. HasMore is derived from page*limit < total so clients can stop iterating without needing arithmetic.

type InstrumentedCache

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

InstrumentedCache wraps a TransferCache and records Prometheus metrics for every Put and GetAndDelete call.

func NewInstrumentedCache

func NewInstrumentedCache(inner TransferCache, metrics *CacheMetrics) *InstrumentedCache

NewInstrumentedCache returns a metrics-instrumented wrapper around the given TransferCache.

func (*InstrumentedCache) GetAndDelete

func (c *InstrumentedCache) GetAndDelete(transferID string) (*token.PreparedTransfer, error)

func (*InstrumentedCache) Put

func (c *InstrumentedCache) Put(transfer *token.PreparedTransfer) error

type PendingOfferLister

type PendingOfferLister interface {
	GetPendingOffersForParty(
		ctx context.Context, partyID string, p indexer.Pagination,
	) (*indexer.Page[indexer.PendingOffer], error)
}

PendingOfferLister is the narrow slice of indexer/client.Client used by ListIncoming. The transfer service treats the indexer as the source of truth for pending TransferOffer state instead of querying Canton directly — the indexer already maintains `indexer_pending_offers` with all the fields we need (sender, amount, instrument), so going through it avoids duplicate decode logic in cantonsdk/token.

type PrepareAcceptRequest

type PrepareAcceptRequest struct {
	InstrumentAdmin string `json:"instrument_admin"` // Canton party ID of the instrument admin
}

PrepareAcceptRequest is the HTTP request body for preparing a non-custodial accept.

type PrepareRequest

type PrepareRequest struct {
	To     string `json:"to"`     // Recipient EVM address (0x...)
	Amount string `json:"amount"` // Token amount (decimal string)
	Token  string `json:"token"`  // "DEMO" or "PROMPT"
}

PrepareRequest is the HTTP request body for preparing a non-custodial transfer.

type PrepareResponse

type PrepareResponse struct {
	TransferID      string `json:"transfer_id"`
	TransactionHash string `json:"transaction_hash"` // hex-encoded hash to sign
	PartyID         string `json:"party_id"`
	ExpiresAt       string `json:"expires_at"` // RFC3339
}

PrepareResponse is the HTTP response body for a prepared transfer.

type PreparedTransferCache

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

PreparedTransferCache is an in-memory cache for prepared transfers awaiting external signatures.

func NewPreparedTransferCache

func NewPreparedTransferCache(ttl time.Duration, maxSize int) *PreparedTransferCache

NewPreparedTransferCache creates a new cache with the given TTL and a maximum number of entries.

func (*PreparedTransferCache) GetAndDelete

func (c *PreparedTransferCache) GetAndDelete(transferID string) (*token.PreparedTransfer, error)

GetAndDelete atomically retrieves and removes a prepared transfer. Returns ErrTransferNotFound if the ID doesn't exist, ErrTransferExpired if past TTL.

func (*PreparedTransferCache) Put

Put stores a prepared transfer in the cache. It sets ExpiresAt from the cache TTL. Returns ErrCacheFull if the maximum number of entries has been reached.

func (*PreparedTransferCache) Start

Start runs a background goroutine that periodically removes expired entries. It stops when the context is canceled.

type Service

type Service interface {
	Prepare(ctx context.Context, senderEVMAddr string, req *PrepareRequest) (*PrepareResponse, error)
	Execute(ctx context.Context, senderEVMAddr string, req *ExecuteRequest) (*ExecuteResponse, error)

	// ListIncoming returns one page of pending inbound TransferOffer details for the
	// user with the given EVM address. This call is unauthenticated — anyone can
	// query any address's pending offers; the response is intentionally minimized
	// (party IDs truncated) to keep that from leaking counterparties.
	ListIncoming(ctx context.Context, evmAddr string, p indexer.Pagination) (*IncomingTransfersList, error)
	// PrepareAccept builds a Canton transaction for accepting an inbound offer.
	PrepareAccept(
		ctx context.Context, evmAddr, contractID string, req *PrepareAcceptRequest,
	) (*PrepareResponse, error)
	// ExecuteAccept completes a previously prepared accept using the client's DER signature.
	ExecuteAccept(ctx context.Context, evmAddr string, req *ExecuteRequest) (*ExecuteResponse, error)
}

Service is the interface for the non-custodial prepare/execute transfer flow.

func NewLog

func NewLog(svc Service, logger *zap.Logger) Service

NewLog creates a logging decorator for the transfer Service. It logs method entry/exit, duration, errors, and sanitized request/response data.

type TransferCache

type TransferCache interface {
	Put(transfer *token.PreparedTransfer) error
	GetAndDelete(transferID string) (*token.PreparedTransfer, error)
}

TransferCache is the interface for caching prepared transfers.

type TransferService

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

TransferService implements the non-custodial prepare/execute transfer flow.

func NewTransferService

func NewTransferService(
	cantonToken token.Token,
	userStore UserStore,
	cache TransferCache,
	tokenCfg *pkgtoken.Config,
	offerLister PendingOfferLister,
) *TransferService

NewTransferService creates a new TransferService. tokenCfg supplies the list of allowed token symbols (used by Prepare) and the instrument→EVM-contract mapping (used to enrich ListIncoming responses). offerLister is required — the api-server now wires every service to the same indexer client at startup, so ListIncoming relies on it being non-nil.

func (*TransferService) Execute

func (s *TransferService) Execute(ctx context.Context, senderEVMAddr string, req *ExecuteRequest) (*ExecuteResponse, error)

Execute completes a previously prepared transfer using the client's DER signature.

func (*TransferService) ExecuteAccept

func (s *TransferService) ExecuteAccept(ctx context.Context, evmAddr string, req *ExecuteRequest) (*ExecuteResponse, error)

ExecuteAccept completes a previously prepared accept using the client's DER signature.

func (*TransferService) ListIncoming

func (s *TransferService) ListIncoming(ctx context.Context, evmAddr string, p indexer.Pagination) (*IncomingTransfersList, error)

ListIncoming returns one page of pending inbound TransferOffer details for the user with the given EVM address. Unauthenticated: callers do not need to prove ownership of evmAddr. Data comes from the indexer's `indexer_pending_offers` table (already filtered to status=PENDING at the SQL level), so a single indexer call serves a single client page — no buffering, no re-aggregation.

func (*TransferService) Prepare

func (s *TransferService) Prepare(ctx context.Context, senderEVMAddr string, req *PrepareRequest) (*PrepareResponse, error)

Prepare builds a Canton transaction and returns the hash for external signing.

func (*TransferService) PrepareAccept

func (s *TransferService) PrepareAccept(
	ctx context.Context, evmAddr, contractID string, req *PrepareAcceptRequest,
) (*PrepareResponse, error)

PrepareAccept builds a Canton transaction for accepting an inbound offer.

type UserStore

type UserStore interface {
	GetUserByEVMAddress(ctx context.Context, evmAddress string) (*user.User, error)
}

UserStore is the narrow interface for looking up users.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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