-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add Solana contract call example (#1263)
* docs: add solana memo contract call demo * add demo * update files * update * lock * appease ts warning * tx with wallet * add en translate * upload lock file
- Loading branch information
Showing
7 changed files
with
472 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
--- | ||
nav: Course | ||
group: | ||
title: Advanced | ||
order: 2 | ||
--- | ||
|
||
# Interacting with Solana Contracts | ||
|
||
Interacting with blockchain contracts is one of the core functionalities of a DApp. Achieving this is not difficult, but it does require writing some logic. In this example, we will demonstrate how to connect to the Solana network using Ant Design Web3 and call the Memo contract. | ||
|
||
The Memo Program is a sample program on Solana, which is a simple contract used to add note information to transactions. You can find its documentation here: [Memo Program | Solana Program Library Docs](https://spl.solana.com/memo). | ||
|
||
We will demonstrate how to interact with Solana contracts in two ways: | ||
|
||
1. Directly calling the contract without using a wallet | ||
2. Calling the contract using a wallet | ||
|
||
Regardless of the method used, we need to use `@solana/web3.js`, an official JavaScript library provided by Solana for interacting with the Solana blockchain. Here, we use version 1.x, which is simpler compared to the latest version and sufficient for our example. | ||
|
||
<NormalInstallDependencies packageNames="@solana/web3.js@1" save="true"></NormalInstallDependencies> | ||
|
||
## Directly Calling the Contract | ||
|
||
Directly calling the contract means signing transactions using a private key without a wallet. This method is suitable for simple scenarios, such as debugging on the test network. | ||
|
||
<code src="./demos/solana-tx-without-wallet.tsx"></code> | ||
|
||
In the above code, we did not use any content from `@ant-design/web3-solana`; the actual call is made in the `writeMemo` function. | ||
|
||
In it, we first generate a keypair using Keypair, which includes a public key and a private key. Since we haven't interacted with it, it's just a set of values without any actual significance. | ||
|
||
> You can comment out the code that generates the keypair in `writeMemo` and check the generated public key in the browser console. You can also view this public key information on a blockchain explorer, or check the following address: [61QgmBmUw1Nekq7wGXtA7CCETUcbmTBJPjDWGLSrPqRK](https://solscan.io/account/61QgmBmUw1Nekq7wGXtA7CCETUcbmTBJPjDWGLSrPqRK) | ||
> | ||
> You will find that the displayed data on the page does not show any anomalies because it is indeed a legitimate public key located on the elliptic curve. | ||
Continuing, since the newly generated keypair does not have any balance, we need to obtain some test tokens from the faucet on the testnet. Here, we use the `requestAirdrop` method to send some test tokens to the specified address. | ||
|
||
Next, we use the `confirmTransaction` method to confirm whether the transaction was successful. If successful, we can call the Memo contract. | ||
|
||
> Requesting test tokens and confirming the transaction is not mandatory; you can use your existing account as long as it has enough balance to run our example code. | ||
> | ||
> 🛑 Note: Please do not enter your private key on any website casually!!! | ||
Once we ensure that the account we just created has enough balance, we can call the Memo contract. | ||
|
||
In Solana, a transaction can contain one or more instructions, each being a contract call. We can create a transaction with just a memo instruction, but to make it somewhat interesting, let's try executing two instructions in one transaction: a transfer and writing a memo. | ||
|
||
Continuing, we create a transfer instruction using the `transfer` method from `SystemProgram` and assign it to `transferInstruction`. Then, we create a transaction instruction object pointing to the Memo contract and assign it to `memoInstruction`. | ||
|
||
Then, we create a `Transaction` object and add these two instructions to the transaction. | ||
|
||
When creating the transfer instruction, we use the `SystemProgram.transfer` method. Since transfers are done using Solana's built-in contracts and are very common, @solana/web3.js provides a method for convenient calls. When using transfer, we need to provide the sender's address, recipient's address, and transfer amount. | ||
|
||
When creating the memo instruction, since @solana/web3.js does not provide a corresponding function, we manually construct the transfer instruction object. We need to specify the address of the contract being called (Memo) ([MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr](https://solscan.io/account/MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr?cluster=devnet)), the public keys involved in this instruction, and the data to be passed (here, a string converted to a Buffer). Note: Participant accounts must be signers of the transaction, so `isSigner` is set to `true`. Detailed documentation can be found here: https://spl.solana.com/memo#operational-notes | ||
|
||
Finally, we use the `sendAndConfirmTransaction` method to send the transaction. This method sends the transaction to the network and waits for confirmation. Note that when we call it, we pass the keypair to sign the transaction with the private key. | ||
|
||
If the transaction succeeds, it returns a TransactionSignature object, which is essentially a string representing the transaction's hash. We can use it to view the transaction details on a blockchain explorer. | ||
|
||
Let's click the [Send] button, run the code, and see the effect! | ||
|
||
If everything is successful, you'll see the transaction hash value output in the console, indicating that the transaction has been successfully sent to the network. You can view the transaction details on the [Solana Blockchain Explorer](https://solscan.io/?cluster=devnet). | ||
|
||
If something fails, it might be due to using the RPC too many times, triggering rate limiting. You can register for a new RPC address [here](https://zan.top/service/public-rpc/solana?chInfo=ch_antdweb3) and replace it, or try again after some time. | ||
|
||
## Calling the Contract Using a Wallet | ||
|
||
In dApp development, we typically use a wallet to manage the user's keypair. This approach is more secure and convenient, as users can sign transactions through the wallet instead of exposing their private keys to the dApp. | ||
|
||
<code src="./demos/solana-tx-with-wallet.tsx"></code> | ||
|
||
Compared to the code for directly calling contracts in the first section, this code is slightly reduced, removing steps such as generating a keypair and requesting test tokens. Instead, we directly use the keypair in the wallet to sign the transaction. | ||
|
||
The transaction processing logic is in `writeMemo`, similar to before. It creates a transfer instruction and a memo instruction and adds them to the transaction. | ||
|
||
The difference is that we use the `useWallet` hook to obtain the `sendTransaction` method provided by the wallet. This method sends the transaction to the network and waits for confirmation. Note that we do not pass the keypair when calling it. | ||
|
||
## Additional | ||
|
||
In the above two examples, we manually constructed `TransactionInstruction` objects when creating Memo instructions. While this approach offers flexibility, it also increases the workload. If you prefer a simpler method, you can use the `createMemoInstruction` method provided by `@solana/spl-memo`. The specific code can be found here: [@solana/spl-memo](https://github.com/solana-program/memo/blob/main/clients/js-legacy/src/index.ts). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
--- | ||
nav: 课程 | ||
group: | ||
title: 进阶 | ||
order: 2 | ||
--- | ||
|
||
# 与 Solana 合约交互 | ||
|
||
DApp 与区块链合约的交互是 DApp 的核心功能之一。实现这一点并不困难,但是需要写一些逻辑。在这个示例中,我们将演示如何使用 Ant Design Web3 连接 Solana 网络,并调用 Memo 合约。 | ||
|
||
Memo Program 是 Solana 上的一个示例程序,它是一个简单的合约,用于在交易中添加备注信息。你可以在这里找到它的文档:[Memo Program | Solana Program Library Docs](https://spl.solana.com/memo)。 | ||
|
||
我们接下来通过两种方式来演示如何与 Solana 合约交互: | ||
|
||
1. 不使用钱包,直接调用合约 | ||
2. 使用钱包调用合约 | ||
|
||
无论使用哪种方式,都需要用到 `@solana/web3.js`,一个 Solana 官方提供的,用于与 Solana 区块链进行交互的 JavaScript 库。我们这里使用 1.x 版本,相比最新版本,它使用起来简单一些,对于我们这个例子来说也足够了。 | ||
|
||
<NormalInstallDependencies packageNames="@solana/web3.js@1" save="true"></NormalInstallDependencies> | ||
|
||
## 直接调用合约 | ||
|
||
所谓的直接调用合约,是指不使用钱包,而是直接使用私钥来签署交易。这种方式适用于一些简单的场景,比如在测试网络上进行一些调试。 | ||
|
||
<code src="./demos/solana-tx-without-wallet.tsx"></code> | ||
|
||
在上面的代码中,我们并没有使用任何 `@ant-design/web3-solana` 中的内容,实际调用位于 `writeMemo` 函数。 | ||
|
||
在其中,我们先使用 Keypair 生成了一个密钥对,其中包含公钥和私钥。当然,由于我们并没有使用它进行过交互,所以它实际上就仅仅是一组数值而已,并没有实际的意义。 | ||
|
||
> 你可以将 writeMemo 中除了生成密钥对的代码注释掉,然后在浏览器控制台查看生成的公钥,并在区块链浏览器上查看这个公钥的信息。或者也可以查看这个地址: [61QgmBmUw1Nekq7wGXtA7CCETUcbmTBJPjDWGLSrPqRK](https://solscan.io/account/61QgmBmUw1Nekq7wGXtA7CCETUcbmTBJPjDWGLSrPqRK) | ||
> | ||
> 会发现页面上显示的数据并没有什么异常的地方,因为它确实是一个合法的、位于椭圆曲线上的公钥。 | ||
继续往下,由于刚生成的密钥对中并没有任何余额,所以我们需要从测试网的水龙头获取一些测试币。这里我们使用了 `requestAirdrop` 方法,它会向指定的地址发送一些测试币。 | ||
|
||
接着我们使用 `confirmTransaction` 方法来确认交易是否成功。如果成功,我们就可以调用 Memo 合约了。 | ||
|
||
> 请求测试币和确认交易完成这一步并不是必须的,你可以使用自己已有的账户,只要确保账户中有足够的余额用于运行我们的示例代码即可。 | ||
> | ||
> 🛑 注意:请不要在任何网站上随意输入自己的私钥!!! | ||
好了,确保我们刚刚创建的账户中有足够的余额后,我们就可以调用 Memo 合约了。 | ||
|
||
在 Solana 中,一个交易可以包含一条或多条指令,每条指令都是一个合约调用。我们当然可以只创建一个包含 memo 指令的交易。但是为了稍微有趣一些,让我们尝试在一条交易中执行两条指令:转账和写备忘。 | ||
|
||
继续往下看,我们先通过 `SystemProgram` 的 `transfer` 方法创建了一个转账指令,赋值给了 `transferInstruction`。然后通过创建 `TransactionInstruction` 对象,创建了一个指向 Memo 合约的指令,赋值给了 `memoInstruction`。 | ||
|
||
再然后,我们创建了一个 `Transaction` 对象,将这两个指令添加到了交易中。 | ||
|
||
在创建 transfer 指令时,我们使用了 `SystemProgram.transfer` 方法。由于转账是通过 Solana 提供的内置合约进行,并且它非常常用,所以 @solana/web3.js 直接提供了方法来方便我们调用。使用 transfer 时,我们需要提供支付方地址、接收方地址、转账金额等信息。 | ||
|
||
而在创建 memo 指令时,由于 @solana/web3.js 中没有提供对应的函数,所以我们选择手动构造转账指令对象。我们需要指明要调用的合约(Memo)的地址([MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr](https://solscan.io/account/MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr?cluster=devnet))、参与这条指令的公钥、以及要传递的数据(这里是一个字符串,但是转为了 Buffer)。注意:参与者账户必须是交易的签名者,所以 `isSigner` 值为 `true`。具体文档可以在这里找到:https://spl.solana.com/memo#operational-notes | ||
|
||
最后,我们使用 `sendAndConfirmTransaction` 方法来发送交易。这个方法会将交易发送到网络,并等待确认。注意我们调用它的时候,传入了密钥对,这样它就可以使用私钥来签名交易。 | ||
|
||
如果交易成功,它会返回一个 TransactionSignature 对象。实际上是一个字符串,表示交易的哈希值,我们可以通过它去区块链浏览器上查看交易的详情。 | ||
|
||
让我们点击【Send】按钮,运行一下代码,看看效果吧! | ||
|
||
如果一切正常,你会看到控制台输出了交易哈希值,这意味着交易已经成功发送到了网络。你可以在 [Solana 区块链浏览器](https://solscan.io/?cluster=devnet) 上查看这笔交易的详情。 | ||
|
||
如有有地方失败了,可能是由于 RPC 用了太多次,导致触发了限流。你可以自己在[这里](https://zan.top/service/public-rpc/solana?chInfo=ch_antdweb3)注册并替换一个新的 RPC 地址,或者等待一段时间后再试。 | ||
|
||
## 使用钱包调用合约 | ||
|
||
在 dApp 开发中,我们通常会使用钱包来管理用户的密钥对。这样可以更加安全,也更加方便。于是,用户可以通过钱包来签署交易,而不是将自己的私钥暴露给 dApp。 | ||
|
||
<code src="./demos/solana-tx-with-wallet.tsx"></code> | ||
|
||
相比第一节中直接调用合约的代码,这里做了些删减,去掉了生成密钥对、请求测试币空投等步骤。我们直接使用钱包中的密钥对来签署交易。 | ||
|
||
具体交易处理逻辑在 `writeMemo` 中,和之前的逻辑基本一致。同样创建了一个转账指令和一个 memo 指令,然后将它们添加到交易中。 | ||
|
||
不同的是,我们使用了 `useWallet` hook 来获取钱包提供的 `sendTransaction` 方法。这个方法会将交易发送到网络,并等待确认。注意我们调用它的时候,并没有传入密钥对。 | ||
|
||
## 其他 | ||
|
||
在上面两个示例中,我们在创建 Memo 指令时,均通过手动构造 `TransactionInstruction` 对象进行。这样做虽然可以让我们更加灵活,但是也增加了一些工作量。如果你想要更加简单的方式,可以使用 `@solana/spl-memo` 提供的 `createMemoInstruction` 方法。具体代码可在此处找到:[@solana/spl-memo](https://github.com/solana-program/memo/blob/main/clients/js-legacy/src/index.ts)。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import React, { useState } from 'react'; | ||
import { BrowserLink, ConnectButton, Connector, useAccount } from '@ant-design/web3'; | ||
import { useConnection, useWallet } from '@ant-design/web3-solana'; | ||
import { | ||
LAMPORTS_PER_SOL, | ||
PublicKey, | ||
SystemProgram, | ||
Transaction, | ||
TransactionInstruction, | ||
} from '@solana/web3.js'; | ||
import { Button, Card, ConfigProvider, Flex, Form, Input, InputNumber, message, Space } from 'antd'; | ||
|
||
type FormModel = { | ||
memo: string; | ||
amount: number; | ||
}; | ||
|
||
const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'); | ||
|
||
export default function MemoTx() { | ||
const [messageApi, contextHolder] = message.useMessage({ maxCount: 1 }); | ||
|
||
const { account } = useAccount(); | ||
const { connection } = useConnection(); | ||
const { publicKey, sendTransaction } = useWallet(); | ||
const [signatures, setSignatures] = useState<string[]>([]); | ||
|
||
const writeMemo = async (values: FormModel) => { | ||
if (!publicKey) { | ||
messageApi.error('Please connect wallet first'); | ||
return; | ||
} | ||
|
||
// Create a transaction instruction to transfer 0.1 SOL | ||
const transferIns = SystemProgram.transfer({ | ||
fromPubkey: publicKey, | ||
toPubkey: new PublicKey('4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t'), | ||
lamports: values.amount * LAMPORTS_PER_SOL, | ||
}); | ||
|
||
// Create a transaction instruction to write memo | ||
const memoIns = new TransactionInstruction({ | ||
programId: MEMO_PROGRAM_ID, | ||
keys: [{ pubkey: publicKey, isSigner: true, isWritable: false }], | ||
data: Buffer.from(values.memo, 'utf-8'), | ||
}); | ||
|
||
const tx = new Transaction().add(transferIns, memoIns); | ||
|
||
// Send transaction via wallet (通过钱包发送交易) | ||
// Once the transaction is confirmed, the signature will be returned (交易确认后会返回签名) | ||
const signature = await sendTransaction?.(tx, connection); | ||
|
||
setSignatures([...signatures, signature]); | ||
|
||
messageApi.info( | ||
<Space> | ||
<span>Transaction sent with signature: </span> | ||
<BrowserLink type="transaction" address={signature} ellipsis target="_blank" /> | ||
</Space>, | ||
60, | ||
); | ||
}; | ||
|
||
return ( | ||
<Flex gap={16} flex={1}> | ||
<Form<FormModel> | ||
style={{ flex: 1 }} | ||
layout="vertical" | ||
disabled={!publicKey} | ||
initialValues={{ amount: 0.1, memo: 'Hello, Solana!' }} | ||
onFinish={writeMemo} | ||
> | ||
<Form.Item name="amount" label="Amount" required rules={[{ required: true }]}> | ||
<InputNumber min={0.1} precision={1} step={0.1} style={{ width: 400 }} /> | ||
</Form.Item> | ||
|
||
<Form.Item name="memo" label="Memo" required rules={[{ required: true }]}> | ||
<Input.TextArea style={{ width: 400 }} /> | ||
</Form.Item> | ||
|
||
<Space> | ||
<Button type="primary" htmlType="submit"> | ||
{publicKey ? 'Send Memo' : 'Please connect wallet first'} | ||
</Button> | ||
<Connector> | ||
<ConnectButton disabled={false} /> | ||
</Connector> | ||
</Space> | ||
</Form> | ||
|
||
<Card | ||
title="Memo Signatures" | ||
style={{ flex: 1 }} | ||
actions={[ | ||
<Space key="txs"> | ||
<span>View all your transactions: </span> | ||
{account && <BrowserLink address={account.address} type="address" target="_blank" />} | ||
</Space>, | ||
]} | ||
> | ||
<Space direction="vertical"> | ||
<ConfigProvider theme={{ components: { Typography: { fontSize: 12 } } }}> | ||
<Space> | ||
Example Transaction:{' '} | ||
<BrowserLink | ||
type="transaction" | ||
address="48yFNeh7dfX2rVb3KzYWgNR8r9RKttAGbwpw4ZLypHnQ3qaT8dGDA8KvTEorYx6GBBiCGsVYkhyECiyn7XqYwQyu" | ||
target="_blank" | ||
ellipsis | ||
/> | ||
</Space> | ||
{signatures.map((signature) => ( | ||
<BrowserLink key={signature} type="transaction" address={signature} target="_blank" /> | ||
))} | ||
</ConfigProvider> | ||
</Space> | ||
</Card> | ||
|
||
{contextHolder} | ||
</Flex> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react'; | ||
import { solanaDevnet, SolanaWeb3ConfigProvider } from '@ant-design/web3-solana'; | ||
|
||
import MemoTx from './solana-memo-call'; | ||
|
||
const rpcProvider = () => `https://api.zan.top/node/v1/solana/devnet/${YOUR_ZAN_API_KEY}`; | ||
|
||
export default function CallSolanaMemoApp() { | ||
return ( | ||
<SolanaWeb3ConfigProvider | ||
autoAddRegisteredWallets | ||
chains={[solanaDevnet]} | ||
rpcProvider={rpcProvider} | ||
> | ||
<MemoTx /> | ||
</SolanaWeb3ConfigProvider> | ||
); | ||
} |
Oops, something went wrong.