import { sha512 } from "@noble/hashes/sha2";
async function sendXrpTransfer(
to: string,
amount: number,
xrpAddress: string,
publicKey: string,
): Promise<string> {
const wallet = dynamicClient.wallets.userWallets.find(w => w.chain === "EVM")!;
const pubkeyBytes = hexToBytes(publicKey);
const drops = BigInt(Math.round(amount * 1_000_000));
const [accountInfo, feeInfo] = await Promise.all([
xrpRpc("account_info", [{ account: xrpAddress, ledger_index: "current" }]),
xrpRpc("fee", []),
]);
const sequence = accountInfo.account_data.Sequence;
const fee = BigInt(Math.max(parseInt(feeInfo.drops?.open_ledger_fee || "12"), 12));
const lastLedger = (feeInfo.ledger_current_index || 0) + 75;
const serialized = serializePayment({
account: xrpAddressToAccountId(xrpAddress),
destination: xrpAddressToAccountId(to),
amount: drops,
fee,
sequence,
lastLedgerSequence: lastLedger,
signingPubKey: pubkeyBytes,
});
// Signing hash: SHA-512 first half of (0x53545800 + serialized)
const HASH_PREFIX = new Uint8Array([0x53, 0x54, 0x58, 0x00]);
const hash = sha512(concatBytes(HASH_PREFIX, serialized)).slice(0, 32);
const signature = await dynamicClient.wallets.waas.signRawMessage(wallet.id, {
accountAddress: wallet.address,
message: bytesToHex(hash),
});
// DER-encode the signature with low-S normalization
const sigRaw = hexToBytes(signature.replace(/^0x/, ""));
const derSig = sigToDER(bytesToHex(sigRaw.slice(0, 64)));
const signedSerialized = serializePayment({
account: xrpAddressToAccountId(xrpAddress),
destination: xrpAddressToAccountId(to),
amount: drops,
fee,
sequence,
lastLedgerSequence: lastLedger,
signingPubKey: pubkeyBytes,
txnSignature: derSig,
});
const txBlob = bytesToHex(signedSerialized).toUpperCase();
const result = await xrpRpc("submit", [{ tx_blob: txBlob }]);
if (!result.engine_result?.startsWith("tes") && !result.engine_result?.startsWith("ter")) {
throw new Error(`XRP submit failed: ${result.engine_result_message}`);
}
return result.tx_json?.hash;
}