connect to devnet contract
This commit is contained in:
parent
1d58b37d24
commit
816e82b867
|
@ -4,13 +4,13 @@ import tw from 'twin.macro'
|
|||
import { LockClosedIcon, LockOpenIcon } from '@heroicons/react/outline'
|
||||
import { LinkIcon } from '@heroicons/react/solid'
|
||||
import useWalletStore from '../stores/useWalletStore'
|
||||
import { getUsdcBalance } from '../utils'
|
||||
import Input from './Input'
|
||||
import Button from './Button'
|
||||
import { ConnectWalletButtonSmall } from './ConnectWalletButton'
|
||||
import Slider from './Slider'
|
||||
import Loading from './Loading'
|
||||
import WalletIcon from './WalletIcon'
|
||||
import useLargestAccounts from '../hooks/useLargestAccounts'
|
||||
|
||||
const StyledModalWrapper = styled.div`
|
||||
height: 414px;
|
||||
|
@ -40,9 +40,15 @@ const StyledModalBorder = styled.div<StyledModalBorderProps>`
|
|||
`
|
||||
|
||||
const ContributionModal = () => {
|
||||
const actions = useWalletStore((s) => s.actions)
|
||||
const connected = useWalletStore((s) => s.connected)
|
||||
const wallet = useWalletStore((s) => s.current)
|
||||
const usdcBalance = getUsdcBalance()
|
||||
const largestAccounts = useLargestAccounts()
|
||||
|
||||
const usdcBalance = largestAccounts.usdc?.balance || 0
|
||||
const redeemableBalance = largestAccounts.redeemable?.balance || 0
|
||||
|
||||
console.log({ usdcBalance, redeemableBalance })
|
||||
|
||||
const [contributionAmount, setContributionAmount] = useState(0)
|
||||
const [sliderPercentage, setSliderPercentage] = useState(0)
|
||||
|
@ -52,6 +58,11 @@ const ContributionModal = () => {
|
|||
const [loading, setLoading] = useState(true)
|
||||
const [maxButtonTransition, setMaxButtonTransition] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
console.log('setContributionAmount from redeemableBalance')
|
||||
setContributionAmount(redeemableBalance)
|
||||
}, [redeemableBalance])
|
||||
|
||||
const handleConnectDisconnect = () => {
|
||||
if (connected) {
|
||||
setSubmitted(false)
|
||||
|
@ -103,12 +114,9 @@ const ContributionModal = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (submitting) {
|
||||
// TODO: add submission here
|
||||
const submitTimer = setTimeout(() => {
|
||||
actions.submitContribution(contributionAmount)
|
||||
setSubmitted(true)
|
||||
setSubmitting(false)
|
||||
}, 2000)
|
||||
return () => clearTimeout(submitTimer)
|
||||
}
|
||||
}, [submitting])
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import BN from 'bn.js'
|
||||
import useWalletStore from '../stores/useWalletStore'
|
||||
import { ProgramAccount, TokenAccount } from '../utils/tokens'
|
||||
|
||||
function fixedPointToNumber(value: BN, decimals: number) {
|
||||
const divisor = new BN(10).pow(new BN(decimals))
|
||||
const quotient = value.div(divisor)
|
||||
const remainder = value.mod(divisor)
|
||||
return quotient.toNumber() + remainder.toNumber() / divisor.toNumber()
|
||||
}
|
||||
|
||||
function calculateBalance(mints, account: TokenAccount): number {
|
||||
const mint = mints[account.mint.toBase58()]
|
||||
return mint ? fixedPointToNumber(account.amount, mint.decimals) : 0
|
||||
}
|
||||
|
||||
export function findLargestBalanceAccountForMint(
|
||||
mints,
|
||||
tokenAccounts: ProgramAccount<TokenAccount>[],
|
||||
mintPk
|
||||
) {
|
||||
const accounts = tokenAccounts.filter((a) => a.account.mint.equals(mintPk))
|
||||
if (!accounts.length) return undefined
|
||||
|
||||
const balances = accounts.map((a) => calculateBalance(mints, a.account))
|
||||
const maxBalanceAccountIndex = balances.reduce(
|
||||
(iMax, bal, iBal) => (bal > balances[iMax] ? iBal : iMax),
|
||||
0
|
||||
)
|
||||
const account = accounts[maxBalanceAccountIndex]
|
||||
const balance = balances[maxBalanceAccountIndex]
|
||||
|
||||
console.log(
|
||||
'findLargestBalanceAccountForMint',
|
||||
maxBalanceAccountIndex,
|
||||
account,
|
||||
balance
|
||||
)
|
||||
|
||||
return { account, balance }
|
||||
}
|
||||
|
||||
export default function useLargestAccounts() {
|
||||
const { pool, tokenAccounts, mints, usdcVault } = useWalletStore(
|
||||
(state) => state
|
||||
)
|
||||
const usdc = usdcVault
|
||||
? findLargestBalanceAccountForMint(mints, tokenAccounts, usdcVault.mint)
|
||||
: undefined
|
||||
const redeemable = pool
|
||||
? findLargestBalanceAccountForMint(
|
||||
mints,
|
||||
tokenAccounts,
|
||||
pool.redeemableMint
|
||||
)
|
||||
: undefined
|
||||
return { usdc, redeemable }
|
||||
}
|
|
@ -103,10 +103,9 @@ export default function useWallet() {
|
|||
wallet.publicKey.toString().substr(-5),
|
||||
})
|
||||
await actions.fetchPool()
|
||||
await actions.fetchVault()
|
||||
await Promise.all([
|
||||
actions.fetchWalletTokenAccounts(),
|
||||
actions.fetchVaultMint(),
|
||||
actions.fetchMints(),
|
||||
])
|
||||
})
|
||||
wallet.on('disconnect', () => {
|
||||
|
@ -131,7 +130,7 @@ export default function useWallet() {
|
|||
}, [wallet, setWalletStore])
|
||||
|
||||
useInterval(async () => {
|
||||
await actions.fetchVault()
|
||||
await actions.fetchUsdcVault()
|
||||
}, 20 * SECONDS)
|
||||
|
||||
return { connected, wallet }
|
||||
|
|
|
@ -397,6 +397,6 @@
|
|||
}
|
||||
],
|
||||
"metadata": {
|
||||
"address": "5JTPKQJPkvrMzfDyEHX6EiaEYbR5QxXDkNQ66BoadgUn"
|
||||
"address": "2oBtRS2AAQfsMxXQfg41fKFY9zjvHwSSD7G5idrCFziV"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import create, { State } from 'zustand'
|
||||
import produce from 'immer'
|
||||
import { Connection, PublicKey } from '@solana/web3.js'
|
||||
import { Connection, PublicKey, Transaction } from '@solana/web3.js'
|
||||
import * as anchor from '@project-serum/anchor'
|
||||
|
||||
import { EndpointInfo, WalletAdapter } from '../@types/types'
|
||||
|
@ -16,6 +16,11 @@ import {
|
|||
MintAccount,
|
||||
getTokenAccount,
|
||||
} from '../utils/tokens'
|
||||
import { findLargestBalanceAccountForMint } from '../hooks/useLargestAccounts'
|
||||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token'
|
||||
import { token } from '@project-serum/anchor/dist/utils'
|
||||
import { createAssociatedTokenAccount } from '../utils/associated'
|
||||
import { sendTransaction } from '../utils/send'
|
||||
|
||||
export const ENDPOINTS: EndpointInfo[] = [
|
||||
{
|
||||
|
@ -27,10 +32,10 @@ export const ENDPOINTS: EndpointInfo[] = [
|
|||
},
|
||||
{
|
||||
name: 'devnet',
|
||||
url: 'https://devnet.solana.com',
|
||||
websocket: 'https://devnet.solana.com',
|
||||
programId: 'E5s3D6B3PJinuB9kb3dicxfi3qUNLUGX6hoPawhbqagt',
|
||||
poolKey: '',
|
||||
url: 'https://api.devnet.solana.com',
|
||||
websocket: 'https://api.devnet.solana.com',
|
||||
programId: '2oBtRS2AAQfsMxXQfg41fKFY9zjvHwSSD7G5idrCFziV',
|
||||
poolKey: 'ZfSZf2xrNLBrfY37TwJLoHv9qdKBQpkbZuPrq5FT8U9',
|
||||
},
|
||||
{
|
||||
name: 'localnet',
|
||||
|
@ -41,7 +46,7 @@ export const ENDPOINTS: EndpointInfo[] = [
|
|||
},
|
||||
]
|
||||
|
||||
const CLUSTER = 'localnet'
|
||||
const CLUSTER = 'devnet'
|
||||
const ENDPOINT = ENDPOINTS.find((e) => e.name === CLUSTER)
|
||||
const DEFAULT_CONNECTION = new Connection(ENDPOINT.url, 'recent')
|
||||
const WEBSOCKET_CONNECTION = new Connection(ENDPOINT.websocket, 'recent')
|
||||
|
@ -72,10 +77,13 @@ interface WalletStore extends State {
|
|||
}
|
||||
current: WalletAdapter | undefined
|
||||
providerUrl: string
|
||||
provider: anchor.Provider | undefined
|
||||
program: anchor.Program | undefined
|
||||
pool: PoolAccount | undefined
|
||||
mangoVault: TokenAccount | undefined
|
||||
usdcVault: TokenAccount | undefined
|
||||
tokenAccounts: ProgramAccount<TokenAccount>[]
|
||||
mints: { [pubkey: string]: MintAccount }
|
||||
pool: PoolAccount | undefined
|
||||
vault: TokenAccount | undefined
|
||||
set: (x: any) => void
|
||||
actions: any
|
||||
}
|
||||
|
@ -91,10 +99,13 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
|||
},
|
||||
current: null,
|
||||
providerUrl: null,
|
||||
provider: undefined,
|
||||
program: undefined,
|
||||
pool: undefined,
|
||||
mangoVault: undefined,
|
||||
usdcVault: undefined,
|
||||
tokenAccounts: [],
|
||||
mints: {},
|
||||
pool: undefined,
|
||||
vault: undefined,
|
||||
actions: {
|
||||
async fetchPool() {
|
||||
const connection = get().connection.current
|
||||
|
@ -110,15 +121,23 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
|||
anchor.Provider.defaultOptions()
|
||||
)
|
||||
const program = new anchor.Program(poolIdl, programId, provider)
|
||||
console.log(program)
|
||||
|
||||
const pool = (await program.account.poolAccount.fetch(
|
||||
POOL_PK
|
||||
)) as PoolAccount
|
||||
console.log(pool)
|
||||
|
||||
const [usdcVault, mangoVault] = await Promise.all([
|
||||
getTokenAccount(connection, pool.poolUsdc),
|
||||
getTokenAccount(connection, pool.poolWatermelon),
|
||||
])
|
||||
|
||||
console.log({ program, pool, usdcVault, mangoVault })
|
||||
|
||||
set((state) => {
|
||||
state.provider = provider
|
||||
state.program = program
|
||||
state.pool = pool
|
||||
state.usdcVault = usdcVault.account
|
||||
state.mangoVault = mangoVault.account
|
||||
})
|
||||
}
|
||||
},
|
||||
|
@ -135,6 +154,8 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
|||
walletOwner
|
||||
)
|
||||
|
||||
console.log('fetchWalletTokenAccounts', ownedTokenAccounts)
|
||||
|
||||
set((state) => {
|
||||
state.tokenAccounts = ownedTokenAccounts
|
||||
})
|
||||
|
@ -144,7 +165,7 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
|||
})
|
||||
}
|
||||
},
|
||||
async fetchVault() {
|
||||
async fetchUsdcVault() {
|
||||
const connection = get().connection.current
|
||||
const pool = get().pool
|
||||
const set = get().set
|
||||
|
@ -155,24 +176,124 @@ const useWalletStore = create<WalletStore>((set, get) => ({
|
|||
connection,
|
||||
pool.poolUsdc
|
||||
)
|
||||
console.log('fetchVault', vault)
|
||||
console.log('fetchUsdcVault', vault)
|
||||
|
||||
set((state) => {
|
||||
state.vault = vault
|
||||
state.usdcVault = vault
|
||||
})
|
||||
},
|
||||
async fetchVaultMint() {
|
||||
async fetchMints() {
|
||||
const connection = get().connection.current
|
||||
const vault = get().vault
|
||||
const pool = get().pool
|
||||
const mangoVault = get().mangoVault
|
||||
const usdcVault = get().usdcVault
|
||||
const set = get().set
|
||||
|
||||
const { account: mint } = await getMint(connection, vault.mint)
|
||||
console.log('fetchVaultMint', mint)
|
||||
const mintKeys = [mangoVault.mint, usdcVault.mint, pool.redeemableMint]
|
||||
const mints = await Promise.all(
|
||||
mintKeys.map((pk) => getMint(connection, pk))
|
||||
)
|
||||
console.log('fetchMints', mints)
|
||||
|
||||
set((state) => {
|
||||
state.mints[vault.mint.toBase58()] = mint
|
||||
for (const pa of mints) {
|
||||
state.mints[pa.publicKey.toBase58()] = pa.account
|
||||
console.log('mint', pa.publicKey.toBase58(), pa.account)
|
||||
}
|
||||
})
|
||||
},
|
||||
async submitContribution(amount: number) {
|
||||
console.log('submitContribution', amount)
|
||||
|
||||
const actions = get().actions
|
||||
await actions.fetchWalletTokenAccounts()
|
||||
|
||||
const {
|
||||
program,
|
||||
provider,
|
||||
pool,
|
||||
tokenAccounts,
|
||||
mints,
|
||||
usdcVault,
|
||||
current: wallet,
|
||||
connection: { current: connection },
|
||||
} = get()
|
||||
const usdcDecimals = mints[usdcVault.mint.toBase58()].decimals
|
||||
const redeemable = findLargestBalanceAccountForMint(
|
||||
mints,
|
||||
tokenAccounts,
|
||||
pool.redeemableMint
|
||||
)
|
||||
const usdc = findLargestBalanceAccountForMint(
|
||||
mints,
|
||||
tokenAccounts,
|
||||
usdcVault.mint
|
||||
)
|
||||
|
||||
const difference = amount - (redeemable?.balance || 0)
|
||||
const [poolSigner] = await anchor.web3.PublicKey.findProgramAddress(
|
||||
[pool.watermelonMint.toBuffer()],
|
||||
program.programId
|
||||
)
|
||||
|
||||
if (difference > 0) {
|
||||
const depositAmount = new anchor.BN(
|
||||
difference * Math.pow(10, usdcDecimals)
|
||||
)
|
||||
console.log(depositAmount.toString(), 'exchangeUsdcForReemable')
|
||||
|
||||
let redeemableAccPk = redeemable?.account?.publicKey
|
||||
const transaction = new Transaction()
|
||||
if (!redeemable) {
|
||||
const [ins, pk] = await createAssociatedTokenAccount(
|
||||
wallet.publicKey,
|
||||
wallet.publicKey,
|
||||
pool.redeemableMint
|
||||
)
|
||||
transaction.add(ins)
|
||||
redeemableAccPk = pk
|
||||
}
|
||||
transaction.add(
|
||||
program.instruction.exchangeUsdcForRedeemable(depositAmount, {
|
||||
accounts: {
|
||||
poolAccount: POOL_PK,
|
||||
poolSigner: poolSigner,
|
||||
redeemableMint: pool.redeemableMint,
|
||||
poolUsdc: pool.poolUsdc,
|
||||
userAuthority: provider.wallet.publicKey,
|
||||
userUsdc: usdc.account.publicKey,
|
||||
userRedeemable: redeemableAccPk,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
|
||||
},
|
||||
})
|
||||
)
|
||||
await sendTransaction({ transaction, wallet, connection })
|
||||
} else if (difference < 0) {
|
||||
const withdrawAmount = new anchor.BN(
|
||||
difference * -1 * Math.pow(10, usdcDecimals)
|
||||
)
|
||||
console.log(withdrawAmount.toString(), 'exchangeRedeemableForUsdc')
|
||||
await program.rpc.exchangeRedeemableForUsdc(withdrawAmount, {
|
||||
accounts: {
|
||||
poolAccount: POOL_PK,
|
||||
poolSigner: poolSigner,
|
||||
redeemableMint: pool.redeemableMint,
|
||||
poolUsdc: pool.poolUsdc,
|
||||
userAuthority: provider.wallet.publicKey,
|
||||
userUsdc: usdc.account.publicKey,
|
||||
userRedeemable: redeemable.account.publicKey,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
console.log('difference = 0 no submission needed', difference)
|
||||
return
|
||||
}
|
||||
|
||||
await actions.fetchWalletTokenAccounts()
|
||||
},
|
||||
},
|
||||
set: (fn) => set(produce(fn)),
|
||||
}))
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import {
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js'
|
||||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token'
|
||||
|
||||
const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID: PublicKey = new PublicKey(
|
||||
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
|
||||
)
|
||||
|
||||
export async function findAssociatedTokenAddress(
|
||||
walletAddress: PublicKey,
|
||||
tokenMintAddress: PublicKey
|
||||
): Promise<PublicKey> {
|
||||
return (
|
||||
await PublicKey.findProgramAddress(
|
||||
[
|
||||
walletAddress.toBuffer(),
|
||||
TOKEN_PROGRAM_ID.toBuffer(),
|
||||
tokenMintAddress.toBuffer(),
|
||||
],
|
||||
SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
|
||||
)
|
||||
)[0]
|
||||
}
|
||||
|
||||
export async function createAssociatedTokenAccount(
|
||||
fundingAddress: PublicKey,
|
||||
walletAddress: PublicKey,
|
||||
splTokenMintAddress: PublicKey
|
||||
): Promise<[TransactionInstruction, PublicKey]> {
|
||||
const associatedTokenAddress = await findAssociatedTokenAddress(
|
||||
walletAddress,
|
||||
splTokenMintAddress
|
||||
)
|
||||
const keys = [
|
||||
{
|
||||
pubkey: fundingAddress,
|
||||
isSigner: true,
|
||||
isWritable: true,
|
||||
},
|
||||
{
|
||||
pubkey: associatedTokenAddress,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
},
|
||||
{
|
||||
pubkey: walletAddress,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: splTokenMintAddress,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: SystemProgram.programId,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: TOKEN_PROGRAM_ID,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: SYSVAR_RENT_PUBKEY,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
]
|
||||
return [
|
||||
new TransactionInstruction({
|
||||
keys,
|
||||
programId: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
|
||||
data: Buffer.from([]),
|
||||
}),
|
||||
associatedTokenAddress,
|
||||
]
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import BN from 'bn.js'
|
||||
import useWalletStore from '../stores/useWalletStore'
|
||||
|
||||
function fixedPointToNumber(value: BN, decimals: number) {
|
||||
const divisor = new BN(10).pow(new BN(decimals))
|
||||
const quotient = value.div(divisor)
|
||||
const remainder = value.mod(divisor)
|
||||
return quotient.toNumber() + remainder.toNumber() / divisor.toNumber()
|
||||
}
|
||||
|
||||
export function getUsdcBalance() {
|
||||
const { tokenAccounts, mints, vault } = useWalletStore((state) => state)
|
||||
|
||||
if (!vault) return 0
|
||||
|
||||
const calculateBalance = (a) => {
|
||||
const mint = mints[a.account?.mint?.toBase58()]
|
||||
return mint ? fixedPointToNumber(a.account.amount, mint.decimals) : 0
|
||||
}
|
||||
|
||||
const usdcAddress = vault.mint.toBase58()
|
||||
|
||||
const usdcAccount = tokenAccounts.filter(
|
||||
(a) => a.account.mint.toBase58() === usdcAddress
|
||||
)
|
||||
|
||||
const usdcBalance = usdcAccount.map((a) => calculateBalance(a))
|
||||
|
||||
return usdcBalance.length ? usdcBalance[0] : 0
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
import { notify } from './notifications'
|
||||
import {
|
||||
Account,
|
||||
AccountInfo,
|
||||
Commitment,
|
||||
Connection,
|
||||
PublicKey,
|
||||
RpcResponseAndContext,
|
||||
SimulatedTransactionResponse,
|
||||
Transaction,
|
||||
TransactionSignature,
|
||||
} from '@solana/web3.js'
|
||||
import Wallet from '@project-serum/sol-wallet-adapter'
|
||||
import { Buffer } from 'buffer'
|
||||
import assert from 'assert'
|
||||
import { struct } from 'superstruct'
|
||||
|
||||
class TransactionError extends Error {
|
||||
public txid: string
|
||||
constructor(message: string, txid?: string) {
|
||||
super(message)
|
||||
this.txid = txid
|
||||
}
|
||||
}
|
||||
|
||||
export async function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export function getUnixTs() {
|
||||
return new Date().getTime() / 1000
|
||||
}
|
||||
|
||||
const DEFAULT_TIMEOUT = 30000
|
||||
|
||||
export async function sendTransaction({
|
||||
transaction,
|
||||
wallet,
|
||||
signers = [],
|
||||
connection,
|
||||
sendingMessage = 'Sending transaction...',
|
||||
successMessage = 'Transaction confirmed',
|
||||
timeout = DEFAULT_TIMEOUT,
|
||||
}: {
|
||||
transaction: Transaction
|
||||
wallet: Wallet
|
||||
signers?: Array<Account>
|
||||
connection: Connection
|
||||
sendingMessage?: string
|
||||
successMessage?: string
|
||||
timeout?: number
|
||||
}) {
|
||||
const signedTransaction = await signTransaction({
|
||||
transaction,
|
||||
wallet,
|
||||
signers,
|
||||
connection,
|
||||
})
|
||||
return await sendSignedTransaction({
|
||||
signedTransaction,
|
||||
connection,
|
||||
sendingMessage,
|
||||
successMessage,
|
||||
timeout,
|
||||
})
|
||||
}
|
||||
|
||||
export async function signTransaction({
|
||||
transaction,
|
||||
wallet,
|
||||
signers = [],
|
||||
connection,
|
||||
}: {
|
||||
transaction: Transaction
|
||||
wallet: Wallet
|
||||
signers?: Array<Account>
|
||||
connection: Connection
|
||||
}) {
|
||||
transaction.recentBlockhash = (
|
||||
await connection.getRecentBlockhash('max')
|
||||
).blockhash
|
||||
transaction.setSigners(wallet.publicKey, ...signers.map((s) => s.publicKey))
|
||||
if (signers.length > 0) {
|
||||
transaction.partialSign(...signers)
|
||||
}
|
||||
return await wallet.signTransaction(transaction)
|
||||
}
|
||||
|
||||
export async function signTransactions({
|
||||
transactionsAndSigners,
|
||||
wallet,
|
||||
connection,
|
||||
}: {
|
||||
transactionsAndSigners: {
|
||||
transaction: Transaction
|
||||
signers?: Array<Account>
|
||||
}[]
|
||||
wallet: Wallet
|
||||
connection: Connection
|
||||
}) {
|
||||
const blockhash = (await connection.getRecentBlockhash('max')).blockhash
|
||||
transactionsAndSigners.forEach(({ transaction, signers = [] }) => {
|
||||
transaction.recentBlockhash = blockhash
|
||||
transaction.setSigners(wallet.publicKey, ...signers.map((s) => s.publicKey))
|
||||
if (signers?.length > 0) {
|
||||
transaction.partialSign(...signers)
|
||||
}
|
||||
})
|
||||
return await wallet.signAllTransactions(
|
||||
transactionsAndSigners.map(({ transaction }) => transaction)
|
||||
)
|
||||
}
|
||||
|
||||
export async function sendSignedTransaction({
|
||||
signedTransaction,
|
||||
connection,
|
||||
sendingMessage = 'Sending transaction...',
|
||||
successMessage = 'Transaction confirmed',
|
||||
timeout = DEFAULT_TIMEOUT,
|
||||
}: {
|
||||
signedTransaction: Transaction
|
||||
connection: Connection
|
||||
sendingMessage?: string
|
||||
successMessage?: string
|
||||
timeout?: number
|
||||
}): Promise<string> {
|
||||
const rawTransaction = signedTransaction.serialize()
|
||||
const startTime = getUnixTs()
|
||||
notify({ message: sendingMessage })
|
||||
const txid: TransactionSignature = await connection.sendRawTransaction(
|
||||
rawTransaction,
|
||||
{
|
||||
skipPreflight: true,
|
||||
}
|
||||
)
|
||||
|
||||
console.log('Started awaiting confirmation for', txid)
|
||||
|
||||
let done = false
|
||||
;(async () => {
|
||||
while (!done && getUnixTs() - startTime < timeout) {
|
||||
connection.sendRawTransaction(rawTransaction, {
|
||||
skipPreflight: true,
|
||||
})
|
||||
await sleep(300)
|
||||
}
|
||||
})()
|
||||
try {
|
||||
await awaitTransactionSignatureConfirmation(txid, timeout, connection)
|
||||
} catch (err) {
|
||||
if (err.timeout) {
|
||||
throw new Error('Timed out awaiting confirmation on transaction')
|
||||
}
|
||||
let simulateResult: SimulatedTransactionResponse | null = null
|
||||
try {
|
||||
simulateResult = (
|
||||
await simulateTransaction(connection, signedTransaction, 'single')
|
||||
).value
|
||||
} catch (e) {
|
||||
console.log('Error: ', e)
|
||||
}
|
||||
if (simulateResult && simulateResult.err) {
|
||||
if (simulateResult.logs) {
|
||||
for (let i = simulateResult.logs.length - 1; i >= 0; --i) {
|
||||
const line = simulateResult.logs[i]
|
||||
if (line.startsWith('Program log: ')) {
|
||||
throw new TransactionError(
|
||||
'Transaction failed: ' + line.slice('Program log: '.length),
|
||||
txid
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new TransactionError(JSON.stringify(simulateResult.err), txid)
|
||||
}
|
||||
throw new TransactionError('Transaction failed', txid)
|
||||
} finally {
|
||||
done = true
|
||||
}
|
||||
notify({ message: successMessage, type: 'success', txid })
|
||||
|
||||
console.log('Latency', txid, getUnixTs() - startTime)
|
||||
return txid
|
||||
}
|
||||
|
||||
async function awaitTransactionSignatureConfirmation(
|
||||
txid: TransactionSignature,
|
||||
timeout: number,
|
||||
connection: Connection
|
||||
) {
|
||||
let done = false
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
// eslint-disable-next-line
|
||||
;(async () => {
|
||||
setTimeout(() => {
|
||||
if (done) {
|
||||
return
|
||||
}
|
||||
done = true
|
||||
console.log('Timed out for txid', txid)
|
||||
reject({ timeout: true })
|
||||
}, timeout)
|
||||
try {
|
||||
connection.onSignature(
|
||||
txid,
|
||||
(result) => {
|
||||
console.log('WS confirmed', txid, result)
|
||||
done = true
|
||||
if (result.err) {
|
||||
reject(result.err)
|
||||
} else {
|
||||
resolve(result)
|
||||
}
|
||||
},
|
||||
connection.commitment
|
||||
)
|
||||
console.log('Set up WS connection', txid)
|
||||
} catch (e) {
|
||||
done = true
|
||||
console.log('WS error in setup', txid, e)
|
||||
}
|
||||
while (!done) {
|
||||
// eslint-disable-next-line
|
||||
;(async () => {
|
||||
try {
|
||||
const signatureStatuses = await connection.getSignatureStatuses([
|
||||
txid,
|
||||
])
|
||||
const result = signatureStatuses && signatureStatuses.value[0]
|
||||
if (!done) {
|
||||
if (!result) {
|
||||
// console.log('REST null result for', txid, result);
|
||||
} else if (result.err) {
|
||||
console.log('REST error for', txid, result)
|
||||
done = true
|
||||
reject(result.err)
|
||||
}
|
||||
// @ts-ignore
|
||||
else if (
|
||||
!(
|
||||
result.confirmations ||
|
||||
result.confirmationStatus === 'confirmed' ||
|
||||
result.confirmationStatus === 'finalized'
|
||||
)
|
||||
) {
|
||||
console.log('REST not confirmed', txid, result)
|
||||
} else {
|
||||
console.log('REST confirmed', txid, result)
|
||||
done = true
|
||||
resolve(result)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (!done) {
|
||||
console.log('REST connection error: txid', txid, e)
|
||||
}
|
||||
}
|
||||
})()
|
||||
await sleep(300)
|
||||
}
|
||||
})()
|
||||
})
|
||||
done = true
|
||||
return result
|
||||
}
|
||||
|
||||
/** Copy of Connection.simulateTransaction that takes a commitment parameter. */
|
||||
async function simulateTransaction(
|
||||
connection: Connection,
|
||||
transaction: Transaction,
|
||||
commitment: Commitment
|
||||
): Promise<RpcResponseAndContext<SimulatedTransactionResponse>> {
|
||||
// @ts-ignore
|
||||
transaction.recentBlockhash = await connection._recentBlockhash(
|
||||
// @ts-ignore
|
||||
connection._disableBlockhashCaching
|
||||
)
|
||||
|
||||
const signData = transaction.serializeMessage()
|
||||
// @ts-ignore
|
||||
const wireTransaction = transaction._serialize(signData)
|
||||
const encodedTransaction = wireTransaction.toString('base64')
|
||||
const config: any = { encoding: 'base64', commitment }
|
||||
const args = [encodedTransaction, config]
|
||||
|
||||
// @ts-ignore
|
||||
const res = await connection._rpcRequest('simulateTransaction', args)
|
||||
if (res.error) {
|
||||
throw new Error('failed to simulate transaction: ' + res.error.message)
|
||||
}
|
||||
return res.result
|
||||
}
|
Loading…
Reference in New Issue