Skip to content

EDS - Add TokenStandard TokenMetadataV1 implementation (NONEVM-4878) - #823

Open
friedemannf wants to merge 6 commits into
mainfrom
eds-token-metadata
Open

EDS - Add TokenStandard TokenMetadataV1 implementation (NONEVM-4878)#823
friedemannf wants to merge 6 commits into
mainfrom
eds-token-metadata

Conversation

@friedemannf

@friedemannf friedemannf commented Jul 28, 2026

Copy link
Copy Markdown
Member

This pull request introduces several improvements to the Token Standard API, focusing on enhanced instrument metadata, accurate and efficient total supply calculation with caching, and expanded test coverage. The main changes add support for instrument name and symbol, implement a cache for total supply values to reduce expensive computations, and update tests to cover these new features.

Instrument metadata and supply calculation improvements:

  • Added TokenName and TokenSymbol fields to the Registry and TokenConfig structs, allowing each token to have a display name and symbol. These are now included in the API responses for instrument metadata. [1] [2] [3]
  • Updated the ListInstruments and GetInstrument endpoints to include name, symbol, and totalSupply (with timestamp) in their responses, providing richer and more accurate metadata for clients. [1] [2]

Total supply caching and calculation:

  • Introduced a cache for total supply values per instrument, configurable via the new SupplyCacheTimeout field (default 10 seconds), to avoid repeated expensive calculations. The cache is thread-safe and automatically refreshed as needed. [1] [2] [3] [4] [5]
  • Implemented logic to sum both unlocked and locked holdings for LINK tokens, ensuring the total supply is always accurate. Locked holdings for other instruments are ignored.

Test and codebase updates:

  • Updated and expanded tests in token_standard_test.go to verify correct calculation of total supply, proper caching behavior, and inclusion of name/symbol in API responses. Tests also ensure that locked holdings for other instruments are correctly ignored. [1] [2] [3] [4] [5]
  • Refactored the server constructor to require the InstrumentHoldingStore and properly register parties, ensuring accurate test and production behavior. [1] [2] [3] [4] [5] [6]

These changes collectively improve the API's usability, correctness, and performance when serving token instrument metadata.

Copilot AI review requested due to automatic review settings July 28, 2026 19:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR enables EDS’s TokenStandard API to implement the TokenMetadataV1 endpoints (ListInstruments/GetInstrument) and return token metadata including total supply, backed by EDS stores and covered by unit/integration tests.

Changes:

  • Enable TokenStandard API in the CCIP Burn/Mint integration test and validate TokenMetadata responses (including totalSupply).
  • Extend EDS stores to support listing holdings by instrument and returning multiple active contracts per (party, templateId).
  • Implement totalSupply calculation + caching and wire TokenMetadataV1 responses in the TokenStandard API.

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
integration-tests/ccip/ccip_send_with_token_bnm_test.go Enables TokenStandard API and asserts TokenMetadataV1 totalSupply behavior during a full send flow.
eds/service/service.go Wires InstrumentHoldingStore into TokenStandard API server construction.
eds/internal/store/interfaces.go Updates store interfaces for multi-contract template lookup + instrument holdings listing.
eds/internal/store/instrument_store.go Rekeys holdings storage by instrument and adds ListHoldings API.
eds/internal/store/active_contract_store.go Changes template index to support multiple active contracts per template + party.
eds/internal/mocks/mock_InstrumentHoldingStore.go Updates mocks to include ListHoldings.
eds/internal/mocks/mock_ActiveContractStore.go Updates mocks for GetByTemplateId returning a slice.
eds/internal/api/tokenpool/preapproval.go Adapts to GetByTemplateId returning multiple results.
eds/internal/api/token_standard/token_standard.go Implements TokenMetadataV1 fields (name/symbol/totalSupply/asOf) with caching.
eds/internal/api/token_standard/token_standard_test.go Adds unit tests for totalSupply calculation/caching and metadata fields.
eds/config/config.go Adds registry name/symbol and supply cache timeout configuration (with defaults).
Files not reviewed (2)
  • eds/internal/mocks/mock_ActiveContractStore.go: Generated file
  • eds/internal/mocks/mock_InstrumentHoldingStore.go: Generated file
Comments suppressed due to low confidence (1)

eds/internal/store/active_contract_store.go:232

  • Same as onActiveContract: the package-name TemplateID index isn't populated, so GetByTemplateId calls that use the "#packageName" syntax won't find created contracts.
		contractsByPackageName, ok := contractsForParty[templateIdPackageName]
		if !ok {
			contractsByPackageName = make(map[types.CONTRACT_ID]*apiv2.ActiveContract)
			contractsForParty[templateIdPackageName] = contractsByPackageName
		}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread eds/internal/store/active_contract_store.go
Comment thread eds/internal/store/active_contract_store.go
Comment thread eds/internal/api/tokenpool/preapproval.go
Comment thread eds/internal/store/instrument_store.go
Comment on lines +271 to +276
snapshotTime := time.Now()
holdings, _ := s.instrumentHoldingStore.ListHoldings(splice_api_token_holding_v1.InstrumentId{
Admin: s.admin,
Id: types.TEXT(instrumentId),
})

Comment thread eds/internal/api/token_standard/token_standard.go
Comment thread eds/internal/api/token_standard/token_standard.go Outdated
Comment thread eds/internal/api/token_standard/token_standard.go Outdated
Comment thread eds/internal/api/token_standard/token_standard.go Outdated
Copilot AI review requested due to automatic review settings July 28, 2026 19:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • eds/internal/mocks/mock_ActiveContractStore.go: Generated file
  • eds/internal/mocks/mock_InstrumentHoldingStore.go: Generated file
Comments suppressed due to low confidence (12)

eds/internal/store/active_contract_store.go:180

  • contractsByTemplateId stores a map of contract IDs, but the PackageName key path never inserts the active contract into contractsByPackageName, so lookups by #packageName will always return an empty result.
		contractsByPackageName, ok := contractsForParty[templateIdPackageName]
		if !ok {
			contractsByPackageName = make(map[types.CONTRACT_ID]*apiv2.ActiveContract)
			contractsForParty[templateIdPackageName] = contractsByPackageName
		}

eds/internal/store/active_contract_store.go:232

  • Same issue as in onActiveContract: the PackageName key path initializes the map but never inserts the created contract, so GetByTemplateId using the #packageName form will not work.
		contractsByPackageName, ok := contractsForParty[templateIdPackageName]
		if !ok {
			contractsByPackageName = make(map[types.CONTRACT_ID]*apiv2.ActiveContract)
			contractsForParty[templateIdPackageName] = contractsByPackageName
		}

eds/internal/store/active_contract_store.go:25

  • Now that contractsByTemplateId can hold multiple contracts per (party, templateId), the archive path must delete only the archived contract ID from the inner map (and clean up empty inner maps). The current archive logic still deletes the entire template entry, which will incorrectly remove other active contracts of the same template for that party.
	contractsByTemplateId      map[types.PARTY]map[contracts.TemplateID]map[types.CONTRACT_ID]*apiv2.ActiveContract

eds/internal/store/instrument_store.go:117

  • ListHoldings returns an error when the instrument has no holdings. For a listing API, "no results" should generally be represented as an empty slice with nil error so callers can treat it as zero supply instead of an exceptional condition.
	instrumentHoldings, ok := s.holdings[instrumentId]
	if !ok {
		return nil, fmt.Errorf("no holdings found for instrument %v", instrumentId)
	}

eds/internal/api/token_standard/token_standard.go:276

  • getTotalSupplyForInstrument ignores the ListHoldings error. This can silently return an incorrect totalSupply (e.g., if the holding store isn't running or the query fails).
	holdings, _ := s.instrumentHoldingStore.ListHoldings(splice_api_token_holding_v1.InstrumentId{
		Admin: s.admin,
		Id:    types.TEXT(instrumentId),
	})

eds/internal/api/token_standard/token_standard.go:295

  • GetByTemplateId's boolean return value is ignored here. If no LockedLinkHolding contracts are present, treat it as an empty set explicitly so the intent is clear (and avoid accidentally looping over a nil slice produced by other implementations).
		// List LockLinkHoldings to also sum up
		lockedHoldings, _ := s.activeContractStore.GetByTemplateId(s.admin, contracts.TemplateIDFromBinding(link.LockedLinkHolding{}))
		for _, activeContract := range lockedHoldings {

eds/internal/api/token_standard/token_standard.go:277

  • Summing amounts using big.Float can introduce rounding artifacts for decimal strings (e.g., 0.1-style values), which can lead to incorrect totalSupply output. Prefer summing using an exact representation (e.g., big.Rat, or fixed-point big.Int in 1e10 smallest units for LINK) and then format deterministically.
	totalSupply := new(big.Float)

eds/internal/api/tokenpool/preapproval.go:32

  • If multiple preapproval contracts exist for the same (party, templateId), returning activePreapprovals[0] is non-deterministic (map iteration order) and may pick an arbitrary preapproval. Consider failing fast when more than one is present, or selecting deterministically.
		activePreapprovals, ok := acs.GetByTemplateId(party, templateId)
		if !ok || len(activePreapprovals) == 0 {
			return "", nil, fmt.Errorf("no preapproval found for user %s and template %s", party, templateId.String())
		}

integration-tests/ccip/ccip_send_with_token_bnm_test.go:1083

  • Same as above: this log dereferences TotalSupply / TotalSupplyAsOf without nil checks, which can panic and make the test failure harder to diagnose.
	require.Equal(t, http.StatusOK, linkMetadata.StatusCode(), "expected 200 OK from TokenMetadata API for LINK instrument")
	t.Logf("Queried Token Metadata: id: %v, symbol: %v, name: %v, totalSupply: %v, totalSupplyAsOf: %v",
		linkMetadata.JSON200.Id,
		linkMetadata.JSON200.Symbol,
		linkMetadata.JSON200.Name,

eds/service/service.go:184

  • The TokenStandard API now depends on instrumentHoldingStore to compute totalSupply, but instrumentHoldingStore.Run(...) is only started when TokenPoolAPIConfig.Enabled is true. If TokenStandard is enabled without TokenPool, totalSupply will be wrong (and may always compute as 0).
	if cfg.TokenStandardAPIConfig.Enabled {
		tokenStandardAPIServer, err := token_standard.NewServer(ctx, logger, activeContractStore, instrumentHoldingStore, cfg.TokenStandardAPIConfig)
		if err != nil {

integration-tests/ccip/ccip_send_with_token_bnm_test.go:777

  • This test dereferences TotalSupply / TotalSupplyAsOf without asserting they are present. If the API returns these fields as null (or JSON200 is nil), the test will panic instead of producing a helpful failure.

This issue also appears on line 1079 of the same file.

	require.Equal(t, http.StatusOK, linkMetadata.StatusCode(), "expected 200 OK from TokenMetadata API for LINK instrument")
	t.Logf("Queried Token Metadata: id: %v, symbol: %v, name: %v, totalSupply: %v, totalSupplyAsOf: %v",
		linkMetadata.JSON200.Id,
		linkMetadata.JSON200.Symbol,
		linkMetadata.JSON200.Name,

integration-tests/ccip/ccip_send_with_token_bnm_test.go:824

  • tokenTransferAmountDecimal was changed to 1.0, but later comments still describe the old 0.0000010000 → 10,000 smallest units scenario. Updating those comments will keep the test reasoning aligned with the new amount assertions.
	const tokenTransferAmountDecimal = "1.0"

Copilot AI review requested due to automatic review settings July 28, 2026 20:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • eds/internal/mocks/mock_ActiveContractStore.go: Generated file
  • eds/internal/mocks/mock_InstrumentHoldingStore.go: Generated file
Comments suppressed due to low confidence (6)

eds/internal/api/token_standard/token_standard.go:275

  • getTotalSupplyForInstrument ignores the error returned by InstrumentHoldingStore.ListHoldings, which can mask store/stream failures and lead to silently incorrect TotalSupply values.
	holdings, _ := s.instrumentHoldingStore.ListHoldings(splice_api_token_holding_v1.InstrumentId{
		Admin: s.admin,
		Id:    types.TEXT(instrumentId),
	})

eds/internal/api/token_standard/token_standard.go:281

  • TotalSupply is accumulated using math/big.Float, which is a binary floating point type and can introduce rounding artifacts when summing decimal strings (e.g., 49.05 may serialize as 49.049999…); this can make the TokenMetadataV1 API return incorrect values and cause flaky tests.
	totalSupply := new(big.Float)
	switch tokenConfig.Type {
	case config.TokenTypeLINK:
		// Iterate over all LINK holdings and sum them up
		for i, holding := range holdings {

integration-tests/ccip/ccip_send_with_token_bnm_test.go:1091

  • Same issue as above: the second TokenMetadata query dereferences JSON200/TotalSupply pointers without nil checks, which can panic if the response body isn't decoded into JSON200.
	// Query EDS' TokenMetadata API again, checking that totalSupply has decreased
	linkMetadata, err = tokenMetadataAPIClient.GetInstrumentWithResponse(t.Context(), string(linkInstrumentId.Id))
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, linkMetadata.StatusCode(), "expected 200 OK from TokenMetadata API for LINK instrument")
	t.Logf("Queried Token Metadata: id: %v, symbol: %v, name: %v, totalSupply: %v, totalSupplyAsOf: %v",
		linkMetadata.JSON200.Id,
		linkMetadata.JSON200.Symbol,
		linkMetadata.JSON200.Name,
		*linkMetadata.JSON200.TotalSupply,
		*linkMetadata.JSON200.TotalSupplyAsOf,
	)
	// Started with 50 LINK
	// - 1 LINK bridged
	// + 0.05 LINK pool feeBps retained by sender's pool (poolOwner == sender)
	// = 49.05 LINK remaining in total supply
	require.Equal(t, "49.05", *linkMetadata.JSON200.TotalSupply)

eds/internal/api/token_standard/token_standard.go:267

  • getTotalSupplyForInstrument holds tokenSupplyMux for the entire cache-miss calculation (including listing/cloning all holdings). This serializes concurrent requests across all instruments and can significantly reduce throughput under load; the lock should only guard cache reads/writes, not the expensive computation.
	s.tokenSupplyMux.Lock()
	defer s.tokenSupplyMux.Unlock()

	// Check cache first
	cachedSupply, ok := s.tokenSupplies[instrumentId]
	if ok && time.Since(cachedSupply.UpdatedAt) < s.tokenSupplyCacheTimeout {
		return cachedSupply, nil
	}

eds/internal/store/active_contract_store.go:283

  • onArchivedEvent deletes contract IDs from the nested template maps but never removes now-empty maps. This can leave empty entries such that GetByTemplateId returns (emptySlice, true), which contradicts the method comment ('if any exist') and forces all callers to defensively check len().
		// Delete by PackageId
		contractsByPackageId := contractsForParty[contracts.TemplateID{
			PackageID:  archivedEvent.GetTemplateId().GetPackageId(),
			ModuleName: archivedEvent.GetTemplateId().GetModuleName(),
			EntityName: archivedEvent.GetTemplateId().GetEntityName(),
		}]
		delete(contractsByPackageId, types.CONTRACT_ID(archivedEvent.GetContractId()))
		// Delete by PackageName
		contractsByPackageName := contractsForParty[contracts.TemplateID{
			PackageID:  fmt.Sprintf("#%s", archivedEvent.GetPackageName()),
			ModuleName: archivedEvent.GetTemplateId().GetModuleName(),
			EntityName: archivedEvent.GetTemplateId().GetEntityName(),
		}]
		delete(contractsByPackageName, types.CONTRACT_ID(archivedEvent.GetContractId()))

integration-tests/ccip/ccip_send_with_token_bnm_test.go:779

  • The test dereferences linkMetadata.JSON200 / TotalSupply / TotalSupplyAsOf without nil checks. OpenAPI clients can return StatusCode==200 with JSON200==nil if decoding fails (e.g., unexpected content-type), which would panic and make this test harder to diagnose. Also, a 1s timestamp tolerance is likely flaky under CI load.

This issue also appears on line 1076 of the same file.

	// Query EDS' TokenMetadata API
	linkMetadata, err := tokenMetadataAPIClient.GetInstrumentWithResponse(t.Context(), string(linkInstrumentId.Id))
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, linkMetadata.StatusCode(), "expected 200 OK from TokenMetadata API for LINK instrument")
	t.Logf("Queried Token Metadata: id: %v, symbol: %v, name: %v, totalSupply: %v, totalSupplyAsOf: %v",
		linkMetadata.JSON200.Id,
		linkMetadata.JSON200.Symbol,
		linkMetadata.JSON200.Name,
		*linkMetadata.JSON200.TotalSupply,
		*linkMetadata.JSON200.TotalSupplyAsOf,
	)
	require.Equal(t, "50", *linkMetadata.JSON200.TotalSupply)
	require.WithinDuration(t, time.Now(), *linkMetadata.JSON200.TotalSupplyAsOf, time.Second, "expected LastUpdated to be recent")

Copilot AI review requested due to automatic review settings July 29, 2026 09:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • eds/internal/mocks/mock_ActiveContractStore.go: Generated file
  • eds/internal/mocks/mock_InstrumentHoldingStore.go: Generated file
Comments suppressed due to low confidence (3)

eds/internal/api/token_standard/token_standard.go:297

  • Locked LINK holdings are summed without checking lockedInstrumentId, so if multiple LINK instruments are configured, the total supply for each instrument will incorrectly include all locked holdings. Also the GetByTemplateId boolean result is discarded, making it harder to distinguish "none" from "not tracked".
		// List LockLinkHoldings to also sum up
		lockedHoldings, _ := s.activeContractStore.GetByTemplateId(s.admin, contracts.TemplateIDFromBinding(link.LockedLinkHolding{}))
		for _, activeContract := range lockedHoldings {
			parsedLockedHolding, err := bindings.UnmarshalCreatedEvent[link.LockedLinkHolding](activeContract.GetCreatedEvent())
			if err != nil {

eds/internal/api/token_standard/token_standard.go:276

  • ListHoldings returns an error, but it is currently ignored. If the holding store fails (e.g., stream not ready / backend error), this will silently produce an incorrect total supply and still cache it.
	holdings, _ := s.instrumentHoldingStore.ListHoldings(splice_api_token_holding_v1.InstrumentId{
		Admin: s.admin,
		Id:    types.TEXT(instrumentId),
	})

eds/internal/api/token_standard/token_standard.go:266

  • getTotalSupplyForInstrument holds tokenSupplyMux for the entire calculation (including store reads and protobuf unmarshalling). This serializes all token metadata requests and can become a bottleneck as holdings grow; consider locking only around cache read/write (compute outside the critical section) or using an RWMutex.
	s.tokenSupplyMux.Lock()
	defer s.tokenSupplyMux.Unlock()

	// Check cache first
	cachedSupply, ok := s.tokenSupplies[instrumentId]
	if ok && time.Since(cachedSupply.UpdatedAt) < s.tokenSupplyCacheTimeout {
		return cachedSupply, nil

Copilot AI review requested due to automatic review settings July 29, 2026 09:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated 1 comment.

Files not reviewed (2)
  • eds/internal/mocks/mock_ActiveContractStore.go: Generated file
  • eds/internal/mocks/mock_InstrumentHoldingStore.go: Generated file
Comments suppressed due to low confidence (3)

eds/internal/api/token_standard/token_standard.go:276

  • The error return from instrumentHoldingStore.ListHoldings is ignored, which can silently produce an incorrect totalSupply (e.g., if the store fails). Propagate the error so the handler can return 500 as intended.
	holdings, _ := s.instrumentHoldingStore.ListHoldings(splice_api_token_holding_v1.InstrumentId{
		Admin: s.admin,
		Id:    types.TEXT(instrumentId),
	})

eds/internal/api/token_standard/token_standard.go:319

  • The local variable name tokenSupply := tokenSupply{...} shadows the tokenSupply type name, making the code harder to read and grep. Rename the variable to avoid shadowing.
	// Cache totalSupply
	tokenSupply := tokenSupply{
		UpdatedAt:   snapshotTime,
		TotalSupply: totalSupply.FloatString(10), // 10 decimal places
	}
	s.tokenSupplies[instrumentId] = tokenSupply

eds/internal/api/token_standard/token_standard_test.go:153

  • This comment is misleading: the test mocks TotalSupply to be 100.0 (the mocked LockedLinkHolding is for instrumentId "LINK" and is explicitly ignored), not "100+1+2".
		for _, instrument := range resp.JSON200.Instruments {
			// Validate that TotalSupply is calculated for each instrument (mocked to be 100+1+2)
			require.NotNil(t, instrument.TotalSupply)
			require.Equal(t, "100.0000000000", *instrument.TotalSupply)

Comment thread eds/internal/api/token_standard/token_standard.go
Copilot AI review requested due to automatic review settings July 29, 2026 10:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • eds/internal/mocks/mock_ActiveContractStore.go: Generated file
  • eds/internal/mocks/mock_InstrumentHoldingStore.go: Generated file
Comments suppressed due to low confidence (2)

eds/internal/api/token_standard/token_standard.go:275

  • ListHoldings returns an error but it’s currently ignored; failures will silently produce an incorrect TotalSupply (likely 0) and mask underlying store/stream issues.
	holdings, _ := s.instrumentHoldingStore.ListHoldings(splice_api_token_holding_v1.InstrumentId{
		Admin: s.admin,
		Id:    types.TEXT(instrumentId),
	})

eds/internal/api/token_standard/token_standard.go:224

  • nextPageToken = new(strconv.Itoa(endIndex)) does not compile in Go (the built-in new expects a type, not a value). Create a local string and take its address instead.
	var nextPageToken *string
	if endIndex < len(sortedTokenConfigs) {
		nextPageToken = new(strconv.Itoa(endIndex))
	}

@friedemannf friedemannf changed the title EDS - Add TokenStandard TokenMetadataV1 implementation EDS - Add TokenStandard TokenMetadataV1 implementation (NONEVM-4878) Jul 29, 2026
@friedemannf
friedemannf marked this pull request as ready for review July 30, 2026 17:15
@friedemannf
friedemannf requested a review from a team as a code owner July 30, 2026 17:15
mux sync.RWMutex
contractsByInstanceAddress map[contracts.InstanceAddress]*apiv2.ActiveContract
contractsByTemplateId map[types.PARTY]map[contracts.TemplateID]*apiv2.ActiveContract
contractsByTemplateId map[types.PARTY]map[contracts.TemplateID]map[types.CONTRACT_ID]*apiv2.ActiveContract

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Adding an additional map keyed by ContractId here. Previously, only a single contract of each type would have been stored by party, but we need the ability to list all contracts of a given type.

// Party -> InstrumentId -> ContractId -> Holding
holdings map[types.PARTY]map[splice_api_token_holding_v1.InstrumentId]map[types.CONTRACT_ID]*apiv2.ActiveContract
// InstrumentId -> Party -> ContractId -> Holding
holdings map[splice_api_token_holding_v1.InstrumentId]map[types.PARTY]map[types.CONTRACT_ID]*apiv2.ActiveContract

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reordering these maps to have the InstrumentId at the highest level. There will be more parties than there will be different instruments, and listing will be done per-instrument, not per-party.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants