@fivenorth/loop-sdk 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +25 -8
  2. package/dist/index.js +89 -37
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -142,6 +142,7 @@ try {
142
142
  const result = await provider.submitTransaction(damlCommand, {
143
143
  // Optional: show a custom message in the wallet prompt
144
144
  message: 'Transfer 10 CC to RetailStore',
145
+ estimateTraffic: true, // optional: return estimated traffic in submission response
145
146
  });
146
147
  console.log('Transaction successful:', result);
147
148
  } catch (error) {
@@ -149,7 +150,23 @@ try {
149
150
  }
150
151
  ```
151
152
 
152
- Transaction responses include `command_id`, `submission_id`, `transaction_data`, and `update_id` when available. For token transfers, `update_id` may arrive later (once indexed), in which case `onTransactionUpdate` fires with the finalized `update_id`. Non-transfer transactions do not receive an `update_id` via this mechanism.
153
+ `onTransactionUpdate` fires once per transaction with a single payload that includes `command_id` and `submission_id`. On success it also includes `update_id` and `update_data` (ledger transaction tree); on failure it includes `status: "failed"` and `error.error_message`.
154
+
155
+ `submitTransaction` is the default async path. It returns the submission result first (including `command_id` and `submission_id`), then the ledger update arrives later via `onTransactionUpdate` with `update_id` and `update_data`.
156
+
157
+ To wait for the transaction result directly (opt-in), use:
158
+
159
+ ```javascript
160
+ await provider.submitAndWaitForTransaction(damlCommand, {
161
+ message: 'Transfer 10 CC to RetailStore',
162
+ });
163
+ ```
164
+
165
+ In wait mode, the final result is returned as a single `onTransactionUpdate` payload (command/submission IDs plus update data or failure status).
166
+
167
+ Note: `submitAndWaitForTransaction` errors do not always mean the transaction failed. A 4xx error (e.g., 400) indicates a definite failure. A 5xx/timeout can mean the ledger is slow or backed up; the transaction may still be committed later, so clients should continue to listen for updates rather than assume failure.
168
+
169
+ Deduplication: both async execute and execute-and-wait use a 1 hour deduplication window. If you retry within that window, resubmit the same `command_id` and `submission_id` so the request is idempotent.
153
170
 
154
171
  #### Sign a Message
155
172
 
@@ -170,18 +187,19 @@ try {
170
187
  ```javascript
171
188
  await loop.wallet.transfer(
172
189
  'receiver::fingerprint',
173
- '5', // amount (string or number)
190
+ '5',
174
191
  {
175
- // Optional overrides. Defaults to Amulet/DSO if omitted.
176
- instrument_admin: 'issuer::fingerprint', // optional
177
- instrument_id: 'Amulet', // optional
192
+ instrument_admin: 'issuer::fingerprint', // optional: DSO (default)
193
+ instrument_id: 'Amulet', // optional: Amulet (default)
178
194
  },
179
195
  {
180
- // Optional: show a custom message in the wallet prompt
181
- message: 'Send 5 CC to Alice',
196
+ message: 'Send 5 CC to Alice', // optional: show a custom message in the wallet prompt
197
+ memo: 'optional memo for the transfer', // optional: stored as transfer metadata
198
+ executionMode: 'wait', // optional: 'async' (default) or 'wait'
182
199
  requestedAt: new Date().toISOString(), // optional
183
200
  executeBefore: new Date(Date.now() + 24*60*60*1000).toISOString(), // optional
184
201
  requestTimeout: 5 * 60 * 1000, // optional (ms), defaults to 5 minutes
202
+ estimateTraffic: true, // optional: return estimated traffic in submission response
185
203
  },
186
204
  );
187
205
  ```
@@ -189,7 +207,6 @@ await loop.wallet.transfer(
189
207
  Notes:
190
208
  - You must have spendable holdings for the specified instrument (admin + id). If left blank, the SDK defaults to the native token.
191
209
  - The helper handles fetching holdings, building the transfer factory payload, and submitting via Wallet Connect.
192
- - Requests time out after 5 minutes by default; override with `requestTimeout` in milliseconds.
193
210
 
194
211
  Common instrument overrides (pass into the `instrument` argument above):
195
212
 
package/dist/index.js CHANGED
@@ -2204,6 +2204,9 @@ class Connection {
2204
2204
  if (params.execute_before) {
2205
2205
  payload.execute_before = params.execute_before;
2206
2206
  }
2207
+ if (params.memo) {
2208
+ payload.memo = params.memo;
2209
+ }
2207
2210
  const response = await fetch(`${this.apiUrl}/api/v1/.connect/pair/transfer`, {
2208
2211
  method: "POST",
2209
2212
  headers: {
@@ -2375,7 +2378,11 @@ class Provider {
2375
2378
  }
2376
2379
  handleResponse(message) {
2377
2380
  console.log("Received response:", message);
2378
- if (message?.type === "transaction_completed" /* TRANSACTION_COMPLETED */ && message?.payload?.update_id) {
2381
+ if (message?.type === "transaction_completed" /* TRANSACTION_COMPLETED */ && (message?.payload?.update_id || message?.payload?.update_data || message?.payload?.status)) {
2382
+ if (message?.payload?.error_message) {
2383
+ message.payload.error = { error_message: message.payload.error_message };
2384
+ delete message.payload.error_message;
2385
+ }
2379
2386
  this.hooks?.onTransactionUpdate?.(message.payload, message);
2380
2387
  }
2381
2388
  if (message.request_id) {
@@ -2392,11 +2399,16 @@ class Provider {
2392
2399
  return this.connection.getActiveContracts(this.auth_token, params);
2393
2400
  }
2394
2401
  async submitTransaction(payload, options) {
2395
- return this.sendRequest("run_transaction" /* RUN_TRANSACTION */, payload, options);
2402
+ const requestPayload = options?.estimateTraffic ? { ...payload, estimate_traffic: true } : payload;
2403
+ return this.sendRequest("run_transaction" /* RUN_TRANSACTION */, requestPayload, options);
2404
+ }
2405
+ async submitAndWaitForTransaction(payload, options) {
2406
+ const requestPayload = options?.estimateTraffic ? { ...payload, estimateTraffic: true } : payload;
2407
+ return this.sendRequest("run_transaction" /* RUN_TRANSACTION */, { ...requestPayload, execution_mode: "wait" }, options);
2396
2408
  }
2397
2409
  async transfer(recipient, amount, instrument, options) {
2398
2410
  const amountStr = typeof amount === "number" ? amount.toString() : amount;
2399
- const { requestedAt, executeBefore, requestTimeout } = options || {};
2411
+ const { requestedAt, executeBefore, requestTimeout, estimateTraffic, memo } = options || {};
2400
2412
  const message = options?.message;
2401
2413
  const resolveDate = (value, fallbackMs) => {
2402
2414
  if (value instanceof Date) {
@@ -2422,15 +2434,19 @@ class Provider {
2422
2434
  requested_at: requestedAtIso,
2423
2435
  execute_before: executeBeforeIso
2424
2436
  };
2437
+ if (memo) {
2438
+ transferRequest.memo = memo;
2439
+ }
2425
2440
  const preparedPayload = await this.connection.prepareTransfer(this.auth_token, transferRequest);
2426
- return this.submitTransaction({
2441
+ const submitFn = options?.executionMode === "wait" ? this.submitAndWaitForTransaction.bind(this) : this.submitTransaction.bind(this);
2442
+ return submitFn({
2427
2443
  commands: preparedPayload.commands,
2428
2444
  disclosedContracts: preparedPayload.disclosedContracts,
2429
2445
  packageIdSelectionPreference: preparedPayload.packageIdSelectionPreference,
2430
2446
  actAs: preparedPayload.actAs,
2431
2447
  readAs: preparedPayload.readAs,
2432
2448
  synchronizerId: preparedPayload.synchronizerId
2433
- }, { requestTimeout, message });
2449
+ }, { requestTimeout, message, estimateTraffic });
2434
2450
  }
2435
2451
  async signMessage(message) {
2436
2452
  return this.sendRequest("sign_raw_message" /* SIGN_RAW_MESSAGE */, message);
@@ -2942,28 +2958,31 @@ class LoopSDK {
2942
2958
  .loop-connect {
2943
2959
  position: fixed;
2944
2960
  inset: 0;
2945
- background: oklch(0.222 0 0 / 0.85);
2961
+ background: rgba(0, 0, 0, 0.85);
2946
2962
  backdrop-filter: blur(8px);
2947
2963
  display: flex;
2948
2964
  justify-content: center;
2949
2965
  align-items: center;
2950
2966
  z-index: 10000;
2951
- font-family: system-ui, -apple-system, sans-serif;
2967
+ font-family: "Inter", system-ui, -apple-system, sans-serif;
2952
2968
  animation: fadeIn 0.2s ease-out;
2953
2969
  }
2954
2970
  .loop-connect dialog {
2955
2971
  position: relative;
2956
2972
  overflow: hidden;
2957
- background: oklch(0.253 0.008 274.6);
2958
- box-shadow: 0 4px 24px oklch(0 0 0 / 0.1);
2959
- border: 1px solid oklch(0.41 0.01 278.4);
2960
- border-radius: 32px;
2961
- padding: 24px;
2973
+ background: #080808;
2974
+ box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.5);
2975
+ border-radius: 40px;
2976
+ border: none;
2977
+ width: 340px;
2978
+ height: 534px;
2979
+ box-sizing: border-box;
2980
+ padding: 32px;
2962
2981
  display: flex;
2963
2982
  flex-direction: column;
2964
2983
  align-items: center;
2965
- gap: 16px;
2966
- color: oklch(0.975 0.005 280);
2984
+ gap: 0;
2985
+ color: #ffffff;
2967
2986
  }
2968
2987
  .loop-connect .bg-logo {
2969
2988
  position: absolute;
@@ -2975,56 +2994,89 @@ class LoopSDK {
2975
2994
  pointer-events: none;
2976
2995
  }
2977
2996
  .loop-connect h3 {
2997
+ position: absolute;
2998
+ top: 32px;
2999
+ left: 32px;
3000
+ right: 32px;
2978
3001
  margin: 0;
2979
3002
  font-size: 18px;
2980
- font-weight: 600;
2981
- letter-spacing: -0.015em;
3003
+ font-weight: 700;
3004
+ line-height: 27px;
3005
+ letter-spacing: -0.45px;
3006
+ text-align: center;
2982
3007
  }
2983
3008
  .loop-connect figure {
3009
+ position: absolute;
3010
+ top: 91px;
3011
+ left: 32px;
3012
+ width: 276px;
3013
+ height: 276px;
2984
3014
  margin: 0;
2985
- background: oklch(1 0 0);
2986
- padding: 8px;
2987
- border-radius: 24px;
3015
+ background: #ffffff;
3016
+ padding: 20px;
3017
+ border-radius: 8px;
2988
3018
  display: flex;
2989
3019
  justify-content: center;
2990
- border: 2px solid oklch(0.41 0.01 278.4);
2991
- box-shadow: 0 4px 24px oklch(0 0 0 / 0.1);
3020
+ border: none;
3021
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1);
3022
+ box-sizing: border-box;
2992
3023
  }
2993
3024
  .loop-connect img {
2994
3025
  display: block;
2995
- width: 225px;
2996
- height: 225px;
3026
+ width: 236px;
3027
+ height: 236px;
3028
+ object-fit: contain;
3029
+ border-radius: 12px;
2997
3030
  }
2998
3031
  .loop-connect .divider {
2999
- width: 100%;
3032
+ position: absolute;
3033
+ top: 399px;
3034
+ left: 36px;
3035
+ right: 36px;
3036
+ width: auto;
3000
3037
  display: flex;
3001
3038
  align-items: center;
3002
- gap: 16px;
3003
- color: oklch(0.554 0.012 280.3);
3004
- font-size: 13px;
3005
- font-weight: 600;
3039
+ justify-content: center;
3040
+ gap: 12px;
3041
+ color: #64748b;
3042
+ font-size: 11px;
3043
+ font-weight: 700;
3044
+ letter-spacing: 0.15em;
3045
+ text-transform: uppercase;
3046
+ text-align: center;
3006
3047
  }
3007
3048
  .loop-connect .divider::before,
3008
3049
  .loop-connect .divider::after {
3009
3050
  content: "";
3010
3051
  flex: 1;
3011
3052
  height: 1px;
3012
- background: oklch(0.45 0.01 278);
3053
+ background: #1e293b;
3013
3054
  }
3014
3055
  .loop-connect button {
3015
- background: oklch(0.976 0.101 112.3);
3016
- border: 1px solid oklch(0.82 0.16 110);
3017
- color: oklch(0.222 0 0);
3018
- padding: 16px 32px;
3019
- border-radius: 24px;
3056
+ position: absolute;
3057
+ top: 447.5px;
3058
+ left: 32px;
3059
+ right: 32px;
3060
+ background: #f2ff96;
3061
+ border: none;
3062
+ color: #0f172a;
3063
+ text-align: center;
3064
+ font-family: "Inter", system-ui, -apple-system, sans-serif;
3065
+ font-style: normal;
3066
+ padding: 0 24px;
3067
+ border-radius: 8px;
3020
3068
  font-size: 15px;
3021
3069
  font-weight: 600;
3070
+ line-height: 22.5px;
3022
3071
  cursor: pointer;
3023
3072
  transition: all 0.2s ease;
3024
- width: 100%;
3073
+ width: auto;
3074
+ height: 54.5px;
3075
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2),
3076
+ 0 4px 6px -4px rgba(0, 0, 0, 0.2);
3025
3077
  }
3026
3078
  .loop-connect button:hover {
3027
- background: oklch(0.98 0.105 112.5);
3079
+ background: #f6ffb4;
3028
3080
  }
3029
3081
  @keyframes fadeIn {
3030
3082
  from { opacity: 0; }
@@ -3035,7 +3087,7 @@ class LoopSDK {
3035
3087
  }
3036
3088
  showQrCode(url) {
3037
3089
  this.injectModalStyles();
3038
- import_qrcode.default.toDataURL(url, (err, dataUrl) => {
3090
+ import_qrcode.default.toDataURL(url, { margin: 0 }, (err, dataUrl) => {
3039
3091
  if (err) {
3040
3092
  console.error("Failed to generate QR code", err);
3041
3093
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fivenorth/loop-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "author": "support@fivenorth.io",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",