import { Turnkey } from "@turnkey/sdk-server";
import {
AptosClient,
AptosAccount,
TxnBuilderTypes,
BCS,
TransactionBuilder,
HexString,
} from "aptos";
// Custom Turnkey signer for Aptos
class TurnkeyAptosSigner {
private turnkeyClient: Turnkey;
private aptosClient: AptosClient;
private address: string;
private organizationId: string;
constructor(
apiPrivateKey: string,
apiPublicKey: string,
organizationId: string,
address: string,
nodeUrl: string = "https://fullnode.mainnet.aptoslabs.com/v1"
) {
this.turnkeyClient = new Turnkey({
apiBaseUrl: "https://api.turnkey.com",
apiPrivateKey,
apiPublicKey,
defaultOrganizationId: organizationId,
});
this.aptosClient = new AptosClient(nodeUrl);
this.address = address;
this.organizationId = organizationId;
}
// Get the account address
getAddress(): string {
return this.address;
}
// Sign a transaction using Turnkey
async signTransaction(
rawTxn: TxnBuilderTypes.RawTransaction
): Promise<Uint8Array> {
// Serialize the raw transaction to BCS
const serializer = new BCS.Serializer();
rawTxn.serialize(serializer);
const toSign = serializer.getBytes();
// Sign the serialized transaction with Turnkey
const signResult = await this.turnkeyClient.signRawPayload({
organizationId: this.organizationId,
signWith: this.address,
payload: Buffer.from(toSign).toString("hex"),
encoding: "hex",
});
// Return the signed transaction bytes
return Buffer.from(signResult.signature, "hex");
}
// Submit a transaction
async submitTransaction(payload: any): Promise<string> {
try {
// Create a raw transaction from the payload
const rawTxn = await this.createRawTransaction(payload);
// Sign the transaction
const signature = await this.signTransaction(rawTxn);
// Get the authenticator
const authenticator = new TxnBuilderTypes.TransactionAuthenticatorEd25519(
new TxnBuilderTypes.Ed25519PublicKey(
// Note: In a real implementation, you would need to get the actual public key
new Uint8Array(32) // Placeholder - replace with actual public key
),
new TxnBuilderTypes.Ed25519Signature(signature)
);
// Create a signed transaction
const signedTxn = new TxnBuilderTypes.SignedTransaction(
rawTxn,
authenticator
);
// Submit the transaction
const pendingTxn = await this.aptosClient.submitSignedBCSTransaction(
BCS.bcsToBytes(signedTxn)
);
return pendingTxn.hash;
} catch (error) {
console.error("Error submitting transaction:", error);
throw error;
}
}
// Helper method to create a raw transaction
private async createRawTransaction(
payload: any
): Promise<TxnBuilderTypes.RawTransaction> {
const account = await this.aptosClient.getAccount(this.address);
const chainId = await this.aptosClient.getChainId();
// Create a raw transaction
return new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(this.address),
BigInt(account.sequence_number),
payload,
BigInt(2000), // Max gas amount
BigInt(100), // Gas unit price
BigInt(Math.floor(Date.now() / 1000) + 30), // Expiration timestamp (30 seconds from now)
new TxnBuilderTypes.ChainId(chainId)
);
}
}
// Example usage: Transfer coins
async function transferCoins() {
const signer = new TurnkeyAptosSigner(
process.env.API_PRIVATE_KEY!,
process.env.API_PUBLIC_KEY!,
process.env.ORGANIZATION_ID!,
process.env.APTOS_ADDRESS!, // Your Aptos address in Turnkey
"https://fullnode.testnet.aptoslabs.com/v1" // Testnet URL
);
const recipientAddress = "0x..."; // Recipient address
const amount = 1000000; // Amount in octas (1 APT = 100,000,000 octas)
// Create a transfer transaction payload
const payload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::coin",
"transfer",
[
new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString("0x1::aptos_coin::AptosCoin")
),
],
[
BCS.bcsToBytes(
TxnBuilderTypes.AccountAddress.fromHex(recipientAddress)
),
BCS.bcsSerializeUint64(amount),
]
)
);
try {
const txnHash = await signer.submitTransaction(payload);
console.log(`Transaction submitted successfully! Hash: ${txnHash}`);
return txnHash;
} catch (error) {
console.error("Error transferring coins:", error);
throw error;
}
}