@0xsequence/wallet-wdk 0.0.0-20250520201059
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/.env.test +3 -0
- package/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +202 -0
- package/dist/dbs/auth-commitments.d.ts +17 -0
- package/dist/dbs/auth-commitments.d.ts.map +1 -0
- package/dist/dbs/auth-commitments.js +13 -0
- package/dist/dbs/auth-keys.d.ts +19 -0
- package/dist/dbs/auth-keys.d.ts.map +1 -0
- package/dist/dbs/auth-keys.js +67 -0
- package/dist/dbs/generic.d.ts +33 -0
- package/dist/dbs/generic.d.ts.map +1 -0
- package/dist/dbs/generic.js +170 -0
- package/dist/dbs/index.d.ts +12 -0
- package/dist/dbs/index.d.ts.map +1 -0
- package/dist/dbs/index.js +8 -0
- package/dist/dbs/messages.d.ts +6 -0
- package/dist/dbs/messages.d.ts.map +1 -0
- package/dist/dbs/messages.js +13 -0
- package/dist/dbs/recovery.d.ts +6 -0
- package/dist/dbs/recovery.d.ts.map +1 -0
- package/dist/dbs/recovery.js +13 -0
- package/dist/dbs/signatures.d.ts +6 -0
- package/dist/dbs/signatures.d.ts.map +1 -0
- package/dist/dbs/signatures.js +13 -0
- package/dist/dbs/transactions.d.ts +6 -0
- package/dist/dbs/transactions.d.ts.map +1 -0
- package/dist/dbs/transactions.js +13 -0
- package/dist/dbs/wallets.d.ts +6 -0
- package/dist/dbs/wallets.d.ts.map +1 -0
- package/dist/dbs/wallets.js +13 -0
- package/dist/identity/signer.d.ts +17 -0
- package/dist/identity/signer.d.ts.map +1 -0
- package/dist/identity/signer.js +58 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/sequence/cron.d.ts +19 -0
- package/dist/sequence/cron.d.ts.map +1 -0
- package/dist/sequence/cron.js +118 -0
- package/dist/sequence/devices.d.ts +14 -0
- package/dist/sequence/devices.d.ts.map +1 -0
- package/dist/sequence/devices.js +43 -0
- package/dist/sequence/handlers/authcode-pkce.d.ts +14 -0
- package/dist/sequence/handlers/authcode-pkce.d.ts.map +1 -0
- package/dist/sequence/handlers/authcode-pkce.js +48 -0
- package/dist/sequence/handlers/authcode.d.ts +25 -0
- package/dist/sequence/handlers/authcode.d.ts.map +1 -0
- package/dist/sequence/handlers/authcode.js +91 -0
- package/dist/sequence/handlers/devices.d.ts +14 -0
- package/dist/sequence/handlers/devices.d.ts.map +1 -0
- package/dist/sequence/handlers/devices.js +39 -0
- package/dist/sequence/handlers/handler.d.ts +8 -0
- package/dist/sequence/handlers/handler.d.ts.map +1 -0
- package/dist/sequence/handlers/handler.js +1 -0
- package/dist/sequence/handlers/identity.d.ts +21 -0
- package/dist/sequence/handlers/identity.d.ts.map +1 -0
- package/dist/sequence/handlers/identity.js +86 -0
- package/dist/sequence/handlers/index.d.ts +7 -0
- package/dist/sequence/handlers/index.d.ts.map +1 -0
- package/dist/sequence/handlers/index.js +5 -0
- package/dist/sequence/handlers/mnemonic.d.ts +19 -0
- package/dist/sequence/handlers/mnemonic.d.ts.map +1 -0
- package/dist/sequence/handlers/mnemonic.js +67 -0
- package/dist/sequence/handlers/otp.d.ts +20 -0
- package/dist/sequence/handlers/otp.d.ts.map +1 -0
- package/dist/sequence/handlers/otp.js +83 -0
- package/dist/sequence/handlers/passkeys.d.ts +17 -0
- package/dist/sequence/handlers/passkeys.d.ts.map +1 -0
- package/dist/sequence/handlers/passkeys.js +63 -0
- package/dist/sequence/handlers/recovery.d.ts +15 -0
- package/dist/sequence/handlers/recovery.d.ts.map +1 -0
- package/dist/sequence/handlers/recovery.js +72 -0
- package/dist/sequence/index.d.ts +12 -0
- package/dist/sequence/index.d.ts.map +1 -0
- package/dist/sequence/index.js +9 -0
- package/dist/sequence/logger.d.ts +7 -0
- package/dist/sequence/logger.d.ts.map +1 -0
- package/dist/sequence/logger.js +11 -0
- package/dist/sequence/manager.d.ts +287 -0
- package/dist/sequence/manager.d.ts.map +1 -0
- package/dist/sequence/manager.js +356 -0
- package/dist/sequence/messages.d.ts +18 -0
- package/dist/sequence/messages.d.ts.map +1 -0
- package/dist/sequence/messages.js +115 -0
- package/dist/sequence/recovery.d.ts +30 -0
- package/dist/sequence/recovery.d.ts.map +1 -0
- package/dist/sequence/recovery.js +314 -0
- package/dist/sequence/sessions.d.ts +26 -0
- package/dist/sequence/sessions.d.ts.map +1 -0
- package/dist/sequence/sessions.js +169 -0
- package/dist/sequence/signatures.d.ts +21 -0
- package/dist/sequence/signatures.d.ts.map +1 -0
- package/dist/sequence/signatures.js +192 -0
- package/dist/sequence/signers.d.ts +14 -0
- package/dist/sequence/signers.d.ts.map +1 -0
- package/dist/sequence/signers.js +74 -0
- package/dist/sequence/transactions.d.ts +26 -0
- package/dist/sequence/transactions.d.ts.map +1 -0
- package/dist/sequence/transactions.js +201 -0
- package/dist/sequence/types/index.d.ts +9 -0
- package/dist/sequence/types/index.d.ts.map +1 -0
- package/dist/sequence/types/index.js +2 -0
- package/dist/sequence/types/message-request.d.ts +23 -0
- package/dist/sequence/types/message-request.d.ts.map +1 -0
- package/dist/sequence/types/message-request.js +1 -0
- package/dist/sequence/types/recovery.d.ts +15 -0
- package/dist/sequence/types/recovery.d.ts.map +1 -0
- package/dist/sequence/types/recovery.js +1 -0
- package/dist/sequence/types/signature-request.d.ts +76 -0
- package/dist/sequence/types/signature-request.d.ts.map +1 -0
- package/dist/sequence/types/signature-request.js +11 -0
- package/dist/sequence/types/signer.d.ts +28 -0
- package/dist/sequence/types/signer.d.ts.map +1 -0
- package/dist/sequence/types/signer.js +10 -0
- package/dist/sequence/types/transaction-request.d.ts +41 -0
- package/dist/sequence/types/transaction-request.d.ts.map +1 -0
- package/dist/sequence/types/transaction-request.js +1 -0
- package/dist/sequence/types/wallet.d.ts +21 -0
- package/dist/sequence/types/wallet.d.ts.map +1 -0
- package/dist/sequence/types/wallet.js +1 -0
- package/dist/sequence/wallets.d.ts +121 -0
- package/dist/sequence/wallets.d.ts.map +1 -0
- package/dist/sequence/wallets.js +632 -0
- package/package.json +40 -0
- package/src/dbs/auth-commitments.ts +26 -0
- package/src/dbs/auth-keys.ts +85 -0
- package/src/dbs/generic.ts +194 -0
- package/src/dbs/index.ts +13 -0
- package/src/dbs/messages.ts +16 -0
- package/src/dbs/recovery.ts +15 -0
- package/src/dbs/signatures.ts +15 -0
- package/src/dbs/transactions.ts +16 -0
- package/src/dbs/wallets.ts +16 -0
- package/src/identity/signer.ts +78 -0
- package/src/index.ts +2 -0
- package/src/sequence/cron.ts +134 -0
- package/src/sequence/devices.ts +53 -0
- package/src/sequence/handlers/authcode-pkce.ts +70 -0
- package/src/sequence/handlers/authcode.ts +116 -0
- package/src/sequence/handlers/devices.ts +53 -0
- package/src/sequence/handlers/handler.ts +14 -0
- package/src/sequence/handlers/identity.ts +101 -0
- package/src/sequence/handlers/index.ts +6 -0
- package/src/sequence/handlers/mnemonic.ts +88 -0
- package/src/sequence/handlers/otp.ts +107 -0
- package/src/sequence/handlers/passkeys.ts +84 -0
- package/src/sequence/handlers/recovery.ts +88 -0
- package/src/sequence/index.ts +25 -0
- package/src/sequence/logger.ts +11 -0
- package/src/sequence/manager.ts +634 -0
- package/src/sequence/messages.ts +146 -0
- package/src/sequence/recovery.ts +429 -0
- package/src/sequence/sessions.ts +238 -0
- package/src/sequence/signatures.ts +263 -0
- package/src/sequence/signers.ts +88 -0
- package/src/sequence/transactions.ts +281 -0
- package/src/sequence/types/index.ts +27 -0
- package/src/sequence/types/message-request.ts +26 -0
- package/src/sequence/types/recovery.ts +15 -0
- package/src/sequence/types/signature-request.ts +89 -0
- package/src/sequence/types/signer.ts +32 -0
- package/src/sequence/types/transaction-request.ts +47 -0
- package/src/sequence/types/wallet.ts +24 -0
- package/src/sequence/wallets.ts +853 -0
- package/test/constants.ts +62 -0
- package/test/recovery.test.ts +211 -0
- package/test/sessions.test.ts +324 -0
- package/test/setup.ts +63 -0
- package/test/transactions.test.ts +464 -0
- package/test/wallets.test.ts +381 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/sequence/messages.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAsC,MAAM,IAAI,CAAA;AAEhE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAmC,MAAM,4BAA4B,CAAA;AAErG,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE9B,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI1B,GAAG,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAIlD,yBAAyB;IASjC,OAAO,CACX,IAAI,EAAE,OAAO,CAAC,OAAO,EACrB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,GACA,OAAO,CAAC,MAAM,CAAC;IAyBZ,QAAQ,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqD7D,gBAAgB,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO;IAYrE,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,EAAE,OAAO,CAAC,EAAE,OAAO;IAY9E,MAAM,CAAC,oBAAoB,EAAE,MAAM;CAS1C"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Envelope, Wallet } from '@0xsequence/wallet-core';
|
|
2
|
+
import { Payload } from '@0xsequence/wallet-primitives';
|
|
3
|
+
import { Hex, Provider, RpcTransport } from 'ox';
|
|
4
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
5
|
+
export class Messages {
|
|
6
|
+
shared;
|
|
7
|
+
constructor(shared) {
|
|
8
|
+
this.shared = shared;
|
|
9
|
+
}
|
|
10
|
+
async list() {
|
|
11
|
+
return this.shared.databases.messages.list();
|
|
12
|
+
}
|
|
13
|
+
async get(messageOrSignatureId) {
|
|
14
|
+
return this.getByMessageOrSignatureId(messageOrSignatureId);
|
|
15
|
+
}
|
|
16
|
+
async getByMessageOrSignatureId(messageOrSignatureId) {
|
|
17
|
+
const messages = await this.list();
|
|
18
|
+
const message = messages.find((m) => m.id === messageOrSignatureId || m.signatureId === messageOrSignatureId);
|
|
19
|
+
if (!message) {
|
|
20
|
+
throw new Error(`Message ${messageOrSignatureId} not found`);
|
|
21
|
+
}
|
|
22
|
+
return message;
|
|
23
|
+
}
|
|
24
|
+
async request(from, message, chainId, options) {
|
|
25
|
+
const wallet = new Wallet(from, { stateProvider: this.shared.sequence.stateProvider });
|
|
26
|
+
// Prepare message payload
|
|
27
|
+
const envelope = await wallet.prepareMessageSignature(message, chainId ?? 0n);
|
|
28
|
+
// Prepare signature request
|
|
29
|
+
const signatureRequest = await this.shared.modules.signatures.request(envelope, 'sign-message', {
|
|
30
|
+
origin: options?.source,
|
|
31
|
+
});
|
|
32
|
+
const id = uuidv7();
|
|
33
|
+
await this.shared.databases.messages.set({
|
|
34
|
+
id,
|
|
35
|
+
wallet: from,
|
|
36
|
+
message,
|
|
37
|
+
envelope,
|
|
38
|
+
source: options?.source ?? 'unknown',
|
|
39
|
+
status: 'requested',
|
|
40
|
+
signatureId: signatureRequest,
|
|
41
|
+
});
|
|
42
|
+
return signatureRequest;
|
|
43
|
+
}
|
|
44
|
+
async complete(messageOrSignatureId) {
|
|
45
|
+
const message = await this.getByMessageOrSignatureId(messageOrSignatureId);
|
|
46
|
+
if (message.status === 'signed') {
|
|
47
|
+
// Return the message signature
|
|
48
|
+
return message.messageSignature;
|
|
49
|
+
}
|
|
50
|
+
const messageId = message.id;
|
|
51
|
+
const signature = await this.shared.modules.signatures.get(message.signatureId);
|
|
52
|
+
if (!signature) {
|
|
53
|
+
throw new Error(`Signature ${message.signatureId} not found for message ${messageId}`);
|
|
54
|
+
}
|
|
55
|
+
if (!Payload.isMessage(message.envelope.payload) || !Payload.isMessage(signature.envelope.payload)) {
|
|
56
|
+
throw new Error(`Message ${messageId} is not a message payload`);
|
|
57
|
+
}
|
|
58
|
+
if (!Envelope.isSigned(signature.envelope)) {
|
|
59
|
+
throw new Error(`Message ${messageId} is not signed`);
|
|
60
|
+
}
|
|
61
|
+
const signatureEnvelope = signature.envelope;
|
|
62
|
+
const { weight, threshold } = Envelope.weightOf(signatureEnvelope);
|
|
63
|
+
if (weight < threshold) {
|
|
64
|
+
throw new Error(`Message ${messageId} has insufficient weight`);
|
|
65
|
+
}
|
|
66
|
+
// Get the provider for the message chain
|
|
67
|
+
let provider;
|
|
68
|
+
if (message.envelope.chainId !== 0n) {
|
|
69
|
+
const network = this.shared.sequence.networks.find((network) => network.chainId === message.envelope.chainId);
|
|
70
|
+
if (!network) {
|
|
71
|
+
throw new Error(`Network not found for ${message.envelope.chainId}`);
|
|
72
|
+
}
|
|
73
|
+
const transport = RpcTransport.fromHttp(network.rpc);
|
|
74
|
+
provider = Provider.from(transport);
|
|
75
|
+
}
|
|
76
|
+
const wallet = new Wallet(message.wallet, { stateProvider: this.shared.sequence.stateProvider });
|
|
77
|
+
const messageSignature = Hex.from(await wallet.buildMessageSignature(signatureEnvelope, provider));
|
|
78
|
+
await this.shared.databases.messages.set({
|
|
79
|
+
...message,
|
|
80
|
+
envelope: signature.envelope,
|
|
81
|
+
status: 'signed',
|
|
82
|
+
messageSignature,
|
|
83
|
+
});
|
|
84
|
+
await this.shared.modules.signatures.complete(signature.id);
|
|
85
|
+
return messageSignature;
|
|
86
|
+
}
|
|
87
|
+
onMessagesUpdate(cb, trigger) {
|
|
88
|
+
const undo = this.shared.databases.messages.addListener(() => {
|
|
89
|
+
this.list().then((l) => cb(l));
|
|
90
|
+
});
|
|
91
|
+
if (trigger) {
|
|
92
|
+
this.list().then((l) => cb(l));
|
|
93
|
+
}
|
|
94
|
+
return undo;
|
|
95
|
+
}
|
|
96
|
+
onMessageUpdate(messageId, cb, trigger) {
|
|
97
|
+
const undo = this.shared.databases.messages.addListener(() => {
|
|
98
|
+
this.get(messageId).then((t) => cb(t));
|
|
99
|
+
});
|
|
100
|
+
if (trigger) {
|
|
101
|
+
this.get(messageId).then((t) => cb(t));
|
|
102
|
+
}
|
|
103
|
+
return undo;
|
|
104
|
+
}
|
|
105
|
+
async delete(messageOrSignatureId) {
|
|
106
|
+
try {
|
|
107
|
+
const message = await this.getByMessageOrSignatureId(messageOrSignatureId);
|
|
108
|
+
await this.shared.databases.signatures.del(message.signatureId);
|
|
109
|
+
await this.shared.databases.messages.del(message.id);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// Ignore
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Config, Payload } from '@0xsequence/wallet-primitives';
|
|
2
|
+
import { Shared } from './manager.js';
|
|
3
|
+
import { Address, Hex } from 'ox';
|
|
4
|
+
import { RecoverySigner } from './types/signer.js';
|
|
5
|
+
import { QueuedRecoveryPayload } from './types/recovery.js';
|
|
6
|
+
export declare class Recovery {
|
|
7
|
+
private readonly shared;
|
|
8
|
+
constructor(shared: Shared);
|
|
9
|
+
initialize(): void;
|
|
10
|
+
private updateRecoveryModule;
|
|
11
|
+
initRecoveryModule(modules: Config.SapientSignerLeaf[], address: Address.Address): Promise<void>;
|
|
12
|
+
hasRecoveryModule(modules: Config.SapientSignerLeaf[]): boolean;
|
|
13
|
+
addRecoverySignerToModules(modules: Config.SapientSignerLeaf[], address: Address.Address): Promise<void>;
|
|
14
|
+
removeRecoverySignerFromModules(modules: Config.SapientSignerLeaf[], address: Address.Address): Promise<void>;
|
|
15
|
+
addRecoveryMnemonic(wallet: Address.Address, mnemonic: string): Promise<string>;
|
|
16
|
+
addRecoverySigner(wallet: Address.Address, address: Address.Address): Promise<string>;
|
|
17
|
+
removeRecoverySigner(wallet: Address.Address, address: Address.Address): Promise<string>;
|
|
18
|
+
completeRecoveryUpdate(requestId: string): Promise<void>;
|
|
19
|
+
getRecoverySigners(address: Address.Address): Promise<RecoverySigner[] | undefined>;
|
|
20
|
+
queueRecoveryPayload(wallet: Address.Address, chainId: bigint, payload: Payload.Calls): Promise<string>;
|
|
21
|
+
completeRecoveryPayload(requestId: string): Promise<{
|
|
22
|
+
to: Address.Address;
|
|
23
|
+
data: Hex.Hex;
|
|
24
|
+
}>;
|
|
25
|
+
getQueuedRecoveryPayloads(wallet?: Address.Address): Promise<QueuedRecoveryPayload[]>;
|
|
26
|
+
onQueuedRecoveryPayloadsUpdate(wallet: Address.Address | undefined, cb: (payloads: QueuedRecoveryPayload[]) => void, trigger?: boolean): () => void;
|
|
27
|
+
updateQueuedRecoveryPayloads(): Promise<void>;
|
|
28
|
+
encodeRecoverySignature(imageHash: Hex.Hex, signer: Address.Address): Promise<import("ox/Bytes").Bytes>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../../src/sequence/recovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA2B,OAAO,EAAE,MAAM,+BAA+B,CAAA;AACxF,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,GAAG,EAA0B,MAAM,IAAI,CAAA;AACzD,OAAO,EAAS,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAI3D,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAE3C,UAAU;YAYI,oBAAoB;IAoCrB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO;IA2B7F,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,iBAAiB,EAAE,GAAG,OAAO;IAIzD,0BAA0B,CAAC,OAAO,EAAE,MAAM,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO;IAwBxF,+BAA+B,CAAC,OAAO,EAAE,MAAM,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO;IAsB7F,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM;IAc7D,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO;IAanE,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO;IAWtE,sBAAsB,CAAC,SAAS,EAAE,MAAM;IASxC,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,cAAc,EAAE,GAAG,SAAS,CAAC;IAkCnF,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK;IAuCrF,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAA;KAAE,CAAC;IAsC3F,yBAAyB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAS3F,8BAA8B,CAC5B,MAAM,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS,EACnC,EAAE,EAAE,CAAC,QAAQ,EAAE,qBAAqB,EAAE,KAAK,IAAI,EAC/C,OAAO,CAAC,EAAE,OAAO;IAWb,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC;IA8F7C,uBAAuB,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO;CAgB1E"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { Config, Extensions, GenericTree, Payload } from '@0xsequence/wallet-primitives';
|
|
2
|
+
import { Address, Provider, RpcTransport } from 'ox';
|
|
3
|
+
import { Kinds } from './types/signer.js';
|
|
4
|
+
import { Envelope } from '@0xsequence/wallet-core';
|
|
5
|
+
import { Actions } from './types/index.js';
|
|
6
|
+
import { MnemonicHandler } from './handlers/mnemonic.js';
|
|
7
|
+
export class Recovery {
|
|
8
|
+
shared;
|
|
9
|
+
constructor(shared) {
|
|
10
|
+
this.shared = shared;
|
|
11
|
+
}
|
|
12
|
+
initialize() {
|
|
13
|
+
this.shared.modules.cron.registerJob('update-queued-recovery-payloads', 5 * 60 * 1000, // 5 minutes
|
|
14
|
+
async () => {
|
|
15
|
+
this.shared.modules.logger.log('Running job: update-queued-recovery-payloads');
|
|
16
|
+
await this.updateQueuedRecoveryPayloads();
|
|
17
|
+
});
|
|
18
|
+
this.shared.modules.logger.log('Recovery module initialized and job registered.');
|
|
19
|
+
}
|
|
20
|
+
async updateRecoveryModule(modules, transformer) {
|
|
21
|
+
const ext = this.shared.sequence.extensions.recovery;
|
|
22
|
+
const idx = modules.findIndex((m) => m.address === ext);
|
|
23
|
+
if (idx === -1) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const sapientSigner = modules[idx];
|
|
27
|
+
if (!sapientSigner) {
|
|
28
|
+
throw new Error('recovery-module-not-found');
|
|
29
|
+
}
|
|
30
|
+
const genericTree = await this.shared.sequence.stateProvider.getTree(sapientSigner.imageHash);
|
|
31
|
+
if (!genericTree) {
|
|
32
|
+
throw new Error('recovery-module-tree-not-found');
|
|
33
|
+
}
|
|
34
|
+
const tree = Extensions.Recovery.fromGenericTree(genericTree);
|
|
35
|
+
const { leaves, isComplete } = Extensions.Recovery.getRecoveryLeaves(tree);
|
|
36
|
+
if (!isComplete) {
|
|
37
|
+
throw new Error('recovery-module-tree-incomplete');
|
|
38
|
+
}
|
|
39
|
+
const nextTree = Extensions.Recovery.fromRecoveryLeaves(transformer(leaves));
|
|
40
|
+
const nextGeneric = Extensions.Recovery.toGenericTree(nextTree);
|
|
41
|
+
await this.shared.sequence.stateProvider.saveTree(nextGeneric);
|
|
42
|
+
if (!modules[idx]) {
|
|
43
|
+
throw new Error('recovery-module-not-found-(unreachable)');
|
|
44
|
+
}
|
|
45
|
+
modules[idx].imageHash = GenericTree.hash(nextGeneric);
|
|
46
|
+
}
|
|
47
|
+
async initRecoveryModule(modules, address) {
|
|
48
|
+
if (this.hasRecoveryModule(modules)) {
|
|
49
|
+
throw new Error('recovery-module-already-initialized');
|
|
50
|
+
}
|
|
51
|
+
const recoveryTree = Extensions.Recovery.fromRecoveryLeaves([
|
|
52
|
+
{
|
|
53
|
+
type: 'leaf',
|
|
54
|
+
signer: address,
|
|
55
|
+
requiredDeltaTime: this.shared.sequence.defaultRecoverySettings.requiredDeltaTime,
|
|
56
|
+
minTimestamp: this.shared.sequence.defaultRecoverySettings.minTimestamp,
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
const recoveryGenericTree = Extensions.Recovery.toGenericTree(recoveryTree);
|
|
60
|
+
await this.shared.sequence.stateProvider.saveTree(recoveryGenericTree);
|
|
61
|
+
const recoveryImageHash = GenericTree.hash(recoveryGenericTree);
|
|
62
|
+
modules.push({
|
|
63
|
+
type: 'sapient-signer',
|
|
64
|
+
address: this.shared.sequence.extensions.recovery,
|
|
65
|
+
weight: 255n,
|
|
66
|
+
imageHash: recoveryImageHash,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
hasRecoveryModule(modules) {
|
|
70
|
+
return modules.some((m) => Address.isEqual(m.address, this.shared.sequence.extensions.recovery));
|
|
71
|
+
}
|
|
72
|
+
async addRecoverySignerToModules(modules, address) {
|
|
73
|
+
if (!this.hasRecoveryModule(modules)) {
|
|
74
|
+
throw new Error('recovery-module-not-enabled');
|
|
75
|
+
}
|
|
76
|
+
await this.updateRecoveryModule(modules, (leaves) => {
|
|
77
|
+
if (leaves.some((l) => Address.isEqual(l.signer, address))) {
|
|
78
|
+
return leaves;
|
|
79
|
+
}
|
|
80
|
+
const filtered = leaves.filter((l) => !Address.isEqual(l.signer, '0x0000000000000000000000000000000000000000'));
|
|
81
|
+
return [
|
|
82
|
+
...filtered,
|
|
83
|
+
{
|
|
84
|
+
type: 'leaf',
|
|
85
|
+
signer: address,
|
|
86
|
+
requiredDeltaTime: this.shared.sequence.defaultRecoverySettings.requiredDeltaTime,
|
|
87
|
+
minTimestamp: this.shared.sequence.defaultRecoverySettings.minTimestamp,
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async removeRecoverySignerFromModules(modules, address) {
|
|
93
|
+
if (!this.hasRecoveryModule(modules)) {
|
|
94
|
+
throw new Error('recovery-module-not-enabled');
|
|
95
|
+
}
|
|
96
|
+
await this.updateRecoveryModule(modules, (leaves) => {
|
|
97
|
+
const next = leaves.filter((l) => l.signer !== address);
|
|
98
|
+
if (next.length === 0) {
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
type: 'leaf',
|
|
102
|
+
signer: '0x0000000000000000000000000000000000000000',
|
|
103
|
+
requiredDeltaTime: 0n,
|
|
104
|
+
minTimestamp: 0n,
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
return next;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async addRecoveryMnemonic(wallet, mnemonic) {
|
|
112
|
+
const signer = MnemonicHandler.toSigner(mnemonic);
|
|
113
|
+
if (!signer) {
|
|
114
|
+
throw new Error('invalid-mnemonic');
|
|
115
|
+
}
|
|
116
|
+
await signer.witness(this.shared.sequence.stateProvider, wallet, {
|
|
117
|
+
isForRecovery: true,
|
|
118
|
+
signerKind: Kinds.LoginMnemonic,
|
|
119
|
+
});
|
|
120
|
+
return this.addRecoverySigner(wallet, signer.address);
|
|
121
|
+
}
|
|
122
|
+
async addRecoverySigner(wallet, address) {
|
|
123
|
+
const { modules } = await this.shared.modules.wallets.getConfigurationParts(wallet);
|
|
124
|
+
await this.addRecoverySignerToModules(modules, address);
|
|
125
|
+
return this.shared.modules.wallets.requestConfigurationUpdate(wallet, {
|
|
126
|
+
modules,
|
|
127
|
+
}, Actions.AddRecoverySigner, 'wallet-webapp');
|
|
128
|
+
}
|
|
129
|
+
async removeRecoverySigner(wallet, address) {
|
|
130
|
+
const { modules } = await this.shared.modules.wallets.getConfigurationParts(wallet);
|
|
131
|
+
await this.removeRecoverySignerFromModules(modules, address);
|
|
132
|
+
return this.shared.modules.wallets.requestConfigurationUpdate(wallet, { modules }, Actions.RemoveRecoverySigner, 'wallet-webapp');
|
|
133
|
+
}
|
|
134
|
+
async completeRecoveryUpdate(requestId) {
|
|
135
|
+
const request = await this.shared.modules.signatures.get(requestId);
|
|
136
|
+
if (request.action !== 'add-recovery-signer' && request.action !== 'remove-recovery-signer') {
|
|
137
|
+
throw new Error('invalid-recovery-update-action');
|
|
138
|
+
}
|
|
139
|
+
return this.shared.modules.wallets.completeConfigurationUpdate(requestId);
|
|
140
|
+
}
|
|
141
|
+
async getRecoverySigners(address) {
|
|
142
|
+
const { raw } = await this.shared.modules.wallets.getConfiguration(address);
|
|
143
|
+
const recoveryLeaf = raw.modules.find((m) => Address.isEqual(m.address, this.shared.sequence.extensions.recovery));
|
|
144
|
+
if (!recoveryLeaf) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const recoveryGenericTree = await this.shared.sequence.stateProvider.getTree(recoveryLeaf.imageHash);
|
|
148
|
+
if (!recoveryGenericTree) {
|
|
149
|
+
throw new Error('recovery-module-tree-not-found');
|
|
150
|
+
}
|
|
151
|
+
const recoveryTree = Extensions.Recovery.fromGenericTree(recoveryGenericTree);
|
|
152
|
+
const { leaves, isComplete } = Extensions.Recovery.getRecoveryLeaves(recoveryTree);
|
|
153
|
+
if (!isComplete) {
|
|
154
|
+
throw new Error('recovery-module-tree-incomplete');
|
|
155
|
+
}
|
|
156
|
+
const kos = await this.shared.modules.signers.resolveKinds(address, leaves.map((l) => l.signer));
|
|
157
|
+
return leaves
|
|
158
|
+
.filter((l) => !Address.isEqual(l.signer, '0x0000000000000000000000000000000000000000'))
|
|
159
|
+
.map((l) => ({
|
|
160
|
+
address: l.signer,
|
|
161
|
+
kind: kos.find((s) => Address.isEqual(s.address, l.signer))?.kind || 'unknown',
|
|
162
|
+
isRecovery: true,
|
|
163
|
+
minTimestamp: l.minTimestamp,
|
|
164
|
+
requiredDeltaTime: l.requiredDeltaTime,
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
async queueRecoveryPayload(wallet, chainId, payload) {
|
|
168
|
+
const signers = await this.getRecoverySigners(wallet);
|
|
169
|
+
if (!signers) {
|
|
170
|
+
throw new Error('recovery-signers-not-found');
|
|
171
|
+
}
|
|
172
|
+
const recoveryPayload = Payload.toRecovery(payload);
|
|
173
|
+
const simulatedTopology = Config.flatLeavesToTopology(signers.map((s) => ({
|
|
174
|
+
type: 'signer',
|
|
175
|
+
address: s.address,
|
|
176
|
+
weight: 1n,
|
|
177
|
+
})));
|
|
178
|
+
// Save both versions of the payload in parallel
|
|
179
|
+
await Promise.all([
|
|
180
|
+
this.shared.sequence.stateProvider.savePayload(wallet, payload, chainId),
|
|
181
|
+
this.shared.sequence.stateProvider.savePayload(wallet, recoveryPayload, chainId),
|
|
182
|
+
]);
|
|
183
|
+
const requestId = await this.shared.modules.signatures.request({
|
|
184
|
+
wallet,
|
|
185
|
+
chainId,
|
|
186
|
+
configuration: {
|
|
187
|
+
threshold: 1n,
|
|
188
|
+
checkpoint: 0n,
|
|
189
|
+
topology: simulatedTopology,
|
|
190
|
+
},
|
|
191
|
+
payload: recoveryPayload,
|
|
192
|
+
}, 'recovery');
|
|
193
|
+
return requestId;
|
|
194
|
+
}
|
|
195
|
+
// TODO: Handle this transaction instead of just returning the to and data
|
|
196
|
+
async completeRecoveryPayload(requestId) {
|
|
197
|
+
const signature = await this.shared.modules.signatures.get(requestId);
|
|
198
|
+
if (signature.action !== 'recovery' || !Payload.isRecovery(signature.envelope.payload)) {
|
|
199
|
+
throw new Error('invalid-recovery-payload');
|
|
200
|
+
}
|
|
201
|
+
if (!Envelope.isSigned(signature.envelope)) {
|
|
202
|
+
throw new Error('recovery-payload-not-signed');
|
|
203
|
+
}
|
|
204
|
+
const { weight, threshold } = Envelope.weightOf(signature.envelope);
|
|
205
|
+
if (weight < threshold) {
|
|
206
|
+
throw new Error('recovery-payload-insufficient-weight');
|
|
207
|
+
}
|
|
208
|
+
// Find any valid signature
|
|
209
|
+
const validSignature = signature.envelope.signatures[0];
|
|
210
|
+
if (Envelope.isSapientSignature(validSignature)) {
|
|
211
|
+
throw new Error('recovery-payload-sapient-signatures-not-supported');
|
|
212
|
+
}
|
|
213
|
+
if (!validSignature) {
|
|
214
|
+
throw new Error('recovery-payload-no-valid-signature');
|
|
215
|
+
}
|
|
216
|
+
const calldata = Extensions.Recovery.encodeCalldata(signature.wallet, signature.envelope.payload, validSignature.address, validSignature.signature);
|
|
217
|
+
return {
|
|
218
|
+
to: this.shared.sequence.extensions.recovery,
|
|
219
|
+
data: calldata,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async getQueuedRecoveryPayloads(wallet) {
|
|
223
|
+
const all = await this.shared.databases.recovery.list();
|
|
224
|
+
if (wallet) {
|
|
225
|
+
return all.filter((p) => Address.isEqual(p.wallet, wallet));
|
|
226
|
+
}
|
|
227
|
+
return all;
|
|
228
|
+
}
|
|
229
|
+
onQueuedRecoveryPayloadsUpdate(wallet, cb, trigger) {
|
|
230
|
+
if (trigger) {
|
|
231
|
+
this.getQueuedRecoveryPayloads(wallet).then(cb);
|
|
232
|
+
}
|
|
233
|
+
return this.shared.databases.recovery.addListener(() => {
|
|
234
|
+
this.getQueuedRecoveryPayloads(wallet).then(cb);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async updateQueuedRecoveryPayloads() {
|
|
238
|
+
const wallets = await this.shared.modules.wallets.list();
|
|
239
|
+
if (wallets.length === 0) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Create providers for each network
|
|
243
|
+
const providers = this.shared.sequence.networks.map((network) => ({
|
|
244
|
+
chainId: network.chainId,
|
|
245
|
+
provider: Provider.from(RpcTransport.fromHttp(network.rpc)),
|
|
246
|
+
}));
|
|
247
|
+
const seenInThisRun = new Set();
|
|
248
|
+
for (const wallet of wallets) {
|
|
249
|
+
// See if they have any recover signers
|
|
250
|
+
const signers = await this.getRecoverySigners(wallet.address);
|
|
251
|
+
if (!signers || signers.length === 0) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
// Now we need to fetch, for each signer and network, any queued recovery payloads
|
|
255
|
+
// TODO: This may benefit from multicall, but it is not urgent, as this happens in the background
|
|
256
|
+
for (const signer of signers) {
|
|
257
|
+
for (const { chainId, provider } of providers) {
|
|
258
|
+
const totalPayloads = await Extensions.Recovery.totalQueuedPayloads(provider, this.shared.sequence.extensions.recovery, wallet.address, signer.address);
|
|
259
|
+
for (let i = 0n; i < totalPayloads; i++) {
|
|
260
|
+
const payloadHash = await Extensions.Recovery.queuedPayloadHashOf(provider, this.shared.sequence.extensions.recovery, wallet.address, signer.address, i);
|
|
261
|
+
const timestamp = await Extensions.Recovery.timestampForQueuedPayload(provider, this.shared.sequence.extensions.recovery, wallet.address, signer.address, payloadHash);
|
|
262
|
+
const payload = await this.shared.sequence.stateProvider.getPayload(payloadHash);
|
|
263
|
+
// If ready, we need to check if it was executed already
|
|
264
|
+
// for this, we check if the wallet 77nonce for the given space
|
|
265
|
+
// is greater than the nonce in the payload
|
|
266
|
+
if (timestamp < Date.now() / 1000 && payload && Payload.isCalls(payload.payload)) {
|
|
267
|
+
const nonce = await this.shared.modules.wallets.getNonce(chainId, wallet.address, payload.payload.space);
|
|
268
|
+
if (nonce > i) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// The id is the index + signer address + chainId + wallet address
|
|
273
|
+
const id = `${i}-${signer.address}-${chainId}-${wallet.address}`;
|
|
274
|
+
// Create a new payload
|
|
275
|
+
const payloadEntry = {
|
|
276
|
+
id,
|
|
277
|
+
index: i,
|
|
278
|
+
recoveryModule: this.shared.sequence.extensions.recovery,
|
|
279
|
+
wallet: wallet.address,
|
|
280
|
+
signer: signer.address,
|
|
281
|
+
chainId,
|
|
282
|
+
startTimestamp: timestamp,
|
|
283
|
+
endTimestamp: timestamp + signer.requiredDeltaTime,
|
|
284
|
+
payloadHash,
|
|
285
|
+
payload: payload?.payload,
|
|
286
|
+
};
|
|
287
|
+
await this.shared.databases.recovery.set(payloadEntry);
|
|
288
|
+
seenInThisRun.add(payloadEntry.id);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Delete any unseen queued payloads as they are no longer relevant
|
|
293
|
+
const allQueuedPayloads = await this.shared.databases.recovery.list();
|
|
294
|
+
for (const payload of allQueuedPayloads) {
|
|
295
|
+
if (!seenInThisRun.has(payload.id)) {
|
|
296
|
+
await this.shared.databases.recovery.del(payload.id);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async encodeRecoverySignature(imageHash, signer) {
|
|
302
|
+
const genericTree = await this.shared.sequence.stateProvider.getTree(imageHash);
|
|
303
|
+
if (!genericTree) {
|
|
304
|
+
throw new Error('recovery-module-tree-not-found');
|
|
305
|
+
}
|
|
306
|
+
const tree = Extensions.Recovery.fromGenericTree(genericTree);
|
|
307
|
+
const allSigners = Extensions.Recovery.getRecoveryLeaves(tree).leaves.map((l) => l.signer);
|
|
308
|
+
if (!allSigners.includes(signer)) {
|
|
309
|
+
throw new Error('signer-not-found-in-recovery-module');
|
|
310
|
+
}
|
|
311
|
+
const trimmed = Extensions.Recovery.trimTopology(tree, signer);
|
|
312
|
+
return Extensions.Recovery.encodeTopology(trimmed);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Signers as CoreSigners } from '@0xsequence/wallet-core';
|
|
2
|
+
import { Attestation, Signature as SequenceSignature, SessionConfig } from '@0xsequence/wallet-primitives';
|
|
3
|
+
import { Address, Hex } from 'ox';
|
|
4
|
+
import { Shared } from './manager.js';
|
|
5
|
+
export type AuthorizeImplicitSessionArgs = {
|
|
6
|
+
target: string;
|
|
7
|
+
applicationData?: Hex.Hex;
|
|
8
|
+
};
|
|
9
|
+
export declare class Sessions {
|
|
10
|
+
private readonly shared;
|
|
11
|
+
private readonly sessionManagerAddresses;
|
|
12
|
+
constructor(shared: Shared, sessionManagerAddresses?: Address.Address[]);
|
|
13
|
+
getSessionTopology(walletAddress: Address.Address): Promise<SessionConfig.SessionsTopology>;
|
|
14
|
+
prepareAuthorizeImplicitSession(walletAddress: Address.Address, sessionAddress: Address.Address, args: AuthorizeImplicitSessionArgs): Promise<string>;
|
|
15
|
+
completeAuthorizeImplicitSession(requestId: string): Promise<{
|
|
16
|
+
attestation: Attestation.Attestation;
|
|
17
|
+
signature: SequenceSignature.RSY;
|
|
18
|
+
}>;
|
|
19
|
+
addExplicitSession(walletAddress: Address.Address, sessionAddress: Address.Address, permissions: CoreSigners.Session.ExplicitParams, origin?: string): Promise<string>;
|
|
20
|
+
removeExplicitSession(walletAddress: Address.Address, sessionAddress: Address.Address, origin?: string): Promise<string>;
|
|
21
|
+
addBlacklistAddress(walletAddress: Address.Address, address: Address.Address, origin?: string): Promise<string>;
|
|
22
|
+
removeBlacklistAddress(walletAddress: Address.Address, address: Address.Address, origin?: string): Promise<string>;
|
|
23
|
+
private prepareSessionUpdate;
|
|
24
|
+
completeSessionUpdate(requestId: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/sequence/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAY,MAAM,yBAAyB,CAAA;AAC1E,OAAO,EACL,WAAW,EAKX,SAAS,IAAI,iBAAiB,EAC9B,aAAa,EACd,MAAM,+BAA+B,CAAA;AACtC,OAAO,EAAE,OAAO,EAAe,GAAG,EAAE,MAAM,IAAI,CAAA;AAI9C,OAAO,EAA0B,MAAM,EAAE,MAAM,cAAc,CAAA;AAG7D,MAAM,MAAM,4BAA4B,GAAG;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,GAAG,CAAC,GAAG,CAAA;CAC1B,CAAA;AAID,qBAAa,QAAQ;IAEjB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,uBAAuB;gBADvB,MAAM,EAAE,MAAM,EACd,uBAAuB,GAAE,OAAO,CAAC,OAAO,EAAmC;IAGxF,kBAAkB,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC;IAgB3F,+BAA+B,CACnC,aAAa,EAAE,OAAO,CAAC,OAAO,EAC9B,cAAc,EAAE,OAAO,CAAC,OAAO,EAC/B,IAAI,EAAE,4BAA4B,GACjC,OAAO,CAAC,MAAM,CAAC;IA+DZ,gCAAgC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QACjE,WAAW,EAAE,WAAW,CAAC,WAAW,CAAA;QACpC,SAAS,EAAE,iBAAiB,CAAC,GAAG,CAAA;KACjC,CAAC;IA8BI,kBAAkB,CACtB,aAAa,EAAE,OAAO,CAAC,OAAO,EAC9B,cAAc,EAAE,OAAO,CAAC,OAAO,EAC/B,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,cAAc,EAC/C,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;IASZ,qBAAqB,CACzB,aAAa,EAAE,OAAO,CAAC,OAAO,EAC9B,cAAc,EAAE,OAAO,CAAC,OAAO,EAC/B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;IASZ,mBAAmB,CACvB,aAAa,EAAE,OAAO,CAAC,OAAO,EAC9B,OAAO,EAAE,OAAO,CAAC,OAAO,EACxB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;IAMZ,sBAAsB,CAC1B,aAAa,EAAE,OAAO,CAAC,OAAO,EAC9B,OAAO,EAAE,OAAO,CAAC,OAAO,EACxB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC;YAMJ,oBAAoB;IAoC5B,qBAAqB,CAAC,SAAS,EAAE,MAAM;CAQ9C"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Envelope } from '@0xsequence/wallet-core';
|
|
2
|
+
import { Constants, GenericTree, Payload, SessionConfig, } from '@0xsequence/wallet-primitives';
|
|
3
|
+
import { Address, Bytes, Hash, Hex } from 'ox';
|
|
4
|
+
import { AuthCodePkceHandler } from './handlers/authcode-pkce.js';
|
|
5
|
+
import { IdentityHandler, identityTypeToHex } from './handlers/identity.js';
|
|
6
|
+
import { ManagerOptionsDefaults } from './manager.js';
|
|
7
|
+
import { Actions } from './types/signature-request.js';
|
|
8
|
+
const DefaultSessionManagerAddresses = [Constants.DefaultSessionManager];
|
|
9
|
+
export class Sessions {
|
|
10
|
+
shared;
|
|
11
|
+
sessionManagerAddresses;
|
|
12
|
+
constructor(shared, sessionManagerAddresses = DefaultSessionManagerAddresses) {
|
|
13
|
+
this.shared = shared;
|
|
14
|
+
this.sessionManagerAddresses = sessionManagerAddresses;
|
|
15
|
+
}
|
|
16
|
+
async getSessionTopology(walletAddress) {
|
|
17
|
+
const { modules } = await this.shared.modules.wallets.getConfigurationParts(walletAddress);
|
|
18
|
+
const managerLeaf = modules.find((leaf) => this.sessionManagerAddresses.some((addr) => Address.isEqual(addr, leaf.address)));
|
|
19
|
+
if (!managerLeaf) {
|
|
20
|
+
throw new Error('Session manager not found');
|
|
21
|
+
}
|
|
22
|
+
const imageHash = managerLeaf.imageHash;
|
|
23
|
+
const tree = await this.shared.sequence.stateProvider.getTree(imageHash);
|
|
24
|
+
if (!tree) {
|
|
25
|
+
throw new Error('Session topology not found');
|
|
26
|
+
}
|
|
27
|
+
return SessionConfig.configurationTreeToSessionsTopology(tree);
|
|
28
|
+
}
|
|
29
|
+
async prepareAuthorizeImplicitSession(walletAddress, sessionAddress, args) {
|
|
30
|
+
const topology = await this.getSessionTopology(walletAddress);
|
|
31
|
+
const identitySignerAddress = SessionConfig.getIdentitySigner(topology);
|
|
32
|
+
if (!identitySignerAddress) {
|
|
33
|
+
throw new Error('No identity signer address found');
|
|
34
|
+
}
|
|
35
|
+
const identityKind = await this.shared.modules.signers.kindOf(walletAddress, identitySignerAddress);
|
|
36
|
+
if (!identityKind) {
|
|
37
|
+
throw new Error('No identity handler kind found');
|
|
38
|
+
}
|
|
39
|
+
const handler = this.shared.handlers.get(identityKind);
|
|
40
|
+
if (!handler) {
|
|
41
|
+
throw new Error('No identity handler found');
|
|
42
|
+
}
|
|
43
|
+
// Create the attestation to sign
|
|
44
|
+
let identityType;
|
|
45
|
+
let issuerHash = '0x';
|
|
46
|
+
let audienceHash = '0x';
|
|
47
|
+
if (handler instanceof IdentityHandler) {
|
|
48
|
+
identityType = handler.identityType;
|
|
49
|
+
if (handler instanceof AuthCodePkceHandler) {
|
|
50
|
+
issuerHash = Hash.keccak256(Hex.fromString(handler.issuer));
|
|
51
|
+
audienceHash = Hash.keccak256(Hex.fromString(handler.audience));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const attestation = {
|
|
55
|
+
approvedSigner: sessionAddress,
|
|
56
|
+
identityType: Bytes.fromHex(identityTypeToHex(identityType), { size: 4 }),
|
|
57
|
+
issuerHash: Bytes.fromHex(issuerHash, { size: 32 }),
|
|
58
|
+
audienceHash: Bytes.fromHex(audienceHash, { size: 32 }),
|
|
59
|
+
applicationData: Bytes.fromHex(args.applicationData ?? '0x'),
|
|
60
|
+
authData: {
|
|
61
|
+
redirectUrl: args.target,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
// Fake the configuration with the single required signer
|
|
65
|
+
const configuration = {
|
|
66
|
+
threshold: 1n,
|
|
67
|
+
checkpoint: 0n,
|
|
68
|
+
topology: {
|
|
69
|
+
type: 'signer',
|
|
70
|
+
address: identitySignerAddress,
|
|
71
|
+
weight: 1n,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const envelope = {
|
|
75
|
+
payload: {
|
|
76
|
+
type: 'session-implicit-authorize',
|
|
77
|
+
sessionAddress,
|
|
78
|
+
attestation,
|
|
79
|
+
},
|
|
80
|
+
wallet: walletAddress,
|
|
81
|
+
chainId: 0n,
|
|
82
|
+
configuration,
|
|
83
|
+
};
|
|
84
|
+
// Request the signature from the identity handler
|
|
85
|
+
return this.shared.modules.signatures.request(envelope, 'session-implicit-authorize', {
|
|
86
|
+
origin: args.target,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async completeAuthorizeImplicitSession(requestId) {
|
|
90
|
+
// Get the updated signature request
|
|
91
|
+
const signatureRequest = await this.shared.modules.signatures.get(requestId);
|
|
92
|
+
if (signatureRequest.action !== 'session-implicit-authorize' ||
|
|
93
|
+
!Payload.isSessionImplicitAuthorize(signatureRequest.envelope.payload)) {
|
|
94
|
+
throw new Error('Invalid action');
|
|
95
|
+
}
|
|
96
|
+
if (!Envelope.isSigned(signatureRequest.envelope) || !Envelope.reachedThreshold(signatureRequest.envelope)) {
|
|
97
|
+
throw new Error('Envelope not signed or threshold not reached');
|
|
98
|
+
}
|
|
99
|
+
// Find any valid signature
|
|
100
|
+
const signature = signatureRequest.envelope.signatures[0];
|
|
101
|
+
if (!signature || !Envelope.isSignature(signature)) {
|
|
102
|
+
throw new Error('No valid signature found');
|
|
103
|
+
}
|
|
104
|
+
if (signature.signature.type !== 'hash') {
|
|
105
|
+
// Should never happen
|
|
106
|
+
throw new Error('Unsupported signature type');
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
attestation: signatureRequest.envelope.payload.attestation,
|
|
110
|
+
signature: signature.signature,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async addExplicitSession(walletAddress, sessionAddress, permissions, origin) {
|
|
114
|
+
const topology = await this.getSessionTopology(walletAddress);
|
|
115
|
+
const newTopology = SessionConfig.addExplicitSession(topology, {
|
|
116
|
+
...permissions,
|
|
117
|
+
signer: sessionAddress,
|
|
118
|
+
});
|
|
119
|
+
return this.prepareSessionUpdate(walletAddress, newTopology, origin);
|
|
120
|
+
}
|
|
121
|
+
async removeExplicitSession(walletAddress, sessionAddress, origin) {
|
|
122
|
+
const topology = await this.getSessionTopology(walletAddress);
|
|
123
|
+
const newTopology = SessionConfig.removeExplicitSession(topology, sessionAddress);
|
|
124
|
+
if (!newTopology) {
|
|
125
|
+
throw new Error('Session not found');
|
|
126
|
+
}
|
|
127
|
+
return this.prepareSessionUpdate(walletAddress, newTopology, origin);
|
|
128
|
+
}
|
|
129
|
+
async addBlacklistAddress(walletAddress, address, origin) {
|
|
130
|
+
const topology = await this.getSessionTopology(walletAddress);
|
|
131
|
+
const newTopology = SessionConfig.addToImplicitBlacklist(topology, address);
|
|
132
|
+
return this.prepareSessionUpdate(walletAddress, newTopology, origin);
|
|
133
|
+
}
|
|
134
|
+
async removeBlacklistAddress(walletAddress, address, origin) {
|
|
135
|
+
const topology = await this.getSessionTopology(walletAddress);
|
|
136
|
+
const newTopology = SessionConfig.removeFromImplicitBlacklist(topology, address);
|
|
137
|
+
return this.prepareSessionUpdate(walletAddress, newTopology, origin);
|
|
138
|
+
}
|
|
139
|
+
async prepareSessionUpdate(walletAddress, topology, origin = 'wallet-webapp') {
|
|
140
|
+
// Store the new configuration
|
|
141
|
+
const tree = SessionConfig.sessionsTopologyToConfigurationTree(topology);
|
|
142
|
+
await this.shared.sequence.stateProvider.saveTree(tree);
|
|
143
|
+
const newImageHash = GenericTree.hash(tree);
|
|
144
|
+
// Find the session manager in the old configuration
|
|
145
|
+
const { modules } = await this.shared.modules.wallets.getConfigurationParts(walletAddress);
|
|
146
|
+
const managerLeaf = modules.find((leaf) => this.sessionManagerAddresses.some((addr) => Address.isEqual(addr, leaf.address)));
|
|
147
|
+
if (!managerLeaf) {
|
|
148
|
+
// Missing. Add it
|
|
149
|
+
modules.push({
|
|
150
|
+
...ManagerOptionsDefaults.defaultSessionsTopology,
|
|
151
|
+
imageHash: newImageHash,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Update the configuration to use the new session manager image hash
|
|
156
|
+
managerLeaf.imageHash = newImageHash;
|
|
157
|
+
}
|
|
158
|
+
return this.shared.modules.wallets.requestConfigurationUpdate(walletAddress, {
|
|
159
|
+
modules,
|
|
160
|
+
}, Actions.SessionUpdate, origin);
|
|
161
|
+
}
|
|
162
|
+
async completeSessionUpdate(requestId) {
|
|
163
|
+
const sigRequest = await this.shared.modules.signatures.get(requestId);
|
|
164
|
+
if (sigRequest.action !== 'session-update' || !Payload.isConfigUpdate(sigRequest.envelope.payload)) {
|
|
165
|
+
throw new Error('Invalid action');
|
|
166
|
+
}
|
|
167
|
+
return this.shared.modules.wallets.completeConfigurationUpdate(requestId);
|
|
168
|
+
}
|
|
169
|
+
}
|