@layerzerolabs/protocol-stellar-v2 0.2.60 → 0.2.61

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/sdk/test/utils.ts CHANGED
@@ -167,7 +167,6 @@ import { Secp256k1KeyPair } from './secp256k1';
167
167
  * - expiration: u64 - Ledger timestamp for when auth expires
168
168
  * - signatures: Vec<BytesN<65>> - Secp256k1 signatures from multisig signers
169
169
  * - sender: Sender - Either None or Admin(public_key, ed25519_signature)
170
- * - preimage: Bytes - XDR-encoded HashIdPreimage::SorobanAuthorization (invocation is sliced from byte 48)
171
170
  */
172
171
  export async function signDvnAuthEntries<T>(
173
172
  dvnAddress: string,
@@ -234,11 +233,15 @@ export async function signDvnAuthEntries<T>(
234
233
  adminSignature.toString('hex').slice(0, 32) + '...',
235
234
  );
236
235
 
237
- // 3. Compute the multisig hash from raw invocation XDR
238
- // hash = keccak256(vid || expiration || invocation_xdr)
236
+ // 3. Extract calls from the auth entry and compute the multisig hash
237
+ // The expiration is the ledger timestamp when the auth expires
238
+ // We use validUntilLedgerSeq * 5 as a rough approximation (5 seconds per ledger)
239
239
  const expiration = BigInt(validUntilLedgerSeq) * 5n + BigInt(Math.floor(Date.now() / 1000));
240
- const invocationXdr = Buffer.from(rootInvocation.toXDR());
241
- const callHash = computeCallHash(vid, expiration, invocationXdr);
240
+ const rootCall = getRootCall(rootInvocation);
241
+ const isSelfCall = rootCall.to === dvnAddr.toString();
242
+ const calls = isSelfCall ? [rootCall] : collectCallsFromInvocation(rootInvocation);
243
+ const callsXdr = serializeCallsToXdr(calls);
244
+ const callHash = computeCallHash(vid, expiration, callsXdr);
242
245
  console.log('📝 Call hash for multisig:', Buffer.from(callHash).toString('hex'));
243
246
 
244
247
  // 4. Sign the call hash with multisig signers
@@ -261,19 +264,16 @@ export async function signDvnAuthEntries<T>(
261
264
  const sortedSignatures = signaturesWithAddresses.map((s) => s.sig);
262
265
 
263
266
  // 6. Build TransactionAuthData as ScVal
264
- // struct TransactionAuthData { vid, expiration, signatures, sender, preimage }
265
- const preimageXdr = Buffer.from(preimage.toXDR());
267
+ // struct TransactionAuthData { vid: u32, expiration: u64, signatures: Vec<BytesN<65>>, sender: Sender }
268
+ // enum Sender { None, Admin(BytesN<32>, BytesN<64>) }
266
269
  const transactionAuthDataScVal = xdr.ScVal.scvMap([
267
270
  new xdr.ScMapEntry({
268
271
  key: xdr.ScVal.scvSymbol('expiration'),
269
272
  val: xdr.ScVal.scvU64(new xdr.Uint64(expiration)),
270
273
  }),
271
- new xdr.ScMapEntry({
272
- key: xdr.ScVal.scvSymbol('preimage'),
273
- val: xdr.ScVal.scvBytes(preimageXdr),
274
- }),
275
274
  new xdr.ScMapEntry({
276
275
  key: xdr.ScVal.scvSymbol('sender'),
276
+ // Sender::Admin(public_key, signature)
277
277
  val: xdr.ScVal.scvVec([
278
278
  xdr.ScVal.scvSymbol('Admin'),
279
279
  xdr.ScVal.scvBytes(adminKeypair.rawPublicKey()),
@@ -325,11 +325,97 @@ export async function signDvnAuthEntries<T>(
325
325
  console.log('✅ Final simulation complete');
326
326
  }
327
327
 
328
+ /**
329
+ * Represents a contract call for multisig authorization.
330
+ */
331
+ interface Call {
332
+ to: string; // Contract address
333
+ func: string; // Function name
334
+ args: xdr.ScVal[]; // Function arguments
335
+ }
336
+
337
+ /**
338
+ * Extracts only the root call from an invocation (no recursion into sub-invocations).
339
+ */
340
+ function getRootCall(invocation: xdr.SorobanAuthorizedInvocation): Call {
341
+ const fn = invocation.function();
342
+ if (
343
+ fn.switch() !== xdr.SorobanAuthorizedFunctionType.sorobanAuthorizedFunctionTypeContractFn()
344
+ ) {
345
+ throw new Error('Root invocation is not a contract function');
346
+ }
347
+ const contractFn = fn.contractFn();
348
+ return {
349
+ to: Address.fromScAddress(contractFn.contractAddress()).toString(),
350
+ func: contractFn.functionName().toString(),
351
+ args: contractFn.args(),
352
+ };
353
+ }
354
+
355
+ /**
356
+ * Collects all contract calls from an invocation tree.
357
+ */
358
+ function collectCallsFromInvocation(invocation: xdr.SorobanAuthorizedInvocation): Call[] {
359
+ const calls: Call[] = [];
360
+ collectCallsRecursive(invocation, calls);
361
+ return calls;
362
+ }
363
+
364
+ function collectCallsRecursive(invocation: xdr.SorobanAuthorizedInvocation, calls: Call[]): void {
365
+ const fn = invocation.function();
366
+
367
+ if (
368
+ fn.switch() === xdr.SorobanAuthorizedFunctionType.sorobanAuthorizedFunctionTypeContractFn()
369
+ ) {
370
+ const contractFn = fn.contractFn();
371
+ const contractAddr = Address.fromScAddress(contractFn.contractAddress());
372
+
373
+ calls.push({
374
+ to: contractAddr.toString(),
375
+ func: contractFn.functionName().toString(),
376
+ args: contractFn.args(),
377
+ });
378
+ }
379
+
380
+ // Process sub-invocations
381
+ for (const sub of invocation.subInvocations()) {
382
+ collectCallsRecursive(sub, calls);
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Serializes calls to XDR format matching Soroban's Vec<Call> serialization.
388
+ *
389
+ * Call struct: { args: Vec<Val>, func: Symbol, to: Address }
390
+ * Serialized as ScMap with keys in alphabetical order.
391
+ */
392
+ function serializeCallsToXdr(calls: Call[]): Buffer {
393
+ const callScVals = calls.map((call) =>
394
+ xdr.ScVal.scvMap([
395
+ new xdr.ScMapEntry({
396
+ key: xdr.ScVal.scvSymbol('args'),
397
+ val: xdr.ScVal.scvVec(call.args),
398
+ }),
399
+ new xdr.ScMapEntry({
400
+ key: xdr.ScVal.scvSymbol('func'),
401
+ val: xdr.ScVal.scvSymbol(call.func),
402
+ }),
403
+ new xdr.ScMapEntry({
404
+ key: xdr.ScVal.scvSymbol('to'),
405
+ val: Address.fromString(call.to).toScVal(),
406
+ }),
407
+ ]),
408
+ );
409
+
410
+ const vecScVal = xdr.ScVal.scvVec(callScVals);
411
+ return Buffer.from(vecScVal.toXDR());
412
+ }
413
+
328
414
  /**
329
415
  * Computes the call hash for multisig signing.
330
- * hash = keccak256(vid.to_be_bytes(4) || expiration.to_be_bytes(8) || invocation_xdr)
416
+ * hash = keccak256(vid.to_be_bytes(4) || expiration.to_be_bytes(8) || calls_xdr)
331
417
  */
332
- function computeCallHash(vid: number, expiration: bigint, invocationXdr: Buffer): Uint8Array {
418
+ function computeCallHash(vid: number, expiration: bigint, callsXdr: Buffer): Uint8Array {
333
419
  // vid as 4-byte big-endian
334
420
  const vidBytes = Buffer.alloc(4);
335
421
  vidBytes.writeUInt32BE(vid, 0);
@@ -338,7 +424,8 @@ function computeCallHash(vid: number, expiration: bigint, invocationXdr: Buffer)
338
424
  const expirationBytes = Buffer.alloc(8);
339
425
  expirationBytes.writeBigUInt64BE(expiration, 0);
340
426
 
341
- const data = Buffer.concat([vidBytes, expirationBytes, invocationXdr]);
427
+ // Concatenate and hash
428
+ const data = Buffer.concat([vidBytes, expirationBytes, callsXdr]);
342
429
  return keccak_256(data);
343
430
  }
344
431