Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sign Safe Transactions #8

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/opensea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const run = async (slug: string): Promise<void> => {

const tx = data.fulfillment_data.transaction;
const input_data = tx.input_data;

// TODO - report or fix these bugs with OpenseaSDK
// @ts-expect-error: Undocumented field on type FulfillmentData within FulfillmentDataResponse
const order = input_data.parameters;
Expand Down
39 changes: 39 additions & 0 deletions examples/sign-safe-tx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Safe from "@safe-global/protocol-kit";
import {
provider,
signHashedMessage,
// signHashedMessage
} from "../src/chains/ethereum";
import { setupAccount } from "./setup";
import { ethers } from "ethers";
import { EthersAdapter } from "@safe-global/protocol-kit";
// https://github.com/safe-global/safe-core-sdk
// https://github.com/safe-global/safe-transaction-service

const run = async (): Promise<void> => {
const sender = await setupAccount();

Check failure on line 14 in examples/sign-safe-tx.ts

View workflow job for this annotation

GitHub Actions / types

'sender' is assigned a value but never used
const txData = {
// from: sender,
value: "0",
to: "0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC",
data: "0x0000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c3f2aac800c0000000000000000000000000008d99f8b2710e6a3b94d9bf465a98e5273069acbd000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c00000000000000000000000000244adf43a9241a5f7cffb2dbe6b7154a141940000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065f43d0f00000000000000000000000000000000000000000000000000000000661d0d7f0000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000000952b1fa9b1b2d40000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000002c68af0bb140000000000000000000000000000000a26b00c1f0df003000390027140000faa7190000000000000000000000000000000000000000000000000000000000000040e7314f427776c463dbb2a6c84ffa0cf13118fa86c18c3a0d45d63fb499b1b82d7f7932852085bdc963bcfa0dcdcd0dea66dbe25d2879263f797e7fa16ba7a55300000000360c6ebe",
};

const safeAddress = "0x345375b738CCb4d1dcC663F3Dd3Ff3907f4DD2fe";
const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: provider });
// const safeService = new SafeApiKit({ chainId: 11155111n });
const safeSdk = await Safe.create({
ethAdapter,
safeAddress,
isL1SafeSingleton: true,
});
const safeTx = await safeSdk.createTransaction({ transactions: [txData] });
console.log("SafeTx:", safeTx);
const safeTxHash = await safeSdk.getTransactionHash(safeTx);
console.log("safeTxHash:", safeTxHash);

const signature = await signHashedMessage(txData, safeTxHash);
console.log("Signed Transaction with signature", signature);
};

run();
4 changes: 2 additions & 2 deletions examples/transfer-nft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const run = async (): Promise<void> => {
const sender = await setupAccount();
const value = 0;
// TODO retrieve from user:
const tokenAddress = "0xb5EF4EbB25fCA7603C028610ddc9233d399dA34d";
const tokenId = 17;
const tokenAddress = "0x80763a38213b6cede5b42f72ecb7f91078f27b4c";
const tokenId = 467;
const to = "0x8d99F8b2710e6A3B94d9bf465A98E5273069aCBd";

const callData = erc721Interface().encodeFunctionData(
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"@near-js/accounts": "^1.0.3",
"@near-js/crypto": "^1.2.1",
"@near-js/keystores": "^0.0.9",
"@safe-global/api-kit": "^2.2.0",
"@safe-global/protocol-kit": "^3.0.1",
"@safe-global/safe-core-sdk-types": "^4.0.1",
"bn.js": "^5.2.1",
"bs58check": "^3.0.1",
"elliptic": "^6.5.5",
Expand Down
107 changes: 81 additions & 26 deletions src/chains/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import {
uncompressedHexPointToEvmAddress,
} from "../utils/kdf";
import { NO_DEPOSIT, getNearAccount, provider as nearProvider } from "./near";
import { GasPriceResponse, GasPrices, TxPayload } from "../types";
import {
GasPriceResponse,
GasPrices,
MinimalTxData,
TxPayload,
} from "../types";
import { getMultichainContract } from "../mpc_contract";
import { getFirstNonZeroGasPrice } from "../utils/gasPrice";

Expand Down Expand Up @@ -65,23 +70,21 @@ async function queryGasPrice(): Promise<GasPrices> {
return { maxFeePerGas, maxPriorityFeePerGas };
}

export const createPayload = async (
sender: string,
receiver: string,
amount: number,
data?: string
): Promise<TxPayload> => {
const nonce = await provider.getTransactionCount(sender);
async function buildEIP1559Tx(
from: string,
txData: MinimalTxData
): Promise<FeeMarketEIP1559Transaction> {
const nonce = await provider.getTransactionCount(from);
const { maxFeePerGas, maxPriorityFeePerGas } = await queryGasPrice();
const transactionData = {
nonce,
to: receiver,
value: ethers.parseEther(amount.toString()),
data: data || "0x",
to: txData.to,
value: BigInt(txData.value),
data: txData.data || "0x",
};
const estimatedGas = await provider.estimateGas({
...transactionData,
from: sender,
from,
});
console.log(`Using gas estimate of at ${estimatedGas} GWei`);
const transactionDataWithGasLimit = {
Expand All @@ -91,13 +94,16 @@ export const createPayload = async (
maxPriorityFeePerGas,
};
console.log("TxData:", transactionDataWithGasLimit);
const transaction = FeeMarketEIP1559Transaction.fromTxData(
transactionDataWithGasLimit,
{
common,
}
);
return FeeMarketEIP1559Transaction.fromTxData(transactionDataWithGasLimit, {
common,
});
}

export const createPayload = async (
from: string,
txData: MinimalTxData
): Promise<TxPayload> => {
const transaction = await buildEIP1559Tx(from, txData);
const payload = Array.from(
new Uint8Array(transaction.getHashedMessageToSign().slice().reverse())
);
Expand Down Expand Up @@ -172,20 +178,69 @@ export const signAndSendTransaction = async (
path: string;
}
): Promise<void> => {
const signature = await signTx(sender, receiver, amount, data, options);
console.log("Relaying signed tx to EVM...");
await relayTransaction(signature);
};

export const signTx = async (
sender: string,
receiver: string,
amount: number,
data?: string,
options?: {
path: string;
}
): Promise<FeeMarketEIP1559Transaction> => {
console.log("Creating Payload for sender:", sender);
const { transaction, payload } = await createPayload(
sender,
receiver,
amount,
data
const { transaction, payload } = await createPayload(sender, {
to: receiver,
value: ethers.parseEther(amount.toString()),
data,
});

const { big_r, big_s } = await requestSignature(
payload,
options?.path || "ethereum,1"
);

return reconstructSignature(transaction, big_r, big_s, sender);
};

export const signHashedMessage = async (
sender: string,
tx: MinimalTxData,
hash: string,
options?: {
path: string;
}
): Promise<FeeMarketEIP1559Transaction> => {
console.log("Creating Payload for sender:", sender);
const payload = Array.from(
new Uint8Array(hexStringToByteArray(hash).slice().reverse())
);
const { big_r, big_s } = await requestSignature(
payload,
options?.path || "ethereum,1"
);

const signature = reconstructSignature(transaction, big_r, big_s, sender);
console.log("Relaying signed tx to EVM...");
await relayTransaction(signature);
return reconstructSignature(
await buildEIP1559Tx(sender, tx),
big_r,
big_s,
sender
);
};

function hexStringToByteArray(hexString: string): Uint8Array {
// Remove any leading "0x" if present
if (hexString.startsWith("0x")) {
hexString = hexString.slice(2);
}

const bytes = new Uint8Array(hexString.length / 2);
for (let i = 0, j = 0; i < hexString.length; i += 2, j++) {
bytes[j] = parseInt(hexString.substring(i, i + 2), 16);
}
return bytes;
}
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ export interface TxPayload {
transaction: FeeMarketEIP1559Transaction;
payload: number[];
}

export interface MinimalTxData {
to: string;
value: string | bigint;
data?: string;
}
Loading
Loading