Skip to content

Server API

Loop SDK also supports a server-side signing flow. Instead of asking the user to approve each action in the wallet UI, your backend signs and submits transactions directly using the user's private key.

Important: this flow requires access to the user's private key. Party ID + public key alone is not enough.

Install the SDK

Follow the same step in Install SDK to install the SDK.

Secondly, add node-forge to your project dependencies.

Now, you're ready to use the Loop SDK to sign from your server instead of from a browser dApp.

If you just want a quick example, look at demo/server.ts.

1. Initialize the SDK

Call loop.init() once when your application starts:

import { loop } from '@fivenorth/loop-sdk/server';

loop.init({
    privateKey: process.env.PRIVATE_KEY,
    partyId: process.env.PARTY_ID,
    network: 'local',
});

Parameters

Field Description
privateKey Private key in hex format (exported from Loop wallet UI)
partyId Your party ID
network local, devnet, or mainnet

2. Authenticate yourself

Ideally do this once when your application boots. After init, authenticate with the Loop backend:

await loop.authenticate()

Upon successful authentication, you will have two objects: signer and provider, accessible via getSigner() and getProvider().

Most of the time you won't need them directly and can use the high-level loop.executeTransaction() flow instead.


3. Submit a DAML transaction (simple)

With the signer and provider ready, you can submit any DAML transaction:

await loop.executeTransaction({
  commands: [
    {
      ExerciseCommand: {
        templateId: 'template',
        contractId: 'contractid',
        choice: 'choice',
        choiceArgument: {
          arg1: 'val1',
        },
      },
    },
  ],
  disclosedContracts: [],
});

And that's all

4. Using the Provider (advanced)

For more granular control over transaction submission, you can use the provider object directly. This allows you to integrate your own signing mechanism instead of the SDK signer.

The process involves two steps:

  1. prepareSubmission: This step sends the transaction payload to the server and returns a prepared payload which includes a transaction hash.
  2. executeSubmission: This step takes the prepared payload and a signature of the transaction hash and submits it to the ledger.

Here is an example of how to use the provider to submit a transaction:

import { loop } from '@fivenorth/loop-sdk/server';

// Initialize and authenticate loop first
// ...

// Get provider and signer
const provider = loop.getProvider();
const signer = loop.getSigner();

// 1. Prepare the transaction
const preparedPayload = await provider.prepareSubmission({
  commands: [
    {
      ExerciseCommand: {
        templateId: 'template',
        contractId: 'contractid',
        choice: 'choice',
        choiceArgument: {
          arg1: 'val1',
        },
      },
    },
  ],
  disclosedContracts: [],
});

// 2. Sign the transaction hash from the prepared payload
// The transaction_hash is a base64 encoded string
const signedTransactionHash = signer.signTransactionHash(preparedPayload.transaction_hash);

// 3. Execute the transaction
const submissionResponse = await provider.executeSubmission({
    command_id: preparedPayload.command_id,
    transaction_data: preparedPayload.transaction_data,
    signature: signedTransactionHash,
});

console.log('Transaction submitted:', submissionResponse);

Methods

provider.prepareSubmission(payload: TransactionPayload)

Prepares a transaction for submission.

  • payload: The DAML transaction payload.
  • Returns: A Promise that resolves to a PreparedSubmissionResponse object, which contains transaction_hash, command_id, and transaction_data.

provider.executeSubmission(payload: ExecuteSubmissionRequest)

Executes a prepared transaction.

  • payload: An object containing:
    • command_id: The command ID from the prepared response.
    • transaction_data: The transaction data from the prepared response.
    • signature: The signature of the transaction_hash.
  • Returns: A Promise that resolves to the submission response.

Examples

These are high-level examples to show what the SDK enables. The key point: you can execute any DAML transaction, not just transfers.

Common server-SDK flow for all examples: 1. loop.init({ privateKey, partyId, ... }) 2. await loop.authenticate() (creates a server-SDK JWT via /pair/apikey) 3. Build a DAML command payload 4. await loop.executeTransaction(payload) (prepare → sign → execute)

Example ideas: 1. List pending transfers
Functions: loop.getProvider(), provider.getActiveContracts()
Use getActiveContracts() with the transfer instruction template or interface ID to list pending transfer contracts. This is a read call, no signing required.

  1. Accept a pending transfer
    Functions: loop.executeTransaction()
    Build an ExerciseCommand that accepts the transfer instruction contract and submit it with loop.executeTransaction().

If you can express it as a DAML transaction, you can submit it through the SDK.


Security notes

  • The server flow only works if you can access the user's private key. This is a custody decision.
  • Rate limit: server-side signing requests are limited to 1 request per minute (1 RPM).