@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.
- package/README.md +25 -8
- package/dist/index.js +89 -37
- 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
|
-
|
|
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',
|
|
190
|
+
'5',
|
|
174
191
|
{
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
//
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
2958
|
-
box-shadow: 0
|
|
2959
|
-
border:
|
|
2960
|
-
border
|
|
2961
|
-
|
|
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:
|
|
2966
|
-
color:
|
|
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:
|
|
2981
|
-
|
|
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:
|
|
2986
|
-
padding:
|
|
2987
|
-
border-radius:
|
|
3015
|
+
background: #ffffff;
|
|
3016
|
+
padding: 20px;
|
|
3017
|
+
border-radius: 8px;
|
|
2988
3018
|
display: flex;
|
|
2989
3019
|
justify-content: center;
|
|
2990
|
-
border:
|
|
2991
|
-
box-shadow: 0
|
|
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:
|
|
2996
|
-
height:
|
|
3026
|
+
width: 236px;
|
|
3027
|
+
height: 236px;
|
|
3028
|
+
object-fit: contain;
|
|
3029
|
+
border-radius: 12px;
|
|
2997
3030
|
}
|
|
2998
3031
|
.loop-connect .divider {
|
|
2999
|
-
|
|
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
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
font-
|
|
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:
|
|
3053
|
+
background: #1e293b;
|
|
3013
3054
|
}
|
|
3014
3055
|
.loop-connect button {
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
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:
|
|
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:
|
|
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;
|