Skip to content

Commit 4a21126

Browse files
committed
feat: ini txn bundle
1 parent 80c14b5 commit 4a21126

File tree

2 files changed

+153
-24
lines changed

2 files changed

+153
-24
lines changed

intent_config.go

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,50 +10,95 @@ import (
1010
v2 "github.com/0xsequence/go-sequence/core/v2"
1111
)
1212

13-
// createIntentBundle creates a bundle of transactions with the initial nonce 0
14-
func createIntentBundle(txns []*Transaction) (common.Hash, error) {
13+
// `CreateIntentBundle` creates a bundle of transactions with the initial nonce 0
14+
func CreateIntentBundle(txns []*Transaction) (Transaction, error) {
1515
bundle := Transaction{
1616
DelegateCall: false,
1717
RevertOnError: true,
1818
Transactions: txns,
1919
Nonce: big.NewInt(0),
2020
}
2121

22-
// Compute the digest for the bundle
23-
digest, err := bundle.Digest()
24-
if err != nil {
25-
return common.Hash{}, fmt.Errorf("failed to compute bundle digest: %w", err)
22+
return bundle, nil
23+
}
24+
25+
// CreateIntentSubdigestLeaves iterates over the provided transactions,
26+
// validates that each transaction meets the following criteria:
27+
// - DelegateCall must be false,
28+
// - RevertOnError must be true,
29+
// - Nonce must be non-nil and equal to 0.
30+
//
31+
// For each valid transaction, it computes the digest and creates a new WalletConfigTreeSubdigestLeaf.
32+
func CreateIntentSubdigestLeaves(txns []*Transaction) ([]*v2.WalletConfigTreeSubdigestLeaf, error) {
33+
var leaves []*v2.WalletConfigTreeSubdigestLeaf
34+
35+
for i, tx := range txns {
36+
// Validate the transaction fields:
37+
if tx.DelegateCall {
38+
return nil, fmt.Errorf("transaction at index %d: DelegateCall must be false", i)
39+
}
40+
if !tx.RevertOnError {
41+
return nil, fmt.Errorf("transaction at index %d: RevertOnError must be true", i)
42+
}
43+
if tx.Nonce == nil {
44+
return nil, fmt.Errorf("transaction at index %d: Nonce is nil", i)
45+
}
46+
if tx.Nonce.Cmp(big.NewInt(0)) != 0 {
47+
return nil, fmt.Errorf("transaction at index %d: Nonce must be 0", i)
48+
}
49+
50+
// Create the intent bundle.
51+
bundle, err := CreateIntentBundle(txns)
52+
if err != nil {
53+
return nil, fmt.Errorf("failed to create intent bundle: %w", err)
54+
}
55+
56+
// Compute digest of the transaction.
57+
digest, err := bundle.Digest()
58+
if err != nil {
59+
return nil, fmt.Errorf("failed to compute digest for transaction at index %d: %w", i, err)
60+
}
61+
62+
// Create a subdigest leaf with the computed digest.
63+
leaf := &v2.WalletConfigTreeSubdigestLeaf{
64+
Subdigest: core.Subdigest{Hash: digest},
65+
}
66+
leaves = append(leaves, leaf)
2667
}
2768

28-
return digest, nil
69+
return leaves, nil
2970
}
3071

31-
// `CreateIntentConfiguration` creates a wallet configuration where the intent's transactions are grouped into the initial subdigest
72+
// CreateIntentConfiguration creates a wallet configuration where the intent's transactions
73+
// are grouped into the initial subdigest. It uses the subdigest leaves constructed from the txns.
3274
func CreateIntentConfiguration(mainSigner common.Address, txns []*Transaction) (*v2.WalletConfig, error) {
33-
// Create the bundle digest
34-
digest, err := createIntentBundle(txns)
75+
// Create the subdigest leaves from the transactions.
76+
leaves, err := CreateIntentSubdigestLeaves(txns)
3577
if err != nil {
3678
return nil, err
3779
}
3880

39-
// Create the main signer leaf (with weight 1)
81+
// Convert the slice of subdigest leaves into a slice of v2.WalletConfigTree.
82+
var treeNodes []v2.WalletConfigTree
83+
for _, leaf := range leaves {
84+
treeNodes = append(treeNodes, leaf)
85+
}
86+
87+
// Create the main signer leaf (with weight 1).
4088
mainSignerLeaf := &v2.WalletConfigTreeAddressLeaf{
4189
Weight: 1,
4290
Address: mainSigner,
4391
}
4492

45-
// Create a leaf node from the computed subdigest.
46-
subdigestLeaf := &v2.WalletConfigTreeSubdigestLeaf{
47-
Subdigest: core.Subdigest{Hash: digest},
48-
}
49-
50-
// Construct the new wallet config using the main signer (as the first leaf) and a tree node for the subdigest.
93+
// Construct the new wallet config using:
94+
// • the main signer leaf as the left branch, and
95+
// • a nested tree node containing the subdigest leaves as the right branch.
5196
config := &v2.WalletConfig{
5297
Threshold_: 1,
5398
Checkpoint_: 0,
5499
Tree: v2.WalletConfigTreeNodes(
55100
mainSignerLeaf,
56-
v2.WalletConfigTreeNodes(subdigestLeaf),
101+
v2.WalletConfigTreeNodes(treeNodes...),
57102
),
58103
}
59104

intent_config_test.go

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,45 @@ import (
1515
"github.com/stretchr/testify/require"
1616
)
1717

18+
func TestCreateIntentSubdigestLeaves_Valid(t *testing.T) {
19+
// Create a valid transaction (stx) with required fields.
20+
stx := &sequence.Transaction{
21+
DelegateCall: false,
22+
RevertOnError: true,
23+
Nonce: big.NewInt(0),
24+
// other required fields for stx (if any) are assumed to be set.
25+
}
26+
27+
// Use the valid transaction in a slice.
28+
txns := []*sequence.Transaction{stx}
29+
30+
leaves, err := sequence.CreateIntentSubdigestLeaves(txns)
31+
require.NoError(t, err)
32+
require.Len(t, leaves, 1, "expected one subdigest leaf")
33+
34+
// Verify that the leaf's digest matches the transaction's digest.
35+
digest, err := stx.Digest()
36+
require.NoError(t, err)
37+
require.Equal(t, digest, leaves[0].Subdigest.Hash, "digests do not match")
38+
}
39+
40+
func TestCreateIntentConfiguration_Valid(t *testing.T) {
41+
// Create a valid transaction.
42+
stx := &sequence.Transaction{
43+
DelegateCall: false,
44+
RevertOnError: true,
45+
Nonce: big.NewInt(0),
46+
}
47+
txns := []*sequence.Transaction{stx}
48+
49+
// Use a valid main signer address.
50+
mainSigner := common.HexToAddress("0x1111111111111111111111111111111111111111")
51+
52+
config, err := sequence.CreateIntentConfiguration(mainSigner, txns)
53+
require.NoError(t, err)
54+
require.NotNil(t, config)
55+
}
56+
1857
func TestCreateIntentConfigurationSignature(t *testing.T) {
1958
// Create test wallets
2059
eoa1, err := ethwallet.NewWalletFromRandomEntropy()
@@ -26,8 +65,10 @@ func TestCreateIntentConfigurationSignature(t *testing.T) {
2665
assert.NoError(t, err)
2766

2867
stx := &sequence.Transaction{
29-
To: callmockContract.Address,
30-
Data: calldata,
68+
To: callmockContract.Address,
69+
Data: calldata,
70+
RevertOnError: true,
71+
Nonce: big.NewInt(0),
3172
}
3273

3374
t.Run("signature matches subdigest", func(t *testing.T) {
@@ -96,13 +137,17 @@ func TestCreateIntentConfigurationSignature(t *testing.T) {
96137
require.NoError(t, err)
97138

98139
tx1 := &sequence.Transaction{
99-
To: callmockContract.Address,
100-
Data: calldata1,
140+
To: callmockContract.Address,
141+
Data: calldata1,
142+
RevertOnError: true,
143+
Nonce: big.NewInt(0),
101144
}
102145

103146
tx2 := &sequence.Transaction{
104-
To: callmockContract.Address,
105-
Data: calldata2,
147+
To: callmockContract.Address,
148+
Data: calldata2,
149+
RevertOnError: true,
150+
Nonce: big.NewInt(0),
106151
}
107152

108153
// Create signatures for both transactions
@@ -128,3 +173,42 @@ func TestCreateIntentConfigurationSignature(t *testing.T) {
128173
require.Equal(t, sig1, sig2, "same transactions should produce same signatures")
129174
})
130175
}
176+
177+
func TestCreateIntentConfigurationSignature_MultipleTransactions(t *testing.T) {
178+
// Create test wallets
179+
eoa1, err := ethwallet.NewWalletFromRandomEntropy()
180+
require.NoError(t, err)
181+
182+
// Create two valid transactions with different Data fields so their digests differ.
183+
stx1 := &sequence.Transaction{
184+
DelegateCall: false,
185+
RevertOnError: true,
186+
Nonce: big.NewInt(0),
187+
Data: []byte("transaction1"),
188+
}
189+
stx2 := &sequence.Transaction{
190+
DelegateCall: false,
191+
RevertOnError: true,
192+
Nonce: big.NewInt(0),
193+
Data: []byte("transaction2"),
194+
}
195+
txns := []*sequence.Transaction{stx1, stx2}
196+
197+
// Create a signature
198+
sig, err := sequence.CreateIntentConfigurationSignature(eoa1.Address(), txns)
199+
require.NoError(t, err)
200+
201+
// Convert the full signature into a hex string.
202+
sigHex := common.Bytes2Hex(sig)
203+
204+
// Create the bundle from the transactions
205+
bundle, err := sequence.CreateIntentBundle(txns)
206+
require.NoError(t, err)
207+
208+
// Compute the digest of the bundle
209+
bundleDigest, err := bundle.Digest()
210+
require.NoError(t, err)
211+
212+
// Expect that the signature (in hex) contains the substrings of both transactions' digests.
213+
assert.Contains(t, sigHex, bundleDigest.Hex()[2:], "signature should contain stx1 digest")
214+
}

0 commit comments

Comments
 (0)