Skip to content

Commit e201b43

Browse files
author
Mathéo
committed
WIP
1 parent c81cf56 commit e201b43

16 files changed

+535
-82
lines changed

client/HubApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export default class HubApi<
171171
public signTransaction<B extends BehaviorType = DB>(
172172
request: Promise<SignTransactionRequest> | SignTransactionRequest,
173173
requestBehavior: RequestBehavior<B> = this._defaultBehavior as any,
174-
): Promise<B extends BehaviorType.REDIRECT ? void : SignedTransaction> {
174+
): Promise<B extends BehaviorType.REDIRECT ? void : SignedTransaction | SignedTransaction[]> {
175175
return this._request(requestBehavior, RequestType.SIGN_TRANSACTION, [request]);
176176
}
177177

client/PublicRequestTypes.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,23 @@ export interface ChooseAddressResult extends Address {
9292
};
9393
}
9494

95-
export interface SignTransactionRequest extends BasicRequest {
95+
// Transaction data without sender address (sender address is at request level)
96+
export interface TransactionData {
97+
recipient: string;
98+
recipientType?: Nimiq.AccountType;
99+
recipientLabel?: string;
100+
value: number;
101+
fee?: number;
102+
extraData?: Bytes;
103+
flags?: number;
104+
validityStartHeight: number; // FIXME To be made optional when hub has its own network
105+
// Optional fields for staking transactions
106+
senderType?: Nimiq.AccountType;
107+
senderLabel?: string;
108+
}
109+
110+
// Single transaction request (backward compatible - inline fields)
111+
export interface SignTransactionRequestSingle extends BasicRequest {
96112
sender: string;
97113
recipient: string;
98114
recipientType?: Nimiq.AccountType;
@@ -104,6 +120,16 @@ export interface SignTransactionRequest extends BasicRequest {
104120
validityStartHeight: number; // FIXME To be made optional when hub has its own network
105121
}
106122

123+
// Multi-transaction request (array format)
124+
export interface SignTransactionRequestMulti extends BasicRequest {
125+
sender: string;
126+
senderLabel?: string; // Label for sender (for staking transactions)
127+
recipientLabel?: string; // Label for first recipient (for UI display)
128+
transactions: TransactionData[] | Uint8Array[]; // Data objects or serialized transactions
129+
}
130+
131+
export type SignTransactionRequest = SignTransactionRequestSingle | SignTransactionRequestMulti;
132+
107133
export interface SignStakingRequest extends BasicRequest {
108134
senderLabel?: string;
109135
recipientLabel?: string;
@@ -755,7 +781,7 @@ export type RpcRequest = SignTransactionRequest
755781
| ConnectAccountRequest;
756782

757783
export type RpcResult = SignedTransaction
758-
| SignedTransaction[]
784+
| SignedTransaction[] // Can be returned by SIGN_TRANSACTION (multi-tx) or SIGN_STAKING
759785
| PartialSignature
760786
| Account
761787
| Account[]
@@ -779,7 +805,7 @@ export type ResultByRequestType<T> =
779805
T extends RequestType.LIST_CASHLINKS ? Cashlink[] :
780806
T extends RequestType.CHOOSE_ADDRESS ? ChooseAddressResult :
781807
T extends RequestType.ADD_ADDRESS ? Address :
782-
T extends RequestType.SIGN_TRANSACTION ? SignedTransaction :
808+
T extends RequestType.SIGN_TRANSACTION ? SignedTransaction | SignedTransaction[] :
783809
T extends RequestType.SIGN_MULTISIG_TRANSACTION ? PartialSignature :
784810
T extends RequestType.SIGN_STAKING ? SignedTransaction[] :
785811
T extends RequestType.CHECKOUT ? SignedTransaction | SimpleResult :

client/dist/src/HubApi.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default class HubApi<DB extends BehaviorType = BehaviorType.POPUP, IB ext
3232
version: 2;
3333
} ? SimpleResult | SignedTransaction : SignedTransaction>;
3434
chooseAddress<B extends BehaviorType = DB>(request: Promise<ChooseAddressRequest> | ChooseAddressRequest, requestBehavior?: RequestBehavior<B>): Promise<B extends BehaviorType.REDIRECT ? void : ChooseAddressResult>;
35-
signTransaction<B extends BehaviorType = DB>(request: Promise<SignTransactionRequest> | SignTransactionRequest, requestBehavior?: RequestBehavior<B>): Promise<B extends BehaviorType.REDIRECT ? void : SignedTransaction>;
35+
signTransaction<B extends BehaviorType = DB>(request: Promise<SignTransactionRequest> | SignTransactionRequest, requestBehavior?: RequestBehavior<B>): Promise<B extends BehaviorType.REDIRECT ? void : SignedTransaction | SignedTransaction[]>;
3636
signStaking<B extends BehaviorType = DB>(request: Promise<SignStakingRequest> | SignStakingRequest, requestBehavior?: RequestBehavior<B>): Promise<B extends BehaviorType.REDIRECT ? void : SignedTransaction[]>;
3737
signMessage<B extends BehaviorType = DB>(request: Promise<SignMessageRequest> | SignMessageRequest, requestBehavior?: RequestBehavior<B>): Promise<B extends BehaviorType.REDIRECT ? void : SignedMessage>;
3838
signBtcTransaction<B extends BehaviorType = DB>(request: Promise<SignBtcTransactionRequest> | SignBtcTransactionRequest, requestBehavior?: RequestBehavior<B>): Promise<B extends BehaviorType.REDIRECT ? void : SignedBtcTransaction>;

client/dist/src/PublicRequestTypes.d.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,19 @@ export interface ChooseAddressResult extends Address {
7272
};
7373
};
7474
}
75-
export interface SignTransactionRequest extends BasicRequest {
75+
export interface TransactionData {
76+
recipient: string;
77+
recipientType?: Nimiq.AccountType;
78+
recipientLabel?: string;
79+
value: number;
80+
fee?: number;
81+
extraData?: Bytes;
82+
flags?: number;
83+
validityStartHeight: number;
84+
senderType?: Nimiq.AccountType;
85+
senderLabel?: string;
86+
}
87+
export interface SignTransactionRequestSingle extends BasicRequest {
7688
sender: string;
7789
recipient: string;
7890
recipientType?: Nimiq.AccountType;
@@ -83,6 +95,13 @@ export interface SignTransactionRequest extends BasicRequest {
8395
flags?: number;
8496
validityStartHeight: number;
8597
}
98+
export interface SignTransactionRequestMulti extends BasicRequest {
99+
sender: string;
100+
senderLabel?: string;
101+
recipientLabel?: string;
102+
transactions: TransactionData[] | Uint8Array[];
103+
}
104+
export declare type SignTransactionRequest = SignTransactionRequestSingle | SignTransactionRequestMulti;
86105
export interface SignStakingRequest extends BasicRequest {
87106
senderLabel?: string;
88107
recipientLabel?: string;
@@ -615,4 +634,4 @@ export interface SignedPolygonTransaction {
615634
}
616635
export declare type RpcRequest = SignTransactionRequest | SignMultisigTransactionRequest | SignStakingRequest | CreateCashlinkRequest | ManageCashlinkRequest | CheckoutRequest | BasicRequest | SimpleRequest | ChooseAddressRequest | OnboardRequest | RenameRequest | SignMessageRequest | ExportRequest | SignBtcTransactionRequest | AddBtcAddressesRequest | SignPolygonTransactionRequest | SetupSwapRequest | RefundSwapRequest | ConnectAccountRequest;
617636
export declare type RpcResult = SignedTransaction | SignedTransaction[] | PartialSignature | Account | Account[] | SimpleResult | ChooseAddressResult | Address | Cashlink | Cashlink[] | SignedMessage | ExportResult | SignedBtcTransaction | AddBtcAddressesResult | SignedPolygonTransaction | SetupSwapResult | ConnectedAccount;
618-
export declare type ResultByRequestType<T> = T extends RequestType.RENAME ? Account : T extends RequestType.ONBOARD | RequestType.SIGNUP | RequestType.LOGIN | RequestType.MIGRATE | RequestType.LIST ? Account[] : T extends RequestType.LIST_CASHLINKS ? Cashlink[] : T extends RequestType.CHOOSE_ADDRESS ? ChooseAddressResult : T extends RequestType.ADD_ADDRESS ? Address : T extends RequestType.SIGN_TRANSACTION ? SignedTransaction : T extends RequestType.SIGN_MULTISIG_TRANSACTION ? PartialSignature : T extends RequestType.SIGN_STAKING ? SignedTransaction[] : T extends RequestType.CHECKOUT ? SignedTransaction | SimpleResult : T extends RequestType.SIGN_MESSAGE ? SignedMessage : T extends RequestType.LOGOUT | RequestType.CHANGE_PASSWORD ? SimpleResult : T extends RequestType.EXPORT ? ExportResult : T extends RequestType.CREATE_CASHLINK | RequestType.MANAGE_CASHLINK ? Cashlink : T extends RequestType.SIGN_BTC_TRANSACTION ? SignedBtcTransaction : T extends RequestType.SIGN_POLYGON_TRANSACTION ? SignedPolygonTransaction : T extends RequestType.ACTIVATE_BITCOIN ? Account : T extends RequestType.ACTIVATE_POLYGON ? Account : T extends RequestType.ADD_BTC_ADDRESSES ? AddBtcAddressesResult : T extends RequestType.SETUP_SWAP ? SetupSwapResult : T extends RequestType.CONNECT_ACCOUNT ? ConnectedAccount : never;
637+
export declare type ResultByRequestType<T> = T extends RequestType.RENAME ? Account : T extends RequestType.ONBOARD | RequestType.SIGNUP | RequestType.LOGIN | RequestType.MIGRATE | RequestType.LIST ? Account[] : T extends RequestType.LIST_CASHLINKS ? Cashlink[] : T extends RequestType.CHOOSE_ADDRESS ? ChooseAddressResult : T extends RequestType.ADD_ADDRESS ? Address : T extends RequestType.SIGN_TRANSACTION ? SignedTransaction | SignedTransaction[] : T extends RequestType.SIGN_MULTISIG_TRANSACTION ? PartialSignature : T extends RequestType.SIGN_STAKING ? SignedTransaction[] : T extends RequestType.CHECKOUT ? SignedTransaction | SimpleResult : T extends RequestType.SIGN_MESSAGE ? SignedMessage : T extends RequestType.LOGOUT | RequestType.CHANGE_PASSWORD ? SimpleResult : T extends RequestType.EXPORT ? ExportResult : T extends RequestType.CREATE_CASHLINK | RequestType.MANAGE_CASHLINK ? Cashlink : T extends RequestType.SIGN_BTC_TRANSACTION ? SignedBtcTransaction : T extends RequestType.SIGN_POLYGON_TRANSACTION ? SignedPolygonTransaction : T extends RequestType.ACTIVATE_BITCOIN ? Account : T extends RequestType.ACTIVATE_POLYGON ? Account : T extends RequestType.ADD_BTC_ADDRESSES ? AddBtcAddressesResult : T extends RequestType.SETUP_SWAP ? SetupSwapResult : T extends RequestType.CONNECT_ACCOUNT ? ConnectedAccount : never;

demos/Demo.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import { WalletStore } from '../src/lib/WalletStore';
2929
// BitcoinJS is defined as a global variable in BitcoinJS.min.js loaded by demos/index.html
3030
declare global {
3131
const BitcoinJS: typeof import('bitcoinjs-lib');
32+
interface Window {
33+
loadAlbatross(): Promise<typeof import('@nimiq/core')>;
34+
}
3235
}
3336

3437
class Demo {
@@ -199,6 +202,41 @@ class Demo {
199202
}
200203
});
201204

205+
document.querySelector('button#sign-multi-transaction')!.addEventListener('click', async () => {
206+
const txRequest = generateSignMultiTransactionRequest();
207+
try {
208+
const result = await demo.client.signTransaction(txRequest, demo._defaultBehavior);
209+
if (demo.isRedirectResult(result)) return;
210+
console.log('Result', result);
211+
if (Array.isArray(result)) {
212+
document.querySelector('#result')!.textContent = `${result.length} transactions signed`;
213+
} else {
214+
document.querySelector('#result')!.textContent = 'TX signed';
215+
}
216+
} catch (e) {
217+
console.error(e);
218+
document.querySelector('#result')!.textContent = `Error: ${e.message || e}`;
219+
}
220+
});
221+
222+
document.querySelector('button#sign-staking-multi-transaction')!.addEventListener('click', async () => {
223+
const txRequest = await generateStakingMultiTransactionRequest();
224+
try {
225+
const result = await demo.client.signTransaction(txRequest, demo._defaultBehavior);
226+
if (demo.isRedirectResult(result)) return;
227+
console.log('Result', result);
228+
if (Array.isArray(result)) {
229+
document.querySelector('#result')!.textContent =
230+
`${result.length} staking transactions signed (deactivate, retire, unstake)`;
231+
} else {
232+
document.querySelector('#result')!.textContent = 'Staking TX signed';
233+
}
234+
} catch (e) {
235+
console.error(e);
236+
document.querySelector('#result')!.textContent = `Error: ${e.message || e}`;
237+
}
238+
});
239+
202240
document.querySelector('button#onboard')!.addEventListener('click', async () => {
203241
try {
204242
const result = await demo.client.onboard({ appName: 'Hub Demos' }, demo._defaultBehavior);
@@ -259,6 +297,118 @@ class Demo {
259297
};
260298
}
261299

300+
function generateSignMultiTransactionRequest(): SignTransactionRequest {
301+
const $radio = document.querySelector('input[name="address"]:checked');
302+
if (!$radio) {
303+
alert('You have no account to send a tx from, create an account first (signup)');
304+
throw new Error('No account found');
305+
}
306+
const sender = ($radio as HTMLElement).dataset.address!;
307+
const baseFee = parseInt((document.querySelector('#fee') as HTMLInputElement).value, 10) || 100;
308+
const validityStartHeight = parseInt((document.querySelector('#validitystartheight') as HTMLInputElement).value || '1234', 10);
309+
310+
// Sample recipients for multi-transaction demo
311+
const recipients = [
312+
'NQ63 U7XG 1YYE D6FA SXGG 3F5H X403 NBKN JLDU',
313+
'NQ46 2RM7 QE4T 82KR 61Q9 9B7E R38G LBVM N6KY',
314+
'NQ70 APBA 9GCC FL44 D82R UJCD DS4B Y824 3LYJ',
315+
];
316+
317+
return {
318+
appName: 'Hub Demos',
319+
sender,
320+
recipientLabel: 'Alice',
321+
transactions: recipients.map((recipient, index) => ({
322+
recipient,
323+
value: 100000000 + (index * 10000000), // 1 NIM + increments
324+
fee: baseFee + (index * 10),
325+
validityStartHeight: validityStartHeight + index,
326+
extraData: Utf8Tools.stringToUtf8ByteArray(`Multi-tx demo ${index + 1}`),
327+
})),
328+
};
329+
}
330+
331+
async function generateStakingMultiTransactionRequest(): Promise<SignTransactionRequest> {
332+
const $radio = document.querySelector('input[name="address"]:checked');
333+
if (!$radio) {
334+
alert('You have no account to send a tx from, create an account first (signup)');
335+
throw new Error('No account found');
336+
}
337+
const sender = ($radio as HTMLElement).dataset.address!;
338+
const validityStartHeight = parseInt((document.querySelector('#validitystartheight') as HTMLInputElement).value || '1234', 10);
339+
340+
// Load Nimiq library
341+
const Nimiq = await window.loadAlbatross();
342+
343+
// Demo recipients for simulated multi-transaction staking flow
344+
// Using the same recipients as in the regular multi-transaction demo (known valid addresses)
345+
const recipients = [
346+
'NQ63 U7XG 1YYE D6FA SXGG 3F5H X403 NBKN JLDU',
347+
'NQ46 2RM7 QE4T 82KR 61Q9 9B7E R38G LBVM N6KY',
348+
'NQ70 APBA 9GCC FL44 D82R UJCD DS4B Y824 3LYJ',
349+
];
350+
351+
// Parse sender address
352+
const senderAddress = Nimiq.Address.fromString(sender);
353+
354+
// Network ID for testnet (5 for test-albatross, 1 for mainnet)
355+
const networkId = 5;
356+
357+
// Create actual serialized transactions using Nimiq library
358+
const serializedTransactions = [
359+
// Transaction 1: Deactivate stake (demo)
360+
new Nimiq.Transaction(
361+
senderAddress, // sender
362+
Nimiq.AccountType.Basic, // senderType
363+
new Uint8Array(0), // senderData
364+
Nimiq.Address.fromString(recipients[0]), // recipient
365+
Nimiq.AccountType.Basic, // recipientType
366+
Utf8Tools.stringToUtf8ByteArray('Deactivate stake (demo)'), // recipientData (extraData)
367+
50000000n, // value (0.5 NIM)
368+
100n, // fee
369+
Nimiq.TransactionFlag.None, // flag
370+
validityStartHeight, // validityStartHeight
371+
networkId, // networkId
372+
).serialize(),
373+
// Transaction 2: Retire stake (demo)
374+
new Nimiq.Transaction(
375+
senderAddress,
376+
Nimiq.AccountType.Basic,
377+
new Uint8Array(0),
378+
Nimiq.Address.fromString(recipients[1]),
379+
Nimiq.AccountType.Basic,
380+
Utf8Tools.stringToUtf8ByteArray('Retire stake (demo)'),
381+
50000000n, // 0.5 NIM
382+
100n, // fee
383+
Nimiq.TransactionFlag.None,
384+
validityStartHeight + 10, // Slightly later
385+
networkId,
386+
).serialize(),
387+
// Transaction 3: Unstake complete (demo)
388+
new Nimiq.Transaction(
389+
senderAddress,
390+
Nimiq.AccountType.Basic,
391+
new Uint8Array(0),
392+
Nimiq.Address.fromString(recipients[2]),
393+
Nimiq.AccountType.Basic,
394+
Utf8Tools.stringToUtf8ByteArray('Unstake complete (demo)'),
395+
50000000n, // 0.5 NIM
396+
100n, // fee
397+
Nimiq.TransactionFlag.None,
398+
validityStartHeight + 20,
399+
networkId,
400+
).serialize(),
401+
];
402+
403+
// Return request with serialized transactions
404+
return {
405+
appName: 'Hub Demos',
406+
sender,
407+
recipientLabel: 'Staking Demo Recipients',
408+
transactions: serializedTransactions,
409+
};
410+
}
411+
262412
async function generateCheckoutRequest(multiCheckout?: boolean): Promise<CheckoutRequest> {
263413
const nimTxValue = parseInt((document.querySelector('#value') as HTMLInputElement).value, 10) || 1337;
264414
const nimTxFee = parseInt((document.querySelector('#fee') as HTMLInputElement).value, 10) || 0;

demos/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ <h2>Transactions</h2>
9090
<br><button id="checkout" disabled>Checkout</button>
9191
<br><button id="multi-checkout" disabled>Multi Checkout</button>
9292
<br><button id="sign-transaction" disabled>Sign Transaction</button>
93+
<br><button id="sign-multi-transaction" disabled>Sign Multiple Transactions</button>
94+
<br><button id="sign-staking-multi-transaction" disabled>Sign Staking Multi-Tx (Demo)</button>
9395
</div>
9496

9597
<div class="request">

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"@nimiq/electrum-client": "https://github.com/nimiq/electrum-client#build",
2727
"@nimiq/fastspot-api": "^1.10.2",
2828
"@nimiq/iqons": "^1.5.2",
29-
"@nimiq/keyguard-client": "^1.9.0",
29+
"@nimiq/keyguard-client": "link:../keyguard/client",
3030
"@nimiq/ledger-api": "^3.1.1",
3131
"@nimiq/oasis-api": "^1.1.1",
3232
"@nimiq/rpc": "^0.4.1",
@@ -71,5 +71,6 @@
7171
"webpack-i18n-tools": "https://github.com/nimiq/webpack-i18n-tools#master",
7272
"webpack-subresource-integrity": "^1.5.2",
7373
"write-file-webpack-plugin": "^4.5.1"
74-
}
74+
},
75+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
7576
}

0 commit comments

Comments
 (0)