Skip to content

Commit 1d15e59

Browse files
authored
Merge pull request #735 from 0xsequence/recovery-topology
Recovery extension
2 parents 6a44e06 + 35877ad commit 1d15e59

File tree

29 files changed

+1664
-279
lines changed

29 files changed

+1664
-279
lines changed

.github/workflows/tests.yml

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ jobs:
2626
steps:
2727
- uses: actions/checkout@v4
2828
- uses: ./.github/actions/install-dependencies
29+
- name: Install Foundry
30+
uses: foundry-rs/foundry-toolchain@v1
31+
with:
32+
version: nightly
33+
- name: Start Anvil in background
34+
run: anvil --fork-url https://nodes.sequence.app/arbitrum &
2935
- run: pnpm test
3036

3137
# NOTE: if you'd like to see example of how to run

packages/wallet/core/src/state/cached.ts

+24
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,28 @@ export class Cached implements Provider {
206206
saveDeploy(imageHash: Hex.Hex, context: Context.Context): MaybePromise<void> {
207207
return this.args.source.saveDeploy(imageHash, context)
208208
}
209+
210+
async getPayload(opHash: Hex.Hex): Promise<
211+
| {
212+
chainId: bigint
213+
payload: Payload.Parented
214+
wallet: Address.Address
215+
}
216+
| undefined
217+
> {
218+
const cached = await this.args.cache.getPayload(opHash)
219+
if (cached) {
220+
return cached
221+
}
222+
223+
const source = await this.args.source.getPayload(opHash)
224+
if (source) {
225+
await this.args.cache.savePayload(source.wallet, source.payload, source.chainId)
226+
}
227+
return source
228+
}
229+
230+
savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: bigint): MaybePromise<void> {
231+
return this.args.source.savePayload(wallet, payload, chainId)
232+
}
209233
}

packages/wallet/core/src/state/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ export interface Reader {
4949
): MaybePromise<Array<{ imageHash: Hex.Hex; signature: Signature.RawSignature }>>
5050

5151
getTree(rootHash: Hex.Hex): MaybePromise<GenericTree.Tree | undefined>
52+
getPayload(
53+
opHash: Hex.Hex,
54+
): MaybePromise<{ chainId: bigint; payload: Payload.Parented; wallet: Address.Address } | undefined>
5255
}
5356

5457
export interface Writer {
@@ -71,6 +74,7 @@ export interface Writer {
7174

7275
saveConfiguration(config: Config.Config): MaybePromise<void>
7376
saveDeploy(imageHash: Hex.Hex, context: Context.Context): MaybePromise<void>
77+
savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: bigint): MaybePromise<void>
7478
}
7579

7680
export type MaybePromise<T> = T | Promise<T>

packages/wallet/core/src/state/local/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,18 @@ export class Provider implements ProviderInterface {
404404
context,
405405
)
406406
}
407+
408+
async getPayload(
409+
opHash: Hex.Hex,
410+
): Promise<{ chainId: bigint; payload: Payload.Parented; wallet: Address.Address } | undefined> {
411+
const data = await this.store.loadPayloadOfSubdigest(opHash)
412+
return data ? { chainId: data.chainId, payload: data.content, wallet: data.wallet } : undefined
413+
}
414+
415+
savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: bigint): Promise<void> {
416+
const subdigest = Hex.fromBytes(Payload.hash(wallet, chainId, payload))
417+
return this.store.savePayloadOfSubdigest(subdigest, { content: payload, chainId, wallet })
418+
}
407419
}
408420

409421
export * from './memory.js'

packages/wallet/core/src/state/remote/dev-http.ts

+22
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,26 @@ export class DevHttpProvider implements Provider {
228228
saveDeploy(imageHash: Hex.Hex, context: Context.Context): Promise<void> {
229229
return this.request<void>('POST', '/deploy', { imageHash, context })
230230
}
231+
232+
async getPayload(opHash: Hex.Hex): Promise<
233+
| {
234+
chainId: bigint
235+
payload: Payload.Parented
236+
wallet: Address.Address
237+
}
238+
| undefined
239+
> {
240+
return this.request<
241+
| {
242+
chainId: bigint
243+
payload: Payload.Parented
244+
wallet: Address.Address
245+
}
246+
| undefined
247+
>('GET', `/payload/${opHash}`)
248+
}
249+
250+
async savePayload(wallet: Address.Address, payload: Payload.Parented, chainId: bigint): Promise<void> {
251+
return this.request<void>('POST', '/payload', { wallet, payload, chainId })
252+
}
231253
}

packages/wallet/core/src/wallet.ts

+18-11
Original file line numberDiff line numberDiff line change
@@ -203,23 +203,30 @@ export class Wallet {
203203
}
204204
}
205205

206+
async getNonce(provider: Provider.Provider, space: bigint): Promise<bigint> {
207+
const result = await provider.request({
208+
method: 'eth_call',
209+
params: [{ to: this.address, data: AbiFunction.encodeData(Constants.READ_NONCE, [space]) }],
210+
})
211+
212+
if (result === '0x' || result.length === 0) {
213+
return 0n
214+
}
215+
216+
return BigInt(result)
217+
}
218+
206219
async prepareTransaction(
207220
provider: Provider.Provider,
208221
calls: Payload.Call[],
209222
options?: { space?: bigint },
210223
): Promise<Envelope.Envelope<Payload.Calls>> {
211224
const space = options?.space ?? 0n
212-
const status = await this.getStatus(provider)
213225

214-
let nonce: bigint = 0n
215-
if (status.isDeployed) {
216-
nonce = BigInt(
217-
await provider.request({
218-
method: 'eth_call',
219-
params: [{ to: this.address, data: AbiFunction.encodeData(Constants.READ_NONCE, [space]) }],
220-
}),
221-
)
222-
}
226+
const [chainId, nonce] = await Promise.all([
227+
provider.request({ method: 'eth_chainId' }),
228+
this.getNonce(provider, space),
229+
])
223230

224231
return {
225232
payload: {
@@ -228,7 +235,7 @@ export class Wallet {
228235
nonce,
229236
calls,
230237
},
231-
...(await this.prepareBlankEnvelope(status.chainId ?? 0n)),
238+
...(await this.prepareBlankEnvelope(BigInt(chainId))),
232239
}
233240
}
234241

packages/wallet/primitives-cli/src/subcommands/payload.ts

+5-95
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@ import type { CommandModule } from 'yargs'
33
import { Payload } from '@0xsequence/wallet-primitives'
44
import { fromPosOrStdin } from '../utils.js'
55

6-
export const KIND_TRANSACTIONS = 0x00
7-
const KIND_MESSAGE = 0x01
8-
const KIND_CONFIG_UPDATE = 0x02
9-
const KIND_DIGEST = 0x03
10-
11-
const BEHAVIOR_IGNORE_ERROR = 0x00
12-
const BEHAVIOR_REVERT_ON_ERROR = 0x01
13-
const BEHAVIOR_ABORT_ON_ERROR = 0x02
14-
156
const CallAbi = [
167
{ type: 'address', name: 'to' },
178
{ type: 'uint256', name: 'value' },
@@ -38,98 +29,17 @@ export const DecodedAbi = [
3829
{ type: 'address[]', name: 'parentWallets' },
3930
]
4031

41-
export interface SolidityDecoded {
42-
kind: number
43-
noChainId: boolean
44-
calls: SolidityCall[]
45-
space: bigint
46-
nonce: bigint
47-
message: string
48-
imageHash: string
49-
digest: string
50-
parentWallets: string[]
51-
}
52-
53-
interface SolidityCall {
54-
to: string
55-
value: bigint
56-
data: string
57-
gasLimit: bigint
58-
delegateCall: boolean
59-
onlyFallback: boolean
60-
behaviorOnError: bigint
61-
}
62-
63-
function behaviorOnError(behavior: number): 'ignore' | 'revert' | 'abort' {
64-
switch (behavior) {
65-
case BEHAVIOR_IGNORE_ERROR:
66-
return 'ignore'
67-
case BEHAVIOR_REVERT_ON_ERROR:
68-
return 'revert'
69-
case BEHAVIOR_ABORT_ON_ERROR:
70-
return 'abort'
71-
default:
72-
throw new Error(`Unknown behavior: ${behavior}`)
73-
}
74-
}
75-
7632
export async function doConvertToAbi(_payload: string): Promise<string> {
7733
// Not implemented yet, but following the pattern
7834
throw new Error('Not implemented')
7935
}
8036

81-
export function solidityEncodedToParentedPayload(decoded: SolidityDecoded): Payload.Parented {
82-
if (decoded.kind === KIND_TRANSACTIONS) {
83-
return {
84-
type: 'call',
85-
nonce: decoded.nonce,
86-
space: decoded.space,
87-
calls: decoded.calls.map((call) => ({
88-
to: Address.from(call.to),
89-
value: call.value,
90-
data: call.data as `0x${string}`,
91-
gasLimit: call.gasLimit,
92-
delegateCall: call.delegateCall,
93-
onlyFallback: call.onlyFallback,
94-
behaviorOnError: behaviorOnError(Number(call.behaviorOnError)),
95-
})),
96-
parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)),
97-
}
98-
}
99-
100-
if (decoded.kind === KIND_MESSAGE) {
101-
return {
102-
type: 'message',
103-
message: decoded.message as `0x${string}`,
104-
parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)),
105-
}
106-
}
107-
108-
if (decoded.kind === KIND_CONFIG_UPDATE) {
109-
return {
110-
type: 'config-update',
111-
imageHash: decoded.imageHash as `0x${string}`,
112-
parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)),
113-
}
114-
}
115-
116-
if (decoded.kind === KIND_DIGEST) {
117-
return {
118-
type: 'digest',
119-
digest: decoded.digest as `0x${string}`,
120-
parentWallets: decoded.parentWallets.map((wallet) => Address.from(wallet)),
121-
}
122-
}
123-
124-
throw new Error('Not implemented')
125-
}
126-
12737
export async function doConvertToPacked(payload: string, wallet?: string): Promise<string> {
128-
const decodedPayload = solidityEncodedToParentedPayload(
38+
const decodedPayload = Payload.fromAbiFormat(
12939
AbiParameters.decode(
13040
[{ type: 'tuple', name: 'payload', components: DecodedAbi }],
13141
payload as Hex.Hex,
132-
)[0] as unknown as SolidityDecoded,
42+
)[0] as unknown as Payload.SolidityDecoded,
13343
)
13444

13545
if (Payload.isCalls(decodedPayload)) {
@@ -144,7 +54,7 @@ export async function doConvertToJson(payload: string): Promise<string> {
14454
const decoded = AbiParameters.decode(
14555
[{ type: 'tuple', name: 'payload', components: DecodedAbi }],
14656
payload as Hex.Hex,
147-
)[0] as unknown as SolidityDecoded
57+
)[0] as unknown as Payload.SolidityDecoded
14858

14959
const json = JSON.stringify(decoded)
15060
return json
@@ -154,9 +64,9 @@ export async function doHash(wallet: string, chainId: bigint, payload: string):
15464
const decoded = AbiParameters.decode(
15565
[{ type: 'tuple', name: 'payload', components: DecodedAbi }],
15666
payload as Hex.Hex,
157-
)[0] as unknown as SolidityDecoded
67+
)[0] as unknown as Payload.SolidityDecoded
15868

159-
return Hex.from(Payload.hash(Address.from(wallet), chainId, solidityEncodedToParentedPayload(decoded)))
69+
return Hex.from(Payload.hash(Address.from(wallet), chainId, Payload.fromAbiFormat(decoded)))
16070
}
16171

16272
const payloadCommand: CommandModule = {

packages/wallet/primitives/src/config.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,10 @@ export function findSignerLeaf(
161161
}
162162

163163
export function getWeight(
164-
topology: RawTopology | RawConfig,
164+
topology: RawTopology | RawConfig | Config,
165165
canSign: (signer: SignerLeaf | SapientSignerLeaf) => boolean,
166166
): { weight: bigint; maxWeight: bigint } {
167-
topology = isRawConfig(topology) ? topology.topology : topology
167+
topology = isRawConfig(topology) || isConfig(topology) ? topology.topology : topology
168168

169169
if (isSignedSignerLeaf(topology)) {
170170
return { weight: topology.weight, maxWeight: topology.weight }

packages/wallet/primitives/src/extensions/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { Address } from 'ox'
22

33
export type Extensions = {
44
passkeys: Address.Address
5+
recovery: Address.Address
56
}
67

78
export const Dev1: Extensions = {
89
passkeys: '0x8f26281dB84C18aAeEa8a53F94c835393229d296',
10+
recovery: '0xd98da48C4FF9c19742eA5856A277424557C863a6',
911
}
1012

1113
export * as Passkeys from './passkeys.js'

0 commit comments

Comments
 (0)