Skip to content

Commit

Permalink
Merge pull request #39 from mycel-labs/add-getting-started-with-sign
Browse files Browse the repository at this point in the history
Add getting started with withdraw
  • Loading branch information
peaceandwhisky authored Sep 30, 2024
2 parents 73936a2 + 54e5eed commit 51e9e74
Show file tree
Hide file tree
Showing 6 changed files with 528 additions and 14 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PRIVATE_KEY=your_private_key
TA_STORE_CONTRACT_ADDRESS=0x8F7B5945D5213575b0fAdF876701b79B359c977b
RPC_URL=https://rpc.toliman.suave.flashbots.net
WITHDRAW_TESTNET_RPC=for_signTx_network_rpc
35 changes: 26 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ This project implements a transferable account system using smart contracts.

## Getting Started

This guide will help you set up the Astraeus API server on the Suave Toliman Testnet to easily use Transferable Accounts (TA). You will learn how to create, approve, and transfer TAs using the API server.
This guide will help you set up the Astraeus API server on the Suave Toliman Testnet to easily use Transferable Accounts (TA). You will learn how to create, approve, and transfer TAs using the API server. Additionally, you will learn how to sign transactions on external chains using TAs.

### Prerequisites

Expand Down Expand Up @@ -60,10 +60,10 @@ If you do not have TEETH tokens, you can obtain them from the [Toliman Testnet F

Generate signatures for both of your accounts.
```
go run scripts/utils/generate_timed_signature/main.go 1726946480(validFor) 10c62a6364b1730ec101460c871952403631adb66fe7e043914c7d0056ca8e94(your_private_key)
go run scripts/utils/generate_timed_signature/main.go 1759244400(validFor) 10c62a6364b1730ec101460c871952403631adb66fe7e043914c7d0056ca8e94(your_private_key)
Address: 1b1374742cb5f84b1ef167db57236350380084e1
Message Hash: 32948247c695a2545f9b35c040a293f1c6cd300062e9d7abdf0b3ed2a7b596d1
Signature: 50346a31ad859f211294496e01083dcb85803bb27923b8d256756c71bdbfe36e1e89741215f58fd4bb8db42f04775303e60748b99f10c64e189d1f585d6b77531c
Message Hash: 2f9cc010a830e69220428772a000f8c057229d5c8e668ec86d5d74cf07cffcd2
Signature: 31f8e01e19bfb2bf66f01e34006eef1ac9a3384a71832779e768b80c7a5f1b3c6db26a20a99abce0d612046e4e0b6a73c1da892f0f98a41155a9a4998f29dcbd1c
```

5. **Create Account Request to API Server**
Expand All @@ -72,13 +72,13 @@ If you do not have TEETH tokens, you can obtain them from the [Toliman Testnet F
```
curl -X POST http://localhost:8080/v1/accounts -d '{
"proof": {
"validFor": 1726946480,
"messageHash": "32948247c695a2545f9b35c040a293f1c6cd300062e9d7abdf0b3ed2a7b596d1",
"signature": "50346a31ad859f211294496e01083dcb85803bb27923b8d256756c71bdbfe36e1e89741215f58fd4bb8db42f04775303e60748b99f10c64e189d1f585d6b77531c",
"validFor": 1759244400,
"messageHash": "2f9cc010a830e69220428772a000f8c057229d5c8e668ec86d5d74cf07cffcd2",
"signature": "31f8e01e19bfb2bf66f01e34006eef1ac9a3384a71832779e768b80c7a5f1b3c6db26a20a99abce0d612046e4e0b6a73c1da892f0f98a41155a9a4998f29dcbd1c",
"signer": "1b1374742cb5f84b1ef167db57236350380084e1"
}
}'
{"txHash":"0x554f24eeeb38bd54b64551863c6ab1878b559d390ac14ad26a4b1f3e07beaf5d", "accountId":"0xb06e9fd4baf654208e7886284cdcdab2"}
{"txHash":"0x87deac0a0982fcc41bc915f01c4924f27c1b1b16882d04e64d0f3e1442314fea", "accountId":"0x65c987ba099153e19942c809e2289120"}
```

Once the `txHash` is displayed, the account creation is complete. The displayed `accountId` is the ID of the account you created.
Expand Down Expand Up @@ -127,9 +127,26 @@ If you do not have TEETH tokens, you can obtain them from the [Toliman Testnet F

Once these steps are completed, the ownership of the TA will be transferred.

For more details on API requests, refer to the documentation at:
For more details on API requests, refer to the documentation at:
[API Documentation](https://github.com/mycel-labs/astraeus/blob/main/docs/api.md)

8. **Sign from Transferable Account Request to API Server**

If you own a TA and hold assets on an external chain with that TA, you can create a Tx to send assets from the TA account and broadcast it to the external chain.

Before executing the command, make sure to set the RPC of the chain to broadcast the Tx as `WITHDRAW_TESTNET_RPC` in your `.env` file.

Specify the arguments in the following order: the accountID of the TA you are using, the ChainID to execute the Tx, the address to which you want to send the ETH, and the amount of ETH to send.

Example:
```
$ go run scripts/utils/execute_withdraw_tx/main.go 0x439376239c54540980f027ac33e1c11a 11155111 0x0A772258e2f36999C6aA57B2Ba09B78caF7EbAd3 0.0001
```

Once the transfer is successful, the `txHash` will be displayed.



## Testing

Run the test suite using Foundry:
Expand Down
197 changes: 197 additions & 0 deletions scripts/e2e/send_tx/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package main

import (
"bufio"
"context"
"fmt"
"log"
"os"
"strings"
"time"

"crypto/ecdsa"
"encoding/hex"
"math/big"

"github.com/joho/godotenv"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"

pb "github.com/mycel-labs/transferable-account/src/go/pb/api/v1"
testutil "github.com/mycel-labs/transferable-account/test/utils"
)

func calculateEthereumAddress(publicKeyX, publicKeyY string) (common.Address, error) {
// Decode the hex-encoded X and Y coordinates
xBytes, err := hex.DecodeString(publicKeyX)
if err != nil {
return common.Address{}, fmt.Errorf("failed to decode public key X: %v", err)
}

yBytes, err := hex.DecodeString(publicKeyY)
if err != nil {
return common.Address{}, fmt.Errorf("failed to decode public key Y: %v", err)
}

// Convert bytes to big.Int
x := new(big.Int).SetBytes(xBytes)
y := new(big.Int).SetBytes(yBytes)

// Create an ECDSA public key using the X and Y coordinates
publicKey := ecdsa.PublicKey{
Curve: crypto.S256(), // Use the secp256k1 curve, standard for Ethereum
X: x,
Y: y,
}

// Get the public key bytes (uncompressed form)
publicKeyBytes := crypto.FromECDSAPub(&publicKey)

// Hash the public key bytes using Keccak256 and take the last 20 bytes to get the address
address := crypto.Keccak256Hash(publicKeyBytes[1:]).Hex()
return common.HexToAddress(address), nil
}

func main() {
err := godotenv.Load()
if err != nil {
log.Fatalf("Error loading .env file")
}

if len(os.Args) != 2 {
log.Fatalf("Usage: %s <Bob's address>", os.Args[0])
}
bobAddress := common.HexToAddress(os.Args[1])

// Start the Astraeus server
testutil.StartAstraeusServer()

privateKeyBytes, err := hex.DecodeString(os.Getenv("PRIVATE_KEY"))
if err != nil {
log.Fatalf("failed to decode hex string: %v", err)
}
privKey, err := crypto.ToECDSA(privateKeyBytes)
if err != nil {
log.Fatalf("Failed to create private key: %v", err)
}

timedSignature, err := testutil.GenerateTimedSignature(time.Now().Unix()+86400, privKey)
if err != nil {
log.Fatalf("Failed to generate timed signature: %v", err)
}

createAccountRequest := &pb.CreateAccountRequest{
Proof: timedSignature,
}

createAccountResponse, _, err := testutil.CreateAccount(createAccountRequest)
if err != nil {
log.Fatalf("Failed to create account: %v", err)
}
log.Println("Create Account Response: ", createAccountResponse)

getAccountRequest := &pb.GetAccountRequest{
AccountId: createAccountResponse.AccountId,
}

getAccountResponse, _, err := testutil.GetAccount(getAccountRequest)
if err != nil {
log.Fatalf("Failed to get account: %v", err)
}
log.Println("Get Account Response: ", getAccountResponse)

x := getAccountResponse.Account.PublicKeyX
y := getAccountResponse.Account.PublicKeyY

accountAddress, err := calculateEthereumAddress(x, y)
if err != nil {
log.Fatalf("Failed to calculate Ethereum address: %v", err)
}
log.Println("Account Address: ", accountAddress)

unlockAccountRequest := &pb.UnlockAccountRequest{
Base: &pb.AccountOperationRequest{
AccountId: createAccountResponse.AccountId,
Proof: timedSignature,
},
}
unlockAccountResponse, _, err := testutil.UnlockAccount(unlockAccountRequest)
if err != nil {
log.Fatalf("Failed to unlock account: %v", err)
}
log.Println("Unlock Account Response: ", unlockAccountResponse)

// Create an Ethereum transaction (EIP-1559)
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: big.NewInt(11155111), // Sepolia network ID
Nonce: 0, // Replace with correct nonce
GasFeeCap: big.NewInt(400000000000), // 400 Gwei
GasTipCap: big.NewInt(20000000000), // 20 Gwei
Gas: 21000, // Standard gas limit for a simple transfer
To: &bobAddress,
Value: big.NewInt(10000000000000000), // 0.01 Ether
Data: []byte{},
})

signer := types.LatestSignerForChainID(tx.ChainId())
txHash := signer.Hash(tx).Bytes()

signRequest := &pb.SignRequest{
Base: &pb.AccountOperationRequest{
AccountId: createAccountResponse.AccountId,
Proof: timedSignature,
},
Data: hex.EncodeToString(txHash),
}

signResponse, _, err := testutil.Sign(signRequest)
if err != nil {
log.Fatalf("Failed to sign data: %v", err)
}
log.Println("Sign Response: ", signResponse)

// Decode the signature from signResponse
signature, err := hex.DecodeString(signResponse.Signature)
if err != nil {
log.Fatalf("Failed to decode signature: %v", err)
}

// Apply the signature to the transaction
signedTx, err := tx.WithSignature(signer, signature)
if err != nil {
log.Fatalf("Failed to apply signature to transaction: %v", err)
}

sender, err := types.Sender(types.NewLondonSigner(tx.ChainId()), signedTx)
if err != nil {
log.Fatalf("Failed to get sender from signed transaction: %v", err)
}
log.Println("Sender Address: ", sender)

// Connect to the Ethereum network and send the transaction
client, err := ethclient.Dial(os.Getenv("SEPOLIA_TESTNET_RPC"))
if err != nil {
log.Fatalf("Failed to connect to Ethereum client: %v", err)
}

// Deposit 0.001 ETH to the account
reader := bufio.NewReader(os.Stdin)
fmt.Printf("You need to depoist 0.01 + gas ETH to %s (y/n): ", accountAddress.Hex())
confirmation, _ := reader.ReadString('\n')
confirmation = strings.TrimSpace(strings.ToLower(confirmation))
if confirmation != "y" {
fmt.Println("Transaction canceled.")
return
}

// Send the transaction
err = client.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatalf("Failed to send transaction: %v", err)
}

log.Println("Transaction sent successfully! Tx Hash: ", signedTx.Hash().Hex())
}
Loading

0 comments on commit 51e9e74

Please # to comment.