11import { v1 , commons as v2commons } from '@0xsequence/v2core'
2- import { WalletV1 } from '@0xsequence/v2wallet'
3- import { Config as V3Config , Context as V3Context , Extensions as V3Extensions } from '@0xsequence/wallet-primitives'
2+ import { State } from '@0xsequence/wallet-core'
3+ import {
4+ Payload ,
5+ Config as V3Config ,
6+ Context as V3Context ,
7+ Extensions as V3Extensions ,
8+ } from '@0xsequence/wallet-primitives'
49import { AbiFunction , Address , Hex } from 'ox'
5- import { SignedMigration , UnsignedMigration , VersionedContext } from '../../migrator .js'
6- import { Migration } from '../index.js'
10+ import { UnsignedMigration , VersionedContext } from '../../types .js'
11+ import { MigrationEncoder } from '../index.js'
712import { createDefaultV3Topology } from '../v3/config.js'
813
914// uint160(keccak256("org.sequence.sdk.migration.v1v3.space.nonce"))
@@ -17,25 +22,31 @@ export type ConvertOptions = {
1722 extensions ?: V3Extensions . Extensions
1823}
1924
20- export class Migration_v1v3 implements Migration < v1 . config . WalletConfig , V3Config . Config , ConvertOptions > {
25+ export type PrepareOptions = {
26+ space ?: bigint
27+ }
28+
29+ export class MigrationEncoder_v1v3
30+ implements MigrationEncoder < v1 . config . WalletConfig , V3Config . Config , ConvertOptions , PrepareOptions >
31+ {
2132 fromVersion = 1
2233 toVersion = 3
2334
24- async convertConfig ( v1Config : v1 . config . WalletConfig , options : ConvertOptions ) : Promise < V3Config . Config > {
25- const signerLeaves : V3Config . SignerLeaf [ ] = v1Config . signers . map ( ( signer ) => ( {
35+ async convertConfig ( fromConfig : v1 . config . WalletConfig , options : ConvertOptions ) : Promise < V3Config . Config > {
36+ const signerLeaves : V3Config . SignerLeaf [ ] = fromConfig . signers . map ( ( signer ) => ( {
2637 type : 'signer' ,
2738 address : Address . from ( signer . address ) ,
2839 weight : BigInt ( signer . weight ) ,
2940 } ) )
3041 const v1NestedTopology = V3Config . flatLeavesToTopology ( signerLeaves )
31- const v3Config : V3Config . Config = {
42+ return {
3243 threshold : 1n ,
3344 checkpoint : 0n ,
3445 topology : [
3546 {
3647 type : 'nested' ,
3748 weight : 1n ,
38- threshold : BigInt ( v1Config . threshold ) ,
49+ threshold : BigInt ( fromConfig . threshold ) ,
3950 tree : v1NestedTopology ,
4051 } ,
4152 {
@@ -46,80 +57,101 @@ export class Migration_v1v3 implements Migration<v1.config.WalletConfig, V3Confi
4657 } ,
4758 ] ,
4859 }
49- return v3Config
5060 }
5161
5262 async prepareMigration (
5363 walletAddress : Address . Address ,
5464 contexts : VersionedContext ,
5565 toConfig : V3Config . Config ,
66+ options : PrepareOptions ,
5667 ) : Promise < UnsignedMigration > {
5768 const v3Context = contexts [ 3 ] || V3Context . Rc3
5869 if ( ! V3Context . isContext ( v3Context ) ) {
5970 throw new Error ( 'Invalid context' )
6071 }
6172
62- const nonce = v2commons . transaction . encodeNonce ( MIGRATION_V1_V3_NONCE_SPACE , 0 )
73+ const space = options ?. space ?? BigInt ( MIGRATION_V1_V3_NONCE_SPACE )
74+ const nonce = 0n // Nonce must be unused
75+ // const v2Nonce = v2commons.transaction.encodeNonce(space, nonce)
6376
6477 // Update implementation to v3
6578 const updateImplementationAbi = AbiFunction . from ( 'function updateImplementation(address implementation)' )
66- const updateImplementationTx = {
79+ const updateImplementationTx : Payload . Call = {
6780 to : walletAddress ,
6881 data : AbiFunction . encodeData ( updateImplementationAbi , [ v3Context . stage2 ] ) ,
82+ value : 0n ,
83+ gasLimit : 0n ,
84+ delegateCall : false ,
85+ onlyFallback : false ,
86+ behaviorOnError : 'revert' ,
6987 }
7088 // Update configuration to v3
71- const v3ImageHash = Hex . fromBytes ( V3Config . hashConfiguration ( toConfig ) )
89+ const toImageHash = Hex . fromBytes ( V3Config . hashConfiguration ( toConfig ) )
7290 const updateImageHashAbi = AbiFunction . from ( 'function updateImageHash(bytes32 imageHash)' )
73- const updateImageHashTx = {
91+ const updateImageHashTx : Payload . Call = {
7492 to : walletAddress ,
75- data : AbiFunction . encodeData ( updateImageHashAbi , [ v3ImageHash ] ) ,
93+ data : AbiFunction . encodeData ( updateImageHashAbi , [ toImageHash ] ) ,
94+ value : 0n ,
95+ gasLimit : 0n ,
96+ delegateCall : false ,
97+ onlyFallback : false ,
98+ behaviorOnError : 'revert' ,
99+ }
100+
101+ const payload : Payload . Calls = {
102+ type : 'call' ,
103+ space,
104+ nonce,
105+ calls : [ updateImplementationTx , updateImageHashTx ] ,
76106 }
77107
78108 return {
79- transactions : [ updateImplementationTx , updateImageHashTx ] ,
109+ payload ,
80110 fromVersion : this . fromVersion ,
81111 toVersion : this . toVersion ,
82- nonce ,
112+ toConfig ,
83113 }
84114 }
85115
86- /**
87- * Signs a migration with a wallet
88- * @notice V1 Wallets must call this method for each chain they are migrating on
89- * @param migration The unsigned migration to sign
90- * @param wallet The wallet to sign the migration with
91- * @returns The signed migration
92- */
93- //FIXME Remove this function. Signing is not a responsibility of the migration class.
94- async signMigration ( migration : UnsignedMigration , wallet : WalletV1 ) : Promise < SignedMigration > {
95- const { address } = await this . decodeTransactions ( migration . transactions )
96- if ( address !== wallet . address ) {
97- throw new Error ( 'Wallet address does not match migration address' )
116+ async toTransactionData ( migration : State . Migration ) : Promise < { to : Address . Address ; data : Hex . Hex } > {
117+ const { payload, signature, chainId } = migration
118+ const walletAddress = payload . calls [ 0 ] ! . to
119+ const v2Nonce = v2commons . transaction . encodeNonce ( payload . space , payload . nonce )
120+ const transactions = payload . calls . map ( ( tx ) => ( {
121+ to : tx . to ,
122+ data : tx . data ,
123+ gasLimit : tx . gasLimit ,
124+ revertOnError : tx . behaviorOnError === 'revert' ,
125+ } ) )
126+ const digest = v2commons . transaction . digestOfTransactions ( v2Nonce , transactions )
127+ const txBundle : v2commons . transaction . SignedTransactionBundle = {
128+ entrypoint : walletAddress ,
129+ transactions,
130+ nonce : v2Nonce ,
131+ chainId,
132+ signature,
133+ intent : {
134+ id : digest ,
135+ wallet : walletAddress ,
136+ } ,
98137 }
99- const txBundle : v2commons . transaction . TransactionBundle = {
100- entrypoint : wallet . address ,
101- transactions : migration . transactions . map ( ( tx ) => ( {
102- to : tx . to ,
103- data : tx . data ,
104- gasLimit : 0n ,
105- revertOnError : true ,
106- } ) ) ,
107- nonce : migration . nonce ,
138+ const encodedData = v2commons . transaction . encodeBundleExecData ( txBundle )
139+ Hex . assert ( encodedData )
140+ return {
141+ to : walletAddress ,
142+ data : encodedData ,
108143 }
109- const { signature } = await wallet . signTransactionBundle ( txBundle )
110- Hex . assert ( signature )
111- return { ...migration , signature }
112144 }
113145
114- async decodeTransactions ( transactions : UnsignedMigration [ 'transactions' ] ) : Promise < {
146+ async decodePayload ( payload : Payload . Calls ) : Promise < {
115147 address : Address . Address
116148 toImageHash : Hex . Hex
117149 } > {
118- if ( transactions . length !== 2 ) {
119- throw new Error ( 'Invalid transactions ' )
150+ if ( payload . calls . length !== 2 ) {
151+ throw new Error ( 'Invalid calls ' )
120152 }
121- const tx1 = transactions [ 0 ] !
122- const tx2 = transactions [ 1 ] !
153+ const tx1 = payload . calls [ 0 ] !
154+ const tx2 = payload . calls [ 1 ] !
123155 if ( tx1 . to !== tx2 . to ) {
124156 throw new Error ( 'Invalid to address' )
125157 }
0 commit comments