Anchorで開発したプログラムをExpress x Prisma x TypeScriptのサーバーで読み込もうとしたら(厳密には、Anchorが出力されたIDLを読み込んでるだけでスマコンは呼んでない)発生したエラー。
Anchor内でtest実行する分には問題なかったが、上記環境からAnchorのIDLファイルをExpress内で呼ぼうとすると発生。
Agenda
ANCHOR_PROVIDER_URL is not definedエラー
現象
ExpressでAPIを実行すると以下のエラー。
Error: ANCHOR_PROVIDER_URL is not defined
原因
環境変数に ANCHOR_PROVIDER_URL がないため。
対応
envファイルに以下を追加する。そのあとにExpressを再起動(npm run dev)して環境変数を読み込ませる。
ANCHOR_PROVIDER_URL="http://localhost:8899"
ANCHOR_WALLET is not setエラー
現象
ExpressでAPIを実行すると以下のエラー。
Error: expected environment variable `ANCHOR_WALLET` is not set.
原因
環境変数に ANCHOR_WALLET がないため。
対応
envファイルに以下を追加する。そのあとにExpressを再起動(npm run dev)して環境変数を読み込ませる。
ANCHOR_WALLET="./id.json"
no such file or directory(id.json)エラー
現象
ExpressでAPIを実行すると以下のエラー。
Error: ENOENT: no such file or directory, open '~/.config/solana/id.json'
原因
環境変数 ANCHOR_WALLET のファイルパスが間違っているため。
対応
envファイルを正しいパスに書き直す。自分の場合は以下が正しいパスだった。
そのあとにExpressを再起動(npm run dev)して環境変数を読み込ませる。
ANCHOR_WALLET="./id.json"
補足
相対パスや絶対パスで自分のHomeディレクトリ指定だとダメで、PJルートフォルダ配下じゃないと読み取れなかった。
以下が指定がダメだった例。
~/.config/solana/id.json
/Users/user/.config/solana/id.json
あと、設定を変更したらExpressを再起動しないと環境変数が読み込まれないため注意。
最終版ソース
少し複雑で長くなってしまう上に作業用メモも多いが、理解に重要なため、必要なファイル群と全ソースを以下に記載。
app/contollers/events.ts
// --- Lib ---
import * as fs from 'fs';
// --- Express/Prisma ---
import * as dotenv from 'dotenv'
import { Router } from 'express';
import { PrismaClient } from '@prisma/client';
// --- Solana/Anchor ---
import * as anchor from "@coral-xyz/anchor";
import {
Keypair,
PublicKey,
} from '@solana/web3.js';
dotenv.config();
export const router = Router();
const prisma = new PrismaClient();
router.get("/", async (req, res, next) => {
try {
res.header('Content-Type', 'application/json; charset=utf-8');
const events = await prisma.event.findMany();
return res.json(events);
} catch (error) {
return res.status(400).json(error);
}
});
router.post("/", async (req, res, next) => {
// PDA
let publickey: PublicKey; // For seed
let pda: PublicKey;
let bump: number;
try {
// Anchor Config
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const idl = JSON.parse(
fs.readFileSync("./app/target/idl/hok.json", "utf8")
);
const programId = new anchor.web3.PublicKey("CTgTMTJ2jkCU2TKkWa7FNiLa8aEcsu1knKPahSN9tLD"); // TODO: add to .env
const program = new anchor.Program(idl, programId);
// PDA
publickey = Keypair.generate().publicKey; // Seed for PDA
[pda, bump] = await PublicKey.findProgramAddressSync(
[
Buffer.from('prize'),
publickey.toBuffer(),
provider.wallet.publicKey.toBuffer(),
],
program.programId,
);
console.log('pda =>', pda.toString());
console.log('bump =>', bump);
} catch (error) {
console.log(error);
return res.status(400).json(error);
}
try {
res.header('Content-Type', 'application/json; charset=utf-8');
const { startDate, endDate } = req.body;
const event = await prisma.event.create({
data: {
publickey: publickey.toString(),
startDate: startDate,
endDate: endDate,
collectFeeWallet: pda.toString(),
prize: 0,
}
});
return res.json(event);
} catch (error) {
return res.status(400).json(error);
}
});
router.patch("/", async (req, res, next) => {
try {
res.header('Content-Type', 'application/json; charset=utf-8');
const publickey = Keypair.generate().publicKey; // Seed for PDA
const { startDate, endDate } = req.body;
const event = await prisma.event.create({
data: {
publickey: publickey.toString(),
startDate: startDate,
endDate: endDate,
prize: 0,
}
});
return res.json(event);
} catch (error) {
return res.status(400).json(error);
}
});
app/target/idl/hok.json
{
"version": "0.1.0",
"name": "hok",
"instructions": [
{
"name": "transferProviderSol",
"accounts": [
{
"name": "pda",
"isMut": true,
"isSigner": false
},
{
"name": "taker",
"isMut": true,
"isSigner": false
},
{
"name": "event",
"isMut": false,
"isSigner": false
},
{
"name": "authority",
"isMut": false,
"isSigner": true
},
{
"name": "systemProgram",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "amount",
"type": "u64"
},
{
"name": "bump",
"type": "u8"
}
]
}
],
"metadata": {
"address": "CTgTMTJ2jkCU2TKkWa7FNiLa8aEcsu1knKPahSN9tLD"
}
}
.env.dev
# Anchor
ANCHOR_PROVIDER_URL="http://localhost:8899"
ANCHOR_WALLET="./id.json"
補足:.envに上記を記述してもよいし、envファイルを使わずにターミナル上で上記コマンドを入力してからExpress起動でも問題ない。どうにかして上記の環境変数を読み込めればよい。
package.json
{
"name": "backend-server",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www",
"dev": "nodemon -r dotenv/config app.ts dotenv_config_path=.env.dev"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"dependencies": {
"@coral-xyz/anchor": "^0.26.0",
"@prisma/client": "^4.8.0",
"@solana/web3.js": "^1.73.0",
"@types/cookie-parser": "^1.4.3",
"@types/debug": "^4.1.7",
"@types/express": "^4.17.14",
"@types/http-errors": "^2.0.1",
"@types/morgan": "^1.9.3",
"@types/node": "^18.11.9",
"cookie-parser": "~1.4.6",
"cors": "^2.8.5",
"debug": "~4.3.4",
"dotenv": "^16.0.3",
"ejs": "~3.1.8",
"express": "~4.18.2",
"http-errors": "~2.0.0",
"morgan": "~1.10.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
},
"devDependencies": {
"@types/cors": "^2.8.12",
"nodemon": "^2.0.20",
"npm": "^8.19.2",
"prisma": "^4.8.0"
}
}
ファイル構成(PJルート)
.env
.env.dev
.git
.gitignore
LICENSE
app.ts
docs
node_modules
package.json
public
app
├── contollers
│ └── events.ts
├── modules
├── services
├── target
│ └── idl
│ └── hok.json
└── views
└── error.ejs
bin
id.json
package-lock.json
prisma
tsconfig.json
補足
Macで環境変数を出力したい場合は以下コマンド
% export -p