Token Standard APIs

Overview

Wallet integration with Token Standard Assets

This section provides wallet developers with guidance on how to integrate with token standard assets. Such an integration works by sending the right read and write requests to the Ledger API of the validator node hosting the wallet user’s party. There are four kinds of integration patterns:

All of these integration patterns are demonstrated in the form of executable code as part of the experimental command-line interface for token standard assets. The sections below explaining the patterns below thus all start with a link to the code. They then provide additional context for an implementor.

All interaction works via the JSON Ledger API (see its OpenAPI definition here). This OpenAPI definition is also accessible at http(s)://${YOUR_PARTICIPANT}/docs/openapi. We encourage developers to use OpenAPI code generation tools as opposed to manually writing HTTP requests.

Check out the Authentication docs for more information on how to authenticate the requests.

Reading contracts implementing a Token Standard interface for a party

Reference code from the Token Standard CLI to list contracts by interface

The Token Standard includes several interfaces that are implemented by Daml templates. To list all contracts implementing a particular interface, you have to query the participant’s active-contracts endpoint.

The activeAtOffset parameter can be set to the result of the ledger-end endpoint on the participant to get the latest ACS, or an older (non-pruned) one to get the ACS at that point in time.

To filter for a particular party and interface, it should include a filtersByParty with an InterfaceFilter:

{
  "filtersByParty": {
    "$A_PARTY": {
      "cumulative": [
        {
          "identifierFilter": {
            "InterfaceFilter": {
              "value": {
                "interfaceId": "$AN_INTERFACE_ID",
                "includeInterfaceView": true,
                "includeCreatedEventBlob": true
              }
            }
          }
        }
      ]
    }
  }
}

For example:

  • "$A_PARTY" could look like test::1220a0db3761b3fc919b55e7ff80ad740824336010bfde8829611c0e64477ab7bee5.

  • "$AN_INTERFACE_ID" could be #splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding.

Additionally, there’s three flags that can be set:

  • includeInterfaceView: to include the interface view of the contract in the response.

  • includeCreatedEventBlob: to include a binary blob that is required for explicit disclosure.

  • verbose: to include additional information in the response.

The response for such a query will contain the createdEvent of the contract, including the interface views requested (if any). The viewValue within it will be the JSON-serialized Daml interface view. If more than one interface is requested, you can distinguish them by checking the interfaceId field. You can find an example response for Holdings here.

Reading and parsing transaction history involving Token Standard contracts

Example code: Token Standard CLI’s code to list transactions

The participant has an endpoint to list all transactions involving the provided parties and interfaces.

To filter for a particular party and interface, it should include a filtersByParty with an InterfaceFilter:

{
  "filtersByParty": {
    "$A_PARTY": {
      "cumulative": [
        {
          "identifierFilter": {
            "InterfaceFilter": {
              "value": {
                "interfaceId": "$AN_INTERFACE_ID",
                "includeInterfaceView": true,
                "includeCreatedEventBlob": true
              }
            }
          }
        }
      ]
    }
  }
}

For example:

  • "$A_PARTY" could look like test::1220a0db3761b3fc919b55e7ff80ad740824336010bfde8829611c0e64477ab7bee5.

  • "$AN_INTERFACE_ID" could be #splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding to read all Holding contracts of the specified party.

To include other transaction nodes that don’t directly involve the interfaces (e.g., non-interface-specific children nodes), a WildcardFilter can be included in the cumulative filter array:

{
  "identifierFilter": {
    "WildcardFilter": {
      "value": {
        "includeCreatedEventBlob": true
      }
    }
  }
}

The beginExclusive field is the offset from which to start reading transactions. To paginate, you can start with the participantPrunedUpToInclusive from GET ${PARTICIPANT_URL}/v2/state/latest-pruned-offsets and continue by passing the offset of the last transaction from the previous response.

Parsing the history

Example code: the parser here. It extracts a user-readable wallet history by parsing transactions involving the Holding and TransferInstruction interfaces.

The endpoint returns transaction trees as an array. The transactions are ordered as they occur in the ledger. Given an ExercisedEvent with nodeId=X and lastDescendantNodeId=Y, the children of that node are those with nodeId in the range [X+1, Y]. CreatedEvent and ArchivedEvent (or equivalently, ExercisedEvent where consuming=true) do not have children.

Given the above, a tree-like traversal can be performed on the transaction nodes. Generally, a Token Standard parser will focus on the exercise of Token Standard choices and creation of contracts implementing Token Standard interfaces. Where further customization is required, a parser can decide to also focus on internal/specific choices that are not available in the standard, but in some specific implementation.

In each Token Standard exercise node, one can find:

  • The choice being executed, useful to distinguish what operation was performed.

  • As part of the archival/creation of children, one can find out other relevant operations that happened. For example, creation or archival of Holdings.

  • Meta key/values, of which part of the standard:

    • splice.lfdecentralizedtrust.org/tx-kind: the kind of operation happening in the node. This can give more information than the exercised choice does. It can be one of:

      • transfer

      • merge-split

      • burn

      • mint

      • unlock

      • expire-dust

    • splice.lfdecentralizedtrust.org/sender: which party is the sender in the node.

    • splice.lfdecentralizedtrust.org/reason: a text specifying the reason for the operation in the node.

    • splice.lfdecentralizedtrust.org/burned: how much of a holding was burned in the node.

Warning

Meta key/values can be specified in several optional fields. For transfers, the values from fields that are present should be merged in last-write-wins order of:

  • event.choiceArgument.transfer.meta,

  • event.choiceArgument.extraArgs.meta,

  • event.choiceArgument.meta,

  • event.exerciseResult.meta,

Executing a factory choice

Example code: Token Standard CLI’s code to create a transfer via TransferFactory

To execute a choice via a Token Standard factory, first you need need to fetch the factory from the corresponding registry.

Note

The mapping from an instrument’s admin party-id to the corresponding registry URL needs to be maintained currently by wallets themselves, until a generic solution (likely based on CNS) is implemented.

The registry will return the relevant factory in the corresponding endpoint:

The response’s payload will include three relevant fields: * factoryId: the contract id of the factory * disclosedContracts: must be provided to the exercise of the factory’s choice for it to work

With this data, you can execute a choice on the factory. For external parties you must call the prepare and execute endpoints of the participant. For non-external parties, you can just use the submit-and-wait endpoint.

In both cases, you must include an ExerciseCommand in your payload with the following fields:

  • templateId: the interface id of the factory you want to exercise the choice on. For example, #splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory.

  • contractId: the factoryId obtained from the registry.

  • choice: the name of the choice you want to execute. For example, TransferFactory_Transfer.

  • choiceArgument: the arguments that will be passed to the Daml choice. These will be decoded from JSON. For a TransferFactory_Transfer, this will include for example the sender, receiver and amount, among other fields.

Executing a non-factory choice

Example code: Token Standard CLI’s code to accept a transfer instruction

To execute a choice on a contract implementing a Token Standard interface for external parties, you must call the prepare and execute endpoints of the participant. For non-external parties, you can just use the submit-and-wait endpoint.

In both cases, you must include an ExerciseCommand in your payload with the following fields:

  • templateId: the interface id of the contract you want to exercise the choice on. For example, #splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferInstruction.

  • contractId: the contract id of the contract you want to exercise the choice on. Typically, you’ll get this from the current ACS of a party.

  • choice: the name of the choice you want to execute. For example, TransferInstruction_Accept.

  • choiceArgument: the arguments that will be passed to the Daml choice. These will be decoded from JSON.

Where a context is required as part of the choiceArgument, it can be fetched from the corresponding registry:

The response of these endpoints include two fields:

  • choiceContextData: to be passed as context in the choiceArgument.

  • disclosedContracts: to be passed in the submit or prepare request.

Warning

Note that AllocationRequest_Reject and AllocationRequest_Withdraw should be called with an empty choice context. This ChoiceContext is present to allow for potential future extensions of the behavior of implementations of these choices.

API References

Refer to CIP-0056 for more context on the APIs.

Token Metadata

Holding

This allows implementation of a Portfolio View.

Transfer Instruction

This allows implementation of Direct Peer-to-Peer / Free of Payment (FOP) Transfers.

Allocation

This allows implementation of Delivery versus Payment (DVP) Transfer Workflows, jointly with the Allocation Instruction and Allocation Request APIs below.

Allocation Instruction

Allocation Request

Comments