現象
Reactで以下(AnchorのProvider.send)を実装して試したところ、Transaction fee payer requiredエラーが出力される。
@project-serum / anchor - v0.20.1 / Provider / send
async function transfer() {
const provider = await getProvider();
const connection = provider.connection;
let recentBlockhash = await connection.getRecentBlockhash();
let transaction = new web3.Transaction({
recentBlockhash: recentBlockhash.blockhash,
feePayer: payer.publicKey,
});
const tx_instructions = web3.SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: toAccount.publicKey,
lamports: web3.LAMPORTS_PER_SOL * 0.1,
});
transaction.add(tx_instructions);
console.log(transaction);
try {
const signature = await provider.send(
transaction,
[payer], // signers
);
console.log('Sent! Signature: ', signature);
} catch (err) {
console.log("Transaction Error: ", err);
}
}
(この例だと「recentBlockhash」と「feePayer」の設定は不要だが、わかりやすくするためにあえて追加)
JSのconsole.logの出力結果。
Transaction Error: Error: Transaction fee payer required
at Transaction.compileMessage (transaction.ts:227:1)
at Transaction._compile (transaction.ts:376:1)
at Transaction.serializeMessage (transaction.ts:402:1)
at o (inpage.js:263:601)
at a.<anonymous> (inpage.js:321:1744)
at Generator.next (<anonymous>)
at inpage.js:321:622
at new Promise (<anonymous>)
at c (inpage.js:321:370)
at a.signTransaction (inpage.js:321:1566)
feePayerがnullになっている。
Transaction {signatures: Array(0), feePayer: undefined, instructions: Array(1), recentBlockhash: undefined, nonceInfo: undefined}feePayer: nullinstructions: [TransactionInstruction]nonceInfo: undefinedrecentBlockhash: "HiYZ1oyU7JP6y1g35QHeXZwKZygAKc4ptj3y94B5CLYG"signatures: []data: (...)keys: (...)programId: (...)signature: (...)[[Prototype]]: Object
feePayer: null
instructions: [TransactionInstruction]
nonceInfo: undefined
recentBlockhash: "HiYZ1oyU7JP6y1g35QHeXZwKZygAKc4ptj3y94B5CLYG"
signatures: []
data: (...)
keys: (...)
programId: (...)
signature: (...)
[[Prototype]]: Object
原因
トランザクション実行時のfeePayerが設定されていないため(Instructionではなく)。
対応
トランザクション実行時にウォレット(Phantom)接続してApproveする処理にする(ポップアップで表示される「Approve Transaction」画面を表示する)。
Phantomの呼び出し例。
async function getProvider() {
const network = "http://127.0.0.1:8899";
const connection = new Connection(network, opts.preflightCommitment);
const wallet = window.solana;
const provider = new Provider(
connection, wallet, opts.preflightCommitment,
);
return provider;
}
async function connectWallet() {
try {
const resp = await window.solana.connect();
console.log("Conneted! Public Key: ", resp.publicKey.toString());
} catch (err) {
console.log(err);
// => { code: 4001, message: 'User rejected the request.' }
}
}
Approveすると処理が通り、feePayerにデータ(_r {_bn: h})が入る。
Transaction {signatures: Array(0), feePayer: undefined, instructions: Array(1), recentBlockhash: undefined, nonceInfo: undefined}
feePayer: _r {_bn: h}
instructions: [TransactionInstruction]
nonceInfo: undefined
recentBlockhash: "5pCue1JGmvgcXpDQ1YsKFHHMvxwvZsrTsRQ2XbS9dC75"
signatures: (2) [{…}, {…}]
data: (...)
keys: (...)
programId: (...)
signature: (...)
[[Prototype]]: Object
add: add() { for (var _len4 = arguments.length, items = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { items[_key4] = arguments[_key4]; } if (items.length === 0) { throw new Error('No instructions'); } items.forEach(item => {…}
addSignature: ƒ addSignature(pubkey, signature)
compileMessage: compileMessage() { const { nonceInfo } = this; if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) { this.recentBlockhash = nonceInfo.nonce; this.instructions.unshift(nonceInfo.nonceInstruction); } const { recentBlockhash } = this; if (!recentBlockhash) { throw new Error('Transaction recentBlockhash required'); } if (this.instructions.length < 1) { console.warn('No instructions provided'); } let feePayer; if (this.feePayer) { feePayer = this.feePayer; } else if (this.signatures.length > 0 && this.signatures[0].publicKey) { // Use implicit fee payer feePayer = this.signatures[0].publicKey; } else { throw new Error('Transaction fee payer required'); } for (let i = 0; i < this.instructions.length; i++) { if (this.instructions[i].programId === undefined) { throw new Error(`Transaction instruction index ${i} has undefined program id`); } } const programIds = []; const accountMetas = []; this.instructions.forEach(instruction => {…}
constructor: class Transaction
data: (...)
keys: (...)
partialSign: ƒ partialSign()
programId: (...)
serialize: ƒ serialize(config)
serializeMessage: ƒ serializeMessage()
setSigners: setSigners() { for (var _len5 = arguments.length, signers = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { signers[_key5] = arguments[_key5]; } if (signers.length === 0) { throw new Error('No signers'); } const seen = new Set(); this.signatures = signers.filter(publicKey => {…}
sign: sign() { for (var _len6 = arguments.length, signers = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { signers[_key6] = arguments[_key6]; } if (signers.length === 0) { throw new Error('No signers'); } // Dedupe signers const seen = new Set(); const uniqueSigners = []; for (const signer of signers) { const key = signer.publicKey.toString(); if (seen.has(key)) { continue; } else { seen.add(key); uniqueSigners.push(signer); } } this.signatures = uniqueSigners.map(signer => {…}
signature: (...)
verifySignatures: ƒ verifySignatures()
_addSignature: _addSignature(pubkey, signature) { assert(signature.length === 64); const index = this.signatures.findIndex(sigpair => {…}
_compile: _compile() { const message = this.compileMessage(); const signedKeys = message.accountKeys.slice(0, message.header.numRequiredSignatures); if (this.signatures.length === signedKeys.length) { const valid = this.signatures.every((pair, index) => {…}
_partialSign: _partialSign(message) { const signData = message.serialize(); for (var _len8 = arguments.length, signers = new Array(_len8 > 1 ? _len8 - 1 : 0), _key8 = 1; _key8 < _len8; _key8++) { signers[_key8 - 1] = arguments[_key8]; } signers.forEach(signer => {…}
_serialize: _serialize(signData) { const { signatures } = this; const signatureCount = []; encodeLength(signatureCount, signatures.length); const transactionLength = signatureCount.length + signatures.length * 64 + signData.length; const wireTransaction = buffer__WEBPACK_IMPORTED_MODULE_1__.Buffer.alloc(transactionLength); assert(signatures.length < 256); buffer__WEBPACK_IMPORTED_MODULE_1__.Buffer.from(signatureCount).copy(wireTransaction, 0); signatures.forEach((_ref2, index) => {…}
_verifySignatures: ƒ _verifySignatures(signData, requireAllSignatures)
get data: ƒ data()
get keys: keys() { assert(this.instructions.length === 1); return this.instructions[0].keys.map(keyObj => {…}
get programId: ƒ programId()
get signature: ƒ signature()
[[Prototype]]: Object