@coin-voyage/crypto 2.3.0 → 2.3.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/evm/use-evm-transaction.js +21 -3
- package/dist/solana/use-solana-transaction.js +65 -4
- package/dist/sui/use-sui-transaction.js +31 -5
- package/dist/types/transaction.d.ts +5 -3
- package/dist/utxo/create-psbt-tx.js +1 -3
- package/dist/utxo/create-utxo-transport.js +11 -4
- package/dist/utxo/use-utxo-transaction.js +9 -1
- package/package.json +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getEvmPaymentData } from "@coin-voyage/shared/common";
|
|
1
2
|
import { estimateFeesPerGas } from "viem/actions";
|
|
2
3
|
import { useConfig, useSendTransaction, useWriteContract } from "wagmi";
|
|
3
4
|
export function useEVMTransaction() {
|
|
@@ -5,14 +6,31 @@ export function useEVMTransaction() {
|
|
|
5
6
|
const { sendTransactionAsync } = useSendTransaction();
|
|
6
7
|
const config = useConfig();
|
|
7
8
|
const execute = async (params) => {
|
|
8
|
-
const { chainId, toAddress, amount, token } = params;
|
|
9
|
-
const value = BigInt(amount);
|
|
10
|
-
const to = toAddress;
|
|
11
9
|
const client = config.getClient();
|
|
12
10
|
const gasEstimates = await estimateFeesPerGas(client, {
|
|
13
11
|
chain: client.chain,
|
|
14
12
|
type: "eip1559",
|
|
15
13
|
});
|
|
14
|
+
const evmPaymentData = getEvmPaymentData(params.paymentData);
|
|
15
|
+
if (evmPaymentData) {
|
|
16
|
+
const tx = await sendTransactionAsync({
|
|
17
|
+
to: evmPaymentData.to,
|
|
18
|
+
data: evmPaymentData.data,
|
|
19
|
+
value: BigInt(evmPaymentData.value),
|
|
20
|
+
chainId: evmPaymentData.chainId,
|
|
21
|
+
maxFeePerGas: evmPaymentData.maxFeePerGas ? BigInt(evmPaymentData.maxFeePerGas) : gasEstimates.maxFeePerGas,
|
|
22
|
+
maxPriorityFeePerGas: evmPaymentData.maxPriorityFeePerGas
|
|
23
|
+
? BigInt(evmPaymentData.maxPriorityFeePerGas)
|
|
24
|
+
: gasEstimates.maxPriorityFeePerGas,
|
|
25
|
+
});
|
|
26
|
+
return tx;
|
|
27
|
+
}
|
|
28
|
+
const { chainId, to: toAddress, amount, token } = params;
|
|
29
|
+
if (!toAddress || amount == undefined) {
|
|
30
|
+
throw new Error("Missing EVM transfer target or amount");
|
|
31
|
+
}
|
|
32
|
+
const value = BigInt(amount);
|
|
33
|
+
const to = toAddress;
|
|
16
34
|
if (!token) {
|
|
17
35
|
const tx = await sendTransactionAsync({
|
|
18
36
|
to,
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { getSolanaPaymentData } from "@coin-voyage/shared/common";
|
|
2
|
+
import { hex } from "@scure/base";
|
|
1
3
|
import { createTransferCheckedInstruction, getAssociatedTokenAddress } from "@solana/spl-token";
|
|
2
4
|
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
|
|
3
|
-
import { ComputeBudgetProgram, PublicKey, SystemProgram, Transaction, TransactionInstruction, } from "@solana/web3.js";
|
|
5
|
+
import { ComputeBudgetProgram, PublicKey, SystemProgram, Transaction, TransactionInstruction, VersionedTransaction, } from "@solana/web3.js";
|
|
4
6
|
import { getOrCreateAssociatedTokenAccount } from "./get-or-create-ata";
|
|
7
|
+
const INVALID_TX_PAYLOAD_ERROR = "Invalid Solana transaction payload: expected Transaction, VersionedTransaction, Uint8Array, byte array, hex, or base64";
|
|
5
8
|
export function useSolanaTransaction() {
|
|
6
9
|
const { connection } = useConnection();
|
|
7
10
|
const wallet = useWallet();
|
|
@@ -14,10 +17,22 @@ export function useSolanaTransaction() {
|
|
|
14
17
|
throw new Error("Wallet not connected. Please reconnect your wallet and try again.");
|
|
15
18
|
}
|
|
16
19
|
}
|
|
17
|
-
const
|
|
20
|
+
const preparedPayment = getSolanaPaymentData(params.paymentData);
|
|
21
|
+
if (preparedPayment) {
|
|
22
|
+
const { context: { slot: minContextSlot }, value: { lastValidBlockHeight }, } = await connection.getLatestBlockhashAndContext();
|
|
23
|
+
const transaction = deserializePreparedSolanaTransaction(preparedPayment.transaction);
|
|
24
|
+
const signature = await wallet.sendTransaction(transaction, connection, {
|
|
25
|
+
minContextSlot,
|
|
26
|
+
});
|
|
27
|
+
return waitForTransactionConfirmation(signature, connection, lastValidBlockHeight);
|
|
28
|
+
}
|
|
29
|
+
const { from, to, amount, token } = params;
|
|
30
|
+
if (!to || amount == undefined) {
|
|
31
|
+
throw new Error("Missing Solana transfer target or amount");
|
|
32
|
+
}
|
|
18
33
|
const transaction = new Transaction();
|
|
19
|
-
const senderPubKey = new PublicKey(
|
|
20
|
-
const receiverPubKey = new PublicKey(
|
|
34
|
+
const senderPubKey = new PublicKey(from);
|
|
35
|
+
const receiverPubKey = new PublicKey(to);
|
|
21
36
|
const instructions = [
|
|
22
37
|
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000000 }),
|
|
23
38
|
];
|
|
@@ -56,6 +71,52 @@ export function useSolanaTransaction() {
|
|
|
56
71
|
};
|
|
57
72
|
return { execute };
|
|
58
73
|
}
|
|
74
|
+
function toSerializedTransactionBytes(value) {
|
|
75
|
+
if (value instanceof Uint8Array)
|
|
76
|
+
return value;
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
if (!value.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
|
|
79
|
+
throw new Error("Invalid Solana transaction payload: array must contain byte values");
|
|
80
|
+
}
|
|
81
|
+
return Uint8Array.from(value);
|
|
82
|
+
}
|
|
83
|
+
if (typeof value !== "string") {
|
|
84
|
+
throw new Error(INVALID_TX_PAYLOAD_ERROR);
|
|
85
|
+
}
|
|
86
|
+
const normalized = value.trim();
|
|
87
|
+
if (!normalized) {
|
|
88
|
+
throw new Error("Invalid Solana transaction payload: empty string");
|
|
89
|
+
}
|
|
90
|
+
const hexValue = normalized.startsWith("0x") ? normalized.slice(2) : normalized;
|
|
91
|
+
const looksLikeHex = hexValue.length > 0 && hexValue.length % 2 === 0 && /^[0-9a-fA-F]+$/.test(hexValue);
|
|
92
|
+
if (looksLikeHex) {
|
|
93
|
+
return hex.decode(hexValue);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return Uint8Array.from(atob(normalized), (char) => char.charCodeAt(0));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new Error(INVALID_TX_PAYLOAD_ERROR);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function deserializePreparedSolanaTransaction(serialized) {
|
|
103
|
+
if (serialized instanceof Transaction || serialized instanceof VersionedTransaction) {
|
|
104
|
+
return serialized;
|
|
105
|
+
}
|
|
106
|
+
const bytes = toSerializedTransactionBytes(serialized);
|
|
107
|
+
try {
|
|
108
|
+
return VersionedTransaction.deserialize(bytes);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// fall back to legacy Transaction
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
return Transaction.from(bytes);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new Error(INVALID_TX_PAYLOAD_ERROR);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
59
120
|
async function waitForTransactionConfirmation(signature, connection, lastValidBlockHeight) {
|
|
60
121
|
const maxRetries = 30;
|
|
61
122
|
const retryDelay = 2000;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getSuiPaymentData } from "@coin-voyage/shared/common";
|
|
1
2
|
import { useReportTransactionEffects, useSignTransaction, useSuiClient } from "@mysten/dapp-kit";
|
|
2
3
|
import { Transaction } from "@mysten/sui/transactions";
|
|
3
4
|
import { SUI_PACKAGE_IDS } from "./constants";
|
|
@@ -6,25 +7,50 @@ export function useSUITransaction() {
|
|
|
6
7
|
const { mutateAsync: signTransaction } = useSignTransaction();
|
|
7
8
|
const { mutate: reportTransactionEffects } = useReportTransactionEffects();
|
|
8
9
|
const execute = async (params) => {
|
|
9
|
-
const
|
|
10
|
+
const preparedPayment = getSuiPaymentData(params.paymentData);
|
|
11
|
+
if (preparedPayment) {
|
|
12
|
+
const transaction = typeof preparedPayment.transaction === "string"
|
|
13
|
+
? preparedPayment.transaction
|
|
14
|
+
: Transaction.from(preparedPayment.transaction);
|
|
15
|
+
const { bytes, signature } = await signTransaction({
|
|
16
|
+
transaction,
|
|
17
|
+
});
|
|
18
|
+
const executeResult = await client.executeTransactionBlock({
|
|
19
|
+
transactionBlock: bytes,
|
|
20
|
+
signature,
|
|
21
|
+
options: {
|
|
22
|
+
showRawEffects: true,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
if (executeResult.rawEffects) {
|
|
26
|
+
reportTransactionEffects({
|
|
27
|
+
effects: executeResult.rawEffects,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return executeResult.digest;
|
|
31
|
+
}
|
|
32
|
+
const { from: owner, to, amount, token } = params;
|
|
33
|
+
if (!to || amount == undefined) {
|
|
34
|
+
throw new Error("Missing Sui transfer target or amount");
|
|
35
|
+
}
|
|
10
36
|
const tokenAddress = token?.address;
|
|
11
37
|
const isNative = !tokenAddress || tokenAddress.toLowerCase() === SUI_PACKAGE_IDS["sui"].toLowerCase();
|
|
12
38
|
const tx = new Transaction();
|
|
13
39
|
const value = BigInt(amount);
|
|
14
40
|
if (isNative) {
|
|
15
41
|
const balance = await client.getBalance({
|
|
16
|
-
owner
|
|
42
|
+
owner,
|
|
17
43
|
coinType: SUI_PACKAGE_IDS["sui"],
|
|
18
44
|
});
|
|
19
45
|
if (BigInt(balance.totalBalance) < value) {
|
|
20
46
|
throw new Error(`[CHECKOUT] Insufficient balance: ${balance}`);
|
|
21
47
|
}
|
|
22
48
|
const [coin] = tx.splitCoins(tx.gas, [value]);
|
|
23
|
-
tx.transferObjects([coin],
|
|
49
|
+
tx.transferObjects([coin], to);
|
|
24
50
|
}
|
|
25
51
|
else {
|
|
26
52
|
const coins = await client.getCoins({
|
|
27
|
-
owner
|
|
53
|
+
owner,
|
|
28
54
|
coinType: tokenAddress,
|
|
29
55
|
});
|
|
30
56
|
let total = BigInt(0);
|
|
@@ -42,7 +68,7 @@ export function useSUITransaction() {
|
|
|
42
68
|
tx.mergeCoins(coinInput, coinInputs.slice(1));
|
|
43
69
|
}
|
|
44
70
|
const [paymentCoin] = tx.splitCoins(coinInput, [value]);
|
|
45
|
-
tx.transferObjects([paymentCoin],
|
|
71
|
+
tx.transferObjects([paymentCoin], to);
|
|
46
72
|
}
|
|
47
73
|
const gasPrice = await client.getReferenceGasPrice();
|
|
48
74
|
tx.setGasPrice(gasPrice);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import type { CryptoPaymentData } from "@coin-voyage/shared/types";
|
|
1
2
|
export type ExecuteTransaction = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
amount
|
|
3
|
+
from: string;
|
|
4
|
+
to?: string;
|
|
5
|
+
amount?: bigint;
|
|
5
6
|
chainId?: number;
|
|
6
7
|
token?: {
|
|
7
8
|
address: string;
|
|
8
9
|
decimals: number;
|
|
9
10
|
};
|
|
11
|
+
paymentData?: CryptoPaymentData;
|
|
10
12
|
};
|
|
@@ -40,9 +40,7 @@ export async function createPsbtTx(client, fromAddress, toAddress, amountSats) {
|
|
|
40
40
|
txid: utxo.txId,
|
|
41
41
|
index: utxo.vout,
|
|
42
42
|
sequence: 0xfffffffd, // RBF
|
|
43
|
-
...(addressType === AddressType.p2wpkh ||
|
|
44
|
-
addressType === AddressType.p2wsh ||
|
|
45
|
-
addressType === AddressType.p2tr
|
|
43
|
+
...(addressType === AddressType.p2wpkh || addressType === AddressType.p2wsh || addressType === AddressType.p2tr
|
|
46
44
|
? {
|
|
47
45
|
witnessUtxo: {
|
|
48
46
|
script: scripts.script,
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { ankr, blockchair, blockcypher, fallback, http, mempool
|
|
1
|
+
import { ankr, blockchair, blockcypher, fallback, http, mempool } from "@bigmi/core";
|
|
2
2
|
import { ALCHEMY_KEY } from "../evm/utils";
|
|
3
3
|
export function createUTXOTransport(rpcConfig) {
|
|
4
4
|
const primaryRpc = rpcConfig
|
|
5
5
|
? http(rpcConfig.url, rpcConfig.config)
|
|
6
6
|
: http(`https://bitcoin-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`);
|
|
7
|
-
return fallback([
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
return fallback([
|
|
8
|
+
primaryRpc,
|
|
9
|
+
http(),
|
|
10
|
+
blockchair(),
|
|
11
|
+
blockcypher(),
|
|
12
|
+
mempool(),
|
|
13
|
+
ankr({
|
|
14
|
+
apiKey: "2a2801137db177be9c80522e690ec8b59ffc5260d030b2671b5e3936b917daac",
|
|
15
|
+
}),
|
|
16
|
+
]);
|
|
10
17
|
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { useAccount } from "@bigmi/react";
|
|
2
|
+
import { getBitcoinPaymentData } from "@coin-voyage/shared/common";
|
|
2
3
|
import { sendBtc } from "./send-btc";
|
|
3
4
|
export function useUTXOTransaction() {
|
|
4
5
|
const { account, connector } = useAccount();
|
|
5
6
|
const execute = async (params) => {
|
|
6
|
-
const
|
|
7
|
+
const bitcoinPaymentData = getBitcoinPaymentData(params.paymentData);
|
|
8
|
+
if (bitcoinPaymentData) {
|
|
9
|
+
throw new Error("Prepared Bitcoin transactions are not supported in wallet flow yet.");
|
|
10
|
+
}
|
|
11
|
+
const { to: toAddress, amount } = params;
|
|
7
12
|
if (!connector?.id || !account?.address) {
|
|
8
13
|
throw new Error("No connection found");
|
|
9
14
|
}
|
|
15
|
+
if (!toAddress || amount == undefined) {
|
|
16
|
+
throw new Error("Missing Bitcoin transfer target or amount");
|
|
17
|
+
}
|
|
10
18
|
const txhash = await sendBtc({
|
|
11
19
|
senderAddress: account.address,
|
|
12
20
|
recipient: toAddress,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coin-voyage/crypto",
|
|
3
3
|
"description": "Crypto utilities for Coin Voyage",
|
|
4
|
-
"version": "2.3.0",
|
|
4
|
+
"version": "2.3.1-beta.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"author": "Lars <lars@coinvoyage.io>",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@solana/wallet-adapter-walletconnect": "0.1.21",
|
|
51
51
|
"@solana/wallet-adapter-base": "0.9.27",
|
|
52
52
|
"@solana/wallet-adapter-coinbase": "0.1.23",
|
|
53
|
-
"@coin-voyage/shared": "2.3.
|
|
53
|
+
"@coin-voyage/shared": "2.3.5-beta.3"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/elliptic": "6.4.18"
|