|  | 
|  | 1 | +/** | 
|  | 2 | + * Performs delegated staking with Aptos. | 
|  | 3 | + * | 
|  | 4 | + * Copyright 2025, BitGo, Inc.  All Rights Reserved. | 
|  | 5 | + */ | 
|  | 6 | +import { BitGoAPI } from '@bitgo/sdk-api'; | 
|  | 7 | +import { coins } from '@bitgo/statics'; | 
|  | 8 | +import { Tapt, TransactionBuilderFactory, Utils } from '@bitgo/sdk-coin-apt'; | 
|  | 9 | +import { Network, Aptos, AptosConfig, Account, Ed25519PrivateKey, SimpleTransaction } from '@aptos-labs/ts-sdk'; | 
|  | 10 | + | 
|  | 11 | +require('dotenv').config({ path: '../../.env' }); | 
|  | 12 | + | 
|  | 13 | +const AMOUNT_OCTAS = 11 * 100_000_000; | 
|  | 14 | +const NETWORK = Network.TESTNET; | 
|  | 15 | + | 
|  | 16 | +const aptosConfig = new AptosConfig({ network: NETWORK }); | 
|  | 17 | +const aptos = new Aptos(aptosConfig); | 
|  | 18 | + | 
|  | 19 | +const bitgo = new BitGoAPI({ | 
|  | 20 | +  accessToken: process.env.TESTNET_ACCESS_TOKEN, | 
|  | 21 | +  env: 'test', | 
|  | 22 | +}); | 
|  | 23 | +const coin = coins.get('tapt'); | 
|  | 24 | +bitgo.register(coin.name, Tapt.createInstance); | 
|  | 25 | + | 
|  | 26 | +const broadcastToSimple = (serializedTx: string) => | 
|  | 27 | +  new SimpleTransaction(Utils.default.deserializeSignedTransaction(serializedTx).raw_txn); | 
|  | 28 | + | 
|  | 29 | +async function main() { | 
|  | 30 | +  const account = getAccount(); | 
|  | 31 | +  const delegationPoolAddress = getDelegationPoolAddress(); | 
|  | 32 | + | 
|  | 33 | +  // Account should have sufficient balance | 
|  | 34 | +  const accountBalance = await aptos.getAccountAPTAmount({ accountAddress: account.accountAddress }); | 
|  | 35 | +  if (accountBalance < AMOUNT_OCTAS) { | 
|  | 36 | +    console.info(`Balance of ${account.accountAddress} is ${accountBalance} octas, requesting funds.`); | 
|  | 37 | +    const txn = await aptos.fundAccount({ accountAddress: account.accountAddress, amount: AMOUNT_OCTAS }); | 
|  | 38 | +    await aptos.waitForTransaction({ transactionHash: txn.hash }); | 
|  | 39 | +    console.info(`Funding successful: ${txn.hash}`); | 
|  | 40 | +  } | 
|  | 41 | +  const { sequence_number } = await aptos.getAccountInfo({ accountAddress: account.accountAddress }); | 
|  | 42 | + | 
|  | 43 | +  // Use BitGoAPI to build instruction | 
|  | 44 | +  const txBuilder = new TransactionBuilderFactory(coin).getDelegationPoolAddStakeTransactionBuilder(); | 
|  | 45 | +  txBuilder | 
|  | 46 | +    .sender(account.accountAddress.toString()) | 
|  | 47 | +    .recipients([{ address: delegationPoolAddress, amount: `${AMOUNT_OCTAS}` }]) | 
|  | 48 | +    .sequenceNumber(Number(sequence_number)); | 
|  | 49 | +  const unsignedTx = await txBuilder.build(); | 
|  | 50 | +  const serializedUnsignedTx = unsignedTx.toBroadcastFormat(); | 
|  | 51 | + | 
|  | 52 | +  // Sign transaction. Signing is flexible, let's use Aptos libs | 
|  | 53 | +  const authenticator = aptos.sign({ | 
|  | 54 | +    signer: account, | 
|  | 55 | +    transaction: broadcastToSimple(serializedUnsignedTx), | 
|  | 56 | +  }); | 
|  | 57 | +  if (!authenticator.isEd25519()) { | 
|  | 58 | +    throw new Error('Example only supports Ed25519'); | 
|  | 59 | +  } | 
|  | 60 | +  txBuilder.addSenderSignature( | 
|  | 61 | +    { pub: account.publicKey.toString() }, | 
|  | 62 | +    Buffer.from(authenticator.signature.toUint8Array()) | 
|  | 63 | +  ); | 
|  | 64 | +  const tx = await txBuilder.build(); | 
|  | 65 | +  const serializedTx = tx.toBroadcastFormat(); | 
|  | 66 | +  console.info(`Transaction ${serializedTx} and JSON:\n${JSON.stringify(tx.toJson(), undefined, 2)}`); | 
|  | 67 | + | 
|  | 68 | +  // Submit transaction | 
|  | 69 | +  const submittedTxn = await aptos.transaction.submit.simple({ | 
|  | 70 | +    transaction: broadcastToSimple(serializedTx), | 
|  | 71 | +    senderAuthenticator: authenticator, | 
|  | 72 | +  }); | 
|  | 73 | +  console.log(`Success: ${submittedTxn.hash}`); | 
|  | 74 | +} | 
|  | 75 | + | 
|  | 76 | +const getAccount = () => { | 
|  | 77 | +  const privateKey = process.env.APTOS_PRIVATE_KEY; | 
|  | 78 | +  if (privateKey === undefined) { | 
|  | 79 | +    const { privateKey } = Account.generate(); | 
|  | 80 | +    console.log('# Here is a new account to save into your .env file.'); | 
|  | 81 | +    console.log(`APTOS_PRIVATE_KEY=${privateKey.toAIP80String()}`); | 
|  | 82 | +    throw new Error('Missing account information'); | 
|  | 83 | +  } | 
|  | 84 | +  return Account.fromPrivateKey({ privateKey: new Ed25519PrivateKey(privateKey) }); | 
|  | 85 | +}; | 
|  | 86 | + | 
|  | 87 | +const getDelegationPoolAddress = () => { | 
|  | 88 | +  const address = process.env.APTOS_DELEGATION_POOL_ADDRESS; | 
|  | 89 | +  if (!address) { | 
|  | 90 | +    console.log('# Provide a delegation pool.'); | 
|  | 91 | +    console.log(`APTOS_DELEGATION_POOL_ADDRESS=`); | 
|  | 92 | +    throw new Error('Missing delegation pool address'); | 
|  | 93 | +  } | 
|  | 94 | +  return address; | 
|  | 95 | +}; | 
|  | 96 | + | 
|  | 97 | +main().catch((e) => console.error(e)); | 
0 commit comments