@thru/programs 0.2.27 → 0.2.28
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/dist/multicall/index.cjs +166 -24
- package/dist/multicall/index.cjs.map +1 -1
- package/dist/multicall/index.d.cts +39 -3
- package/dist/multicall/index.d.ts +39 -3
- package/dist/multicall/index.js +166 -25
- package/dist/multicall/index.js.map +1 -1
- package/dist/passkey-manager/index.cjs +1235 -345
- package/dist/passkey-manager/index.cjs.map +1 -1
- package/dist/passkey-manager/index.d.cts +45 -11
- package/dist/passkey-manager/index.d.ts +45 -11
- package/dist/passkey-manager/index.js +1225 -346
- package/dist/passkey-manager/index.js.map +1 -1
- package/package.json +2 -2
- package/src/multicall/abi/thru/common/primitives/types.ts +14 -9
- package/src/multicall/abi/thru/program/multicall/types.ts +136 -4
- package/src/multicall/index.ts +6 -17
- package/src/passkey-manager/abi/thru/blockchain/state_proof/types.ts +11 -6
- package/src/passkey-manager/abi/thru/common/primitives/types.ts +19 -14
- package/src/passkey-manager/abi/thru/program/passkey_manager/types.ts +1069 -271
- package/src/passkey-manager/accounts.ts +79 -40
- package/src/passkey-manager/constants.ts +10 -1
- package/src/passkey-manager/index.ts +18 -2
- package/src/passkey-manager/instructions/add-authority.ts +31 -3
- package/src/passkey-manager/instructions/create.ts +106 -11
- package/src/passkey-manager/instructions/invoke.ts +9 -0
- package/src/passkey-manager/instructions/shared.ts +15 -0
- package/src/passkey-manager/instructions/transfer.ts +1 -1
- package/src/passkey-manager/instructions/validate.ts +13 -43
- package/src/passkey-manager/types.ts +7 -2
- package/src/passkey-manager/validate.test.ts +71 -3
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { encodeAddress } from '@thru/sdk/helpers';
|
|
2
|
+
import { LONG_LIVED_AUTHORITY_EXPIRY_SECONDS } from './constants';
|
|
2
3
|
import {
|
|
3
4
|
CredentialLookup,
|
|
4
5
|
WalletAccount,
|
|
5
6
|
} from './abi/thru/program/passkey_manager/types';
|
|
6
7
|
|
|
8
|
+
const LEGACY_WALLET_HEADER_BYTES = 9;
|
|
9
|
+
const LEGACY_AUTHORITY_BYTES = 65;
|
|
10
|
+
const AUTHORITY_DATA_BYTES = 64;
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* Parse wallet account data to extract nonce.
|
|
9
14
|
*/
|
|
10
15
|
export function parseWalletNonce(data: Uint8Array): bigint {
|
|
11
16
|
const account = WalletAccount.from_array(data);
|
|
12
|
-
if (!account) return 0n;
|
|
17
|
+
if (!account) return parseLegacyWalletNonce(data) ?? 0n;
|
|
13
18
|
return account.get_nonce();
|
|
14
19
|
}
|
|
15
20
|
|
|
@@ -24,9 +29,7 @@ export async function fetchWalletNonce(
|
|
|
24
29
|
const account = await sdk.accounts.get(walletAddress);
|
|
25
30
|
const data = account.data?.data;
|
|
26
31
|
if (!data) return 0n;
|
|
27
|
-
|
|
28
|
-
if (!parsed) return 0n;
|
|
29
|
-
return parsed.get_nonce();
|
|
32
|
+
return parseWalletNonce(data);
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/* ------------------------------------------------------------------ */
|
|
@@ -36,17 +39,20 @@ export async function fetchWalletNonce(
|
|
|
36
39
|
export type ParsedAuthority =
|
|
37
40
|
| {
|
|
38
41
|
idx: number;
|
|
42
|
+
expiresAtBlockTimeSeconds: bigint;
|
|
39
43
|
kind: 'passkey';
|
|
40
44
|
x: Uint8Array;
|
|
41
45
|
y: Uint8Array;
|
|
42
46
|
}
|
|
43
47
|
| {
|
|
44
48
|
idx: number;
|
|
49
|
+
expiresAtBlockTimeSeconds: bigint;
|
|
45
50
|
kind: 'pubkey';
|
|
46
51
|
pubkey: Uint8Array;
|
|
47
52
|
}
|
|
48
53
|
| {
|
|
49
54
|
idx: number;
|
|
55
|
+
expiresAtBlockTimeSeconds: bigint;
|
|
50
56
|
kind: 'unknown';
|
|
51
57
|
tag: number;
|
|
52
58
|
data: Uint8Array;
|
|
@@ -55,56 +61,84 @@ export type ParsedAuthority =
|
|
|
55
61
|
export interface WalletAuthorities {
|
|
56
62
|
nonce: bigint;
|
|
57
63
|
authorities: ParsedAuthority[];
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const AUTHORITY_HEADER_BYTES = 9; /* num_auth (u8) + nonce (u64 LE) */
|
|
61
|
-
const AUTHORITY_ENTRY_BYTES = 65; /* tag (u8) + data (64) */
|
|
62
|
-
|
|
63
|
-
function readU64LE(data: Uint8Array, offset: number): bigint {
|
|
64
|
-
if (offset + 8 > data.length) {
|
|
65
|
-
throw new Error('Out of bounds');
|
|
66
|
-
}
|
|
67
|
-
let value = 0n;
|
|
68
|
-
for (let i = 0; i < 8; i++) {
|
|
69
|
-
value |= BigInt(data[offset + i]) << (8n * BigInt(i));
|
|
70
|
-
}
|
|
71
|
-
return value;
|
|
64
|
+
layout: 'authorityRecord' | 'legacyAuthority';
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
/**
|
|
75
68
|
* Parse the on-chain WalletAccount data buffer into its nonce and full
|
|
76
|
-
* authority list
|
|
77
|
-
*
|
|
78
|
-
* num_auth: u8
|
|
79
|
-
* nonce: u64 LE
|
|
80
|
-
* authorities[num_auth + 1]: { tag: u8, data: [u8; 64] }
|
|
81
|
-
*
|
|
82
|
-
* (`num_auth + 1` because num_auth stores the count minus one.)
|
|
69
|
+
* authority list using the generated ABI view.
|
|
83
70
|
*/
|
|
84
71
|
export function parseWalletAuthorities(data: Uint8Array): WalletAuthorities {
|
|
85
|
-
|
|
86
|
-
|
|
72
|
+
const account = WalletAccount.from_array(data);
|
|
73
|
+
if (!account) {
|
|
74
|
+
const legacy = parseLegacyWalletAuthorities(data);
|
|
75
|
+
if (legacy) return legacy;
|
|
76
|
+
throw new Error('Wallet data truncated');
|
|
87
77
|
}
|
|
88
78
|
|
|
79
|
+
const authorities: ParsedAuthority[] = [];
|
|
80
|
+
account.get_authorities().forEach((record, idx) => {
|
|
81
|
+
const authority = record.get_authority();
|
|
82
|
+
const tag = authority.get_tag();
|
|
83
|
+
const payload = Uint8Array.from(authority.get_data());
|
|
84
|
+
const expiresAtBlockTimeSeconds =
|
|
85
|
+
record.get_expires_at_block_time_seconds();
|
|
86
|
+
|
|
87
|
+
if (tag === 1) {
|
|
88
|
+
authorities.push({
|
|
89
|
+
idx,
|
|
90
|
+
expiresAtBlockTimeSeconds,
|
|
91
|
+
kind: 'passkey',
|
|
92
|
+
x: payload.slice(0, 32),
|
|
93
|
+
y: payload.slice(32, 64),
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (tag === 2) {
|
|
99
|
+
authorities.push({
|
|
100
|
+
idx,
|
|
101
|
+
expiresAtBlockTimeSeconds,
|
|
102
|
+
kind: 'pubkey',
|
|
103
|
+
pubkey: payload.slice(0, 32),
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
authorities.push({ idx, expiresAtBlockTimeSeconds, kind: 'unknown', tag, data: payload });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return { nonce: account.get_nonce(), authorities, layout: 'authorityRecord' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseLegacyWalletNonce(data: Uint8Array): bigint | null {
|
|
115
|
+
if (data.length < LEGACY_WALLET_HEADER_BYTES) return null;
|
|
116
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
117
|
+
return view.getBigUint64(1, true);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseLegacyWalletAuthorities(data: Uint8Array): WalletAuthorities | null {
|
|
121
|
+
if (data.length < LEGACY_WALLET_HEADER_BYTES) return null;
|
|
122
|
+
|
|
89
123
|
const numAuth = data[0];
|
|
90
|
-
const
|
|
124
|
+
const authorityCount = numAuth + 1;
|
|
125
|
+
const requiredLength = LEGACY_WALLET_HEADER_BYTES + authorityCount * LEGACY_AUTHORITY_BYTES;
|
|
126
|
+
if (data.length < requiredLength) return null;
|
|
91
127
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
AUTHORITY_HEADER_BYTES + count * AUTHORITY_ENTRY_BYTES;
|
|
95
|
-
if (data.length < required) {
|
|
96
|
-
throw new Error('Wallet data truncated');
|
|
97
|
-
}
|
|
128
|
+
const nonce = parseLegacyWalletNonce(data);
|
|
129
|
+
if (nonce === null) return null;
|
|
98
130
|
|
|
99
131
|
const authorities: ParsedAuthority[] = [];
|
|
100
|
-
for (let idx = 0; idx <
|
|
101
|
-
const offset =
|
|
132
|
+
for (let idx = 0; idx < authorityCount; idx += 1) {
|
|
133
|
+
const offset = LEGACY_WALLET_HEADER_BYTES + idx * LEGACY_AUTHORITY_BYTES;
|
|
102
134
|
const tag = data[offset];
|
|
103
|
-
const payload = data.slice(offset + 1, offset +
|
|
135
|
+
const payload = data.slice(offset + 1, offset + 1 + AUTHORITY_DATA_BYTES);
|
|
136
|
+
const expiresAtBlockTimeSeconds = LONG_LIVED_AUTHORITY_EXPIRY_SECONDS;
|
|
104
137
|
|
|
105
138
|
if (tag === 1) {
|
|
106
139
|
authorities.push({
|
|
107
140
|
idx,
|
|
141
|
+
expiresAtBlockTimeSeconds,
|
|
108
142
|
kind: 'passkey',
|
|
109
143
|
x: payload.slice(0, 32),
|
|
110
144
|
y: payload.slice(32, 64),
|
|
@@ -113,14 +147,19 @@ export function parseWalletAuthorities(data: Uint8Array): WalletAuthorities {
|
|
|
113
147
|
}
|
|
114
148
|
|
|
115
149
|
if (tag === 2) {
|
|
116
|
-
authorities.push({
|
|
150
|
+
authorities.push({
|
|
151
|
+
idx,
|
|
152
|
+
expiresAtBlockTimeSeconds,
|
|
153
|
+
kind: 'pubkey',
|
|
154
|
+
pubkey: payload.slice(0, 32),
|
|
155
|
+
});
|
|
117
156
|
continue;
|
|
118
157
|
}
|
|
119
158
|
|
|
120
|
-
authorities.push({ idx, kind: 'unknown', tag, data: payload });
|
|
159
|
+
authorities.push({ idx, expiresAtBlockTimeSeconds, kind: 'unknown', tag, data: payload });
|
|
121
160
|
}
|
|
122
161
|
|
|
123
|
-
return { nonce, authorities };
|
|
162
|
+
return { nonce, authorities, layout: 'legacyAuthority' };
|
|
124
163
|
}
|
|
125
164
|
|
|
126
165
|
/**
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Authority,
|
|
3
|
+
AuthorityRecord,
|
|
4
|
+
} from './abi/thru/program/passkey_manager/types';
|
|
5
|
+
|
|
1
6
|
export const PASSKEY_MANAGER_PROGRAM_ADDRESS =
|
|
2
|
-
'
|
|
7
|
+
'tabsq39mzj3DZlutXOGG6VtfMj8fUvI0HIOXfZm7TLLY6N';
|
|
3
8
|
|
|
4
9
|
// Instruction discriminants
|
|
5
10
|
export const INSTRUCTION_CREATE = 0x00;
|
|
@@ -12,3 +17,7 @@ export const INSTRUCTION_REGISTER_CREDENTIAL = 0x06;
|
|
|
12
17
|
// Authority tags
|
|
13
18
|
export const AUTHORITY_TAG_PASSKEY = 1;
|
|
14
19
|
export const AUTHORITY_TAG_PUBKEY = 2;
|
|
20
|
+
|
|
21
|
+
export const AUTHORITY_BYTES = Authority.footprint();
|
|
22
|
+
export const AUTHORITY_RECORD_BYTES = AuthorityRecord.footprint();
|
|
23
|
+
export const LONG_LIVED_AUTHORITY_EXPIRY_SECONDS = 0xffffffffffffffffn;
|
|
@@ -9,11 +9,15 @@ export {
|
|
|
9
9
|
INSTRUCTION_REGISTER_CREDENTIAL,
|
|
10
10
|
AUTHORITY_TAG_PASSKEY,
|
|
11
11
|
AUTHORITY_TAG_PUBKEY,
|
|
12
|
+
AUTHORITY_BYTES,
|
|
13
|
+
AUTHORITY_RECORD_BYTES,
|
|
14
|
+
LONG_LIVED_AUTHORITY_EXPIRY_SECONDS,
|
|
12
15
|
} from './constants';
|
|
13
16
|
|
|
14
17
|
// Types
|
|
15
18
|
export type {
|
|
16
19
|
Authority,
|
|
20
|
+
AuthorityRecord,
|
|
17
21
|
CreateInstructionParams,
|
|
18
22
|
TransferInstructionParams,
|
|
19
23
|
TargetInstructionParams,
|
|
@@ -31,12 +35,24 @@ export type {
|
|
|
31
35
|
} from './types';
|
|
32
36
|
|
|
33
37
|
// Instructions
|
|
34
|
-
export {
|
|
38
|
+
export {
|
|
39
|
+
buildAuthority,
|
|
40
|
+
buildAuthorityRecord,
|
|
41
|
+
createAuthorityRecord,
|
|
42
|
+
createSessionAuthorityRecord,
|
|
43
|
+
encodeCreateInstruction,
|
|
44
|
+
encodeLegacyCreateInstruction,
|
|
45
|
+
} from './instructions/create';
|
|
35
46
|
export { encodeValidateInstruction } from './instructions/validate';
|
|
36
47
|
export { encodeTransferInstruction } from './instructions/transfer';
|
|
37
|
-
export {
|
|
48
|
+
export { encodeInvokeInstruction } from './instructions/invoke';
|
|
49
|
+
export {
|
|
50
|
+
encodeAddAuthorityInstruction,
|
|
51
|
+
encodeLegacyAddAuthorityInstruction,
|
|
52
|
+
} from './instructions/add-authority';
|
|
38
53
|
export { encodeRemoveAuthorityInstruction } from './instructions/remove-authority';
|
|
39
54
|
export { encodeRegisterCredentialInstruction } from './instructions/register-credential';
|
|
55
|
+
export { concatenateInstructions } from './instructions/shared';
|
|
40
56
|
|
|
41
57
|
// Challenge
|
|
42
58
|
export { createValidateChallenge, VALIDATE_CHALLENGE_DOMAIN } from './challenge';
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { AddAuthorityInstructionParams } from '../types';
|
|
2
|
+
import { INSTRUCTION_ADD_AUTHORITY } from '../constants';
|
|
2
3
|
import {
|
|
3
4
|
AddAuthorityArgsBuilder,
|
|
4
5
|
PasskeyInstructionBuilder,
|
|
5
6
|
} from '../abi/thru/program/passkey_manager/types';
|
|
6
|
-
import { buildAuthority } from './create';
|
|
7
|
+
import { buildAuthority, buildAuthorityRecord } from './create';
|
|
7
8
|
|
|
8
9
|
export function encodeAddAuthorityInstruction(params: AddAuthorityInstructionParams): Uint8Array {
|
|
9
10
|
const { walletAccountIdx } = params;
|
|
@@ -11,11 +12,11 @@ export function encodeAddAuthorityInstruction(params: AddAuthorityInstructionPar
|
|
|
11
12
|
throw new Error('walletAccountIdx must be 0-65535');
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
+
const authorityRecordBytes = buildAuthorityRecord(params.authorityRecord);
|
|
15
16
|
|
|
16
17
|
const argsPayload = new AddAuthorityArgsBuilder()
|
|
17
18
|
.set_wallet_account_idx(walletAccountIdx)
|
|
18
|
-
.
|
|
19
|
+
.set_authority_record(authorityRecordBytes)
|
|
19
20
|
.build();
|
|
20
21
|
|
|
21
22
|
return new PasskeyInstructionBuilder()
|
|
@@ -25,3 +26,30 @@ export function encodeAddAuthorityInstruction(params: AddAuthorityInstructionPar
|
|
|
25
26
|
.finish()
|
|
26
27
|
.build();
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
function writeU16LE(target: Uint8Array, offset: number, value: number): void {
|
|
31
|
+
target[offset] = value & 0xff;
|
|
32
|
+
target[offset + 1] = (value >> 8) & 0xff;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Encode ADD_AUTHORITY for the currently deployed legacy passkey-manager.
|
|
37
|
+
*
|
|
38
|
+
* Legacy ADD_AUTHORITY appends a bare 65-byte Authority. Expiry remains a
|
|
39
|
+
* local wallet/session policy until the AuthorityRecord program is deployed.
|
|
40
|
+
*/
|
|
41
|
+
export function encodeLegacyAddAuthorityInstruction(
|
|
42
|
+
params: AddAuthorityInstructionParams
|
|
43
|
+
): Uint8Array {
|
|
44
|
+
const { walletAccountIdx } = params;
|
|
45
|
+
if (walletAccountIdx < 0 || walletAccountIdx > 0xffff) {
|
|
46
|
+
throw new Error('walletAccountIdx must be 0-65535');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const authorityBytes = buildAuthority(params.authorityRecord.authority);
|
|
50
|
+
const output = new Uint8Array(1 + 2 + authorityBytes.length);
|
|
51
|
+
output[0] = INSTRUCTION_ADD_AUTHORITY;
|
|
52
|
+
writeU16LE(output, 1, walletAccountIdx);
|
|
53
|
+
output.set(authorityBytes, 3);
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
import type { Authority, CreateInstructionParams } from '../types';
|
|
2
1
|
import {
|
|
2
|
+
AUTHORITY_BYTES,
|
|
3
|
+
AUTHORITY_RECORD_BYTES,
|
|
4
|
+
LONG_LIVED_AUTHORITY_EXPIRY_SECONDS,
|
|
5
|
+
INSTRUCTION_CREATE,
|
|
6
|
+
} from '../constants';
|
|
7
|
+
import type { Authority, AuthorityRecord, CreateInstructionParams } from '../types';
|
|
8
|
+
import {
|
|
9
|
+
Authority as AuthorityView,
|
|
3
10
|
AuthorityBuilder,
|
|
11
|
+
AuthorityRecord as AuthorityRecordView,
|
|
4
12
|
CreateArgsBuilder,
|
|
5
13
|
PasskeyInstructionBuilder,
|
|
6
14
|
} from '../abi/thru/program/passkey_manager/types';
|
|
7
15
|
|
|
16
|
+
const AUTHORITY_DATA_BYTES = AUTHORITY_BYTES - 1;
|
|
17
|
+
const PUBKEY_BYTES = AUTHORITY_DATA_BYTES / 2;
|
|
18
|
+
const U64_MAX = 0xffffffffffffffffn;
|
|
19
|
+
|
|
8
20
|
function buildAuthority(authority: Authority): Uint8Array {
|
|
9
|
-
const data = new Array<number>(
|
|
21
|
+
const data = new Array<number>(AUTHORITY_DATA_BYTES).fill(0);
|
|
10
22
|
|
|
11
23
|
if (authority.tag === 1) {
|
|
12
|
-
if (authority.pubkeyX.length !==
|
|
13
|
-
if (authority.pubkeyY.length !==
|
|
14
|
-
for (let i = 0; i <
|
|
15
|
-
for (let i = 0; i <
|
|
24
|
+
if (authority.pubkeyX.length !== PUBKEY_BYTES) throw new Error('pubkeyX must be 32 bytes');
|
|
25
|
+
if (authority.pubkeyY.length !== PUBKEY_BYTES) throw new Error('pubkeyY must be 32 bytes');
|
|
26
|
+
for (let i = 0; i < PUBKEY_BYTES; i++) data[i] = authority.pubkeyX[i];
|
|
27
|
+
for (let i = 0; i < PUBKEY_BYTES; i++) data[PUBKEY_BYTES + i] = authority.pubkeyY[i];
|
|
16
28
|
} else if (authority.tag === 2) {
|
|
17
|
-
if (authority.pubkey.length !==
|
|
18
|
-
for (let i = 0; i <
|
|
29
|
+
if (authority.pubkey.length !== PUBKEY_BYTES) throw new Error('pubkey must be 32 bytes');
|
|
30
|
+
for (let i = 0; i < PUBKEY_BYTES; i++) data[i] = authority.pubkey[i];
|
|
19
31
|
} else {
|
|
20
32
|
throw new Error('Invalid authority tag');
|
|
21
33
|
}
|
|
@@ -28,19 +40,70 @@ function buildAuthority(authority: Authority): Uint8Array {
|
|
|
28
40
|
|
|
29
41
|
export { buildAuthority };
|
|
30
42
|
|
|
43
|
+
function copyGeneratedBuffer(view: unknown, label: string): Uint8Array {
|
|
44
|
+
const buffer = (view as { buffer?: Uint8Array }).buffer;
|
|
45
|
+
if (!buffer) {
|
|
46
|
+
throw new Error(`${label} did not expose a generated buffer`);
|
|
47
|
+
}
|
|
48
|
+
return buffer.slice();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertU64(value: bigint, label: string): void {
|
|
52
|
+
if (value < 0n || value > U64_MAX) {
|
|
53
|
+
throw new Error(`${label} must fit in u64`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createAuthorityRecord(
|
|
58
|
+
authority: Authority,
|
|
59
|
+
expiresAtBlockTimeSeconds = LONG_LIVED_AUTHORITY_EXPIRY_SECONDS
|
|
60
|
+
): AuthorityRecord {
|
|
61
|
+
return { authority, expiresAtBlockTimeSeconds };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createSessionAuthorityRecord(
|
|
65
|
+
params: {
|
|
66
|
+
pubkey: Uint8Array;
|
|
67
|
+
expiresAtBlockTimeSeconds: bigint;
|
|
68
|
+
}
|
|
69
|
+
): AuthorityRecord {
|
|
70
|
+
return createAuthorityRecord(
|
|
71
|
+
{ tag: 2, pubkey: params.pubkey },
|
|
72
|
+
params.expiresAtBlockTimeSeconds
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function buildAuthorityRecord(record: AuthorityRecord): Uint8Array {
|
|
77
|
+
assertU64(record.expiresAtBlockTimeSeconds, 'expiresAtBlockTimeSeconds');
|
|
78
|
+
|
|
79
|
+
const authority = AuthorityView.from_array(buildAuthority(record.authority));
|
|
80
|
+
if (!authority) {
|
|
81
|
+
throw new Error('Failed to build authority');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const authorityRecord = AuthorityRecordView.__tnCreateView(
|
|
85
|
+
new Uint8Array(AUTHORITY_RECORD_BYTES)
|
|
86
|
+
);
|
|
87
|
+
authorityRecord.set_authority(authority);
|
|
88
|
+
authorityRecord.set_expires_at_block_time_seconds(
|
|
89
|
+
record.expiresAtBlockTimeSeconds
|
|
90
|
+
);
|
|
91
|
+
return copyGeneratedBuffer(authorityRecord, 'AuthorityRecord');
|
|
92
|
+
}
|
|
93
|
+
|
|
31
94
|
export function encodeCreateInstruction(params: CreateInstructionParams): Uint8Array {
|
|
32
|
-
const { walletAccountIdx,
|
|
95
|
+
const { walletAccountIdx, authorityRecord, seed, stateProof } = params;
|
|
33
96
|
|
|
34
97
|
if (seed.length !== 32) throw new Error('seed must be 32 bytes');
|
|
35
98
|
if (walletAccountIdx < 0 || walletAccountIdx > 0xffff) {
|
|
36
99
|
throw new Error('walletAccountIdx must be 0-65535');
|
|
37
100
|
}
|
|
38
101
|
|
|
39
|
-
const
|
|
102
|
+
const authorityRecordBytes = buildAuthorityRecord(authorityRecord);
|
|
40
103
|
|
|
41
104
|
const argsPayload = new CreateArgsBuilder()
|
|
42
105
|
.set_wallet_account_idx(walletAccountIdx)
|
|
43
|
-
.
|
|
106
|
+
.set_authority_record(authorityRecordBytes)
|
|
44
107
|
.set_seed(seed)
|
|
45
108
|
.set_state_proof(stateProof)
|
|
46
109
|
.build();
|
|
@@ -52,3 +115,35 @@ export function encodeCreateInstruction(params: CreateInstructionParams): Uint8A
|
|
|
52
115
|
.finish()
|
|
53
116
|
.build();
|
|
54
117
|
}
|
|
118
|
+
|
|
119
|
+
function writeU16LE(target: Uint8Array, offset: number, value: number): void {
|
|
120
|
+
target[offset] = value & 0xff;
|
|
121
|
+
target[offset + 1] = (value >> 8) & 0xff;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Encode CREATE for the currently deployed legacy passkey-manager program.
|
|
126
|
+
*
|
|
127
|
+
* Legacy CREATE stores the initial bare Authority directly:
|
|
128
|
+
* tag || wallet_account_idx || Authority || seed || StateProof
|
|
129
|
+
*
|
|
130
|
+
* Newer program builds should use encodeCreateInstruction, which carries a
|
|
131
|
+
* full AuthorityRecord with an expiry timestamp.
|
|
132
|
+
*/
|
|
133
|
+
export function encodeLegacyCreateInstruction(params: CreateInstructionParams): Uint8Array {
|
|
134
|
+
const { walletAccountIdx, authorityRecord, seed, stateProof } = params;
|
|
135
|
+
|
|
136
|
+
if (seed.length !== 32) throw new Error('seed must be 32 bytes');
|
|
137
|
+
if (walletAccountIdx < 0 || walletAccountIdx > 0xffff) {
|
|
138
|
+
throw new Error('walletAccountIdx must be 0-65535');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const authorityBytes = buildAuthority(authorityRecord.authority);
|
|
142
|
+
const output = new Uint8Array(1 + 2 + authorityBytes.length + seed.length + stateProof.length);
|
|
143
|
+
output[0] = INSTRUCTION_CREATE;
|
|
144
|
+
writeU16LE(output, 1, walletAccountIdx);
|
|
145
|
+
output.set(authorityBytes, 3);
|
|
146
|
+
output.set(seed, 3 + authorityBytes.length);
|
|
147
|
+
output.set(stateProof, 3 + authorityBytes.length + seed.length);
|
|
148
|
+
return output;
|
|
149
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function encodeInvokeInstruction(
|
|
2
|
+
_programPubkey: Uint8Array,
|
|
3
|
+
_instruction: Uint8Array,
|
|
4
|
+
): Uint8Array {
|
|
5
|
+
throw new Error(
|
|
6
|
+
'encodeInvokeInstruction is from the legacy passkey-manager ABI. ' +
|
|
7
|
+
'Migrate this flow to encodeValidateInstruction({ targetInstruction }) against the upgraded passkey manager.',
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function concatenateInstructions(instructions: Uint8Array[]): Uint8Array {
|
|
2
|
+
const totalLength = instructions.reduce(
|
|
3
|
+
(sum, instruction) => sum + instruction.length,
|
|
4
|
+
0,
|
|
5
|
+
);
|
|
6
|
+
const result = new Uint8Array(totalLength);
|
|
7
|
+
|
|
8
|
+
let offset = 0;
|
|
9
|
+
for (const instruction of instructions) {
|
|
10
|
+
result.set(instruction, offset);
|
|
11
|
+
offset += instruction.length;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
@@ -18,7 +18,7 @@ export function encodeTransferInstruction(params: TransferInstructionParams): Ui
|
|
|
18
18
|
const argsPayload = new TransferArgsBuilder()
|
|
19
19
|
.set_wallet_account_idx(walletAccountIdx)
|
|
20
20
|
.set_to_account_idx(toAccountIdx)
|
|
21
|
-
.set_amount(amount
|
|
21
|
+
.set_amount(amount)
|
|
22
22
|
.build();
|
|
23
23
|
|
|
24
24
|
return new PasskeyInstructionBuilder()
|
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
import type { ValidateInstructionParams } from '../types';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
PasskeyInstructionBuilder,
|
|
4
|
+
ValidateArgsBuilder,
|
|
5
|
+
} from '../abi/thru/program/passkey_manager/types';
|
|
3
6
|
import { buildTargetInstructionBytes } from '../target-instruction';
|
|
4
7
|
|
|
5
|
-
const U8_SIZE = Uint8Array.BYTES_PER_ELEMENT;
|
|
6
|
-
const U16_SIZE = Uint16Array.BYTES_PER_ELEMENT;
|
|
7
|
-
const P256_COORDINATE_SIZE = 32;
|
|
8
|
-
const VALIDATE_FIXED_PREFIX_SIZE =
|
|
9
|
-
U16_SIZE +
|
|
10
|
-
U8_SIZE +
|
|
11
|
-
P256_COORDINATE_SIZE +
|
|
12
|
-
P256_COORDINATE_SIZE +
|
|
13
|
-
U16_SIZE +
|
|
14
|
-
U16_SIZE;
|
|
15
|
-
|
|
16
|
-
function writeU16LE(target: Uint8Array, offset: number, value: number): void {
|
|
17
|
-
target[offset] = value & 0xff;
|
|
18
|
-
target[offset + 1] = (value >> 8) & 0xff;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
8
|
export function encodeValidateInstruction(params: ValidateInstructionParams): Uint8Array {
|
|
22
9
|
const {
|
|
23
10
|
walletAccountIdx,
|
|
@@ -44,36 +31,19 @@ export function encodeValidateInstruction(params: ValidateInstructionParams): Ui
|
|
|
44
31
|
|
|
45
32
|
const targetInstructionBytes = buildTargetInstructionBytes(targetInstruction);
|
|
46
33
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
writeU16LE(argsPayload, offset, walletAccountIdx);
|
|
56
|
-
offset += 2;
|
|
57
|
-
argsPayload[offset] = authIdx;
|
|
58
|
-
offset += 1;
|
|
59
|
-
argsPayload.set(signatureR, offset);
|
|
60
|
-
offset += signatureR.length;
|
|
61
|
-
argsPayload.set(signatureS, offset);
|
|
62
|
-
offset += signatureS.length;
|
|
63
|
-
writeU16LE(argsPayload, offset, authenticatorData.length);
|
|
64
|
-
offset += 2;
|
|
65
|
-
writeU16LE(argsPayload, offset, clientDataJSON.length);
|
|
66
|
-
offset += 2;
|
|
67
|
-
argsPayload.set(authenticatorData, offset);
|
|
68
|
-
offset += authenticatorData.length;
|
|
69
|
-
argsPayload.set(clientDataJSON, offset);
|
|
70
|
-
offset += clientDataJSON.length;
|
|
71
|
-
argsPayload.set(targetInstructionBytes, offset);
|
|
34
|
+
const argsBuilder = new ValidateArgsBuilder()
|
|
35
|
+
.set_wallet_account_idx(walletAccountIdx)
|
|
36
|
+
.set_auth_idx(authIdx)
|
|
37
|
+
.set_signature_r(signatureR)
|
|
38
|
+
.set_signature_s(signatureS)
|
|
39
|
+
.set_target_instruction(targetInstructionBytes);
|
|
40
|
+
argsBuilder.authenticator_data().write(authenticatorData).finish();
|
|
41
|
+
argsBuilder.client_data().write(clientDataJSON).finish();
|
|
72
42
|
|
|
73
43
|
return new PasskeyInstructionBuilder()
|
|
74
44
|
.payload()
|
|
75
45
|
.select('validate')
|
|
76
|
-
.writePayload(
|
|
46
|
+
.writePayload(argsBuilder)
|
|
77
47
|
.finish()
|
|
78
48
|
.build();
|
|
79
49
|
}
|
|
@@ -64,9 +64,14 @@ export type Authority =
|
|
|
64
64
|
pubkey: Uint8Array; // 32 bytes
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
export interface AuthorityRecord {
|
|
68
|
+
authority: Authority;
|
|
69
|
+
expiresAtBlockTimeSeconds: bigint;
|
|
70
|
+
}
|
|
71
|
+
|
|
67
72
|
export interface CreateInstructionParams {
|
|
68
73
|
walletAccountIdx: number;
|
|
69
|
-
|
|
74
|
+
authorityRecord: AuthorityRecord;
|
|
70
75
|
seed: Uint8Array;
|
|
71
76
|
stateProof: Uint8Array;
|
|
72
77
|
}
|
|
@@ -94,7 +99,7 @@ export interface ValidateInstructionParams {
|
|
|
94
99
|
|
|
95
100
|
export interface AddAuthorityInstructionParams {
|
|
96
101
|
walletAccountIdx: number;
|
|
97
|
-
|
|
102
|
+
authorityRecord: AuthorityRecord;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
export interface RemoveAuthorityInstructionParams {
|