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:
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:
prepareSubmission: This step sends the transaction payload to the server and returns a prepared payload which includes a transaction hash.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
Promisethat resolves to aPreparedSubmissionResponseobject, which containstransaction_hash,command_id, andtransaction_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 thetransaction_hash.
- Returns: A
Promisethat 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.
- Accept a pending transfer
Functions:loop.executeTransaction()
Build anExerciseCommandthat accepts the transfer instruction contract and submit it withloop.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).