ビットコいぬのマイニングは、ブロック(4日)ごとにXでポストした参加者を募集し、抽選によって選ばれた当選者に新規発行されたBTCuが割り当てられます。
抽選アルゴリズムと割り当てのルールについては他の記事で説明していますが、ここでは参加するために満たすべき最低条件を説明します。
ビットコいぬのマイニングに応募するためには、以下の条件を満たす必要があります。
抽選後に当選者についてのみ確認が行われます。
これらの参加条件を1つでも満たしていないと、当選しても割り当ては0になります。
1ブロックにつき、1アカウント1つのポストを応募できる。
応募できるポストは応募者本人のアカウントが投稿したポストに限られる。
保護された非公開アカウントではなく、公開されているアカウントであること。
非承認アカウントは開設から1年以上経過していなくてはならない。(承認アカウントはこの制約はない)
参加したブロックの期間中に投稿されたポストであること。
参加したブロックの終了時点から3日後までポストが削除されていないこと。
参加したブロックの終了時点から3日後にインプレッションが50以上あること。
ハッシュタグ #Bitcoinu が含まれていること。
最低20文字以上のテキストがあること。
テキストの先頭から30文字以内に後述のキーワードのうち少なくとも一つ含むこと。
キーワードは、以下の通り。
全ての参加者はこのリストのどの言語のキーワードを用いても良い。

参加条件を確認するコードは以下の通り。
// キーワードリスト
const KEYWORDS = [
"BitcoinuPost to Earn", "Bye Bitcoin", "Sell Bitcoin",
"BTCu is the true DigitalGold", "Bitcoin's Fatal Flaw", "ETH is the Internet Money",
"BTCu是真正的数字黄金", "比特币的致命缺陷", "ETH是互联网货币",
"BTCu ही सच्ची डिजिटल सोना है", "बिटकॉइन की घातक खामी", "ETH इंटरनेट मुद्रा है",
"BTCu es el verdadero oro digital", "El defecto fatal de Bitcoin", "ETH es el dinero de Internet",
"BTCu est le véritable or numérique", "Le défaut fatal du Bitcoin", "ETH est la monnaie d'Internet",
"BTCu هو الذهب الرقمي الحقيقي", "العيب القاتل للبيتكوين", "ETH هو مال الإنترنت",
"BTCu হল সত্যিকারের ডিজিটাল সোনা", "বিটকয়েনের মারাত্মক ত্রুটি", "ETH হল ইন্টারনেট মুদ্রা",
"BTCu é o verdadeiro ouro digital", "A falha fatal do Bitcoin", "ETH é o dinheiro da Internet",
"BTCu — это настоящие цифровое золото", "Фатальный недостаток Биткойна", "ETH — это интернет-деньги",
"ビットコいぬ", "BTCuは真のデジタルゴールド", "ビットコインの致命的な欠陥", "ETHはインターネット通貨",
"BTCu ist das wahre digitale Gold", "Der fatale Defekt von Bitcoin", "ETH ist das Internet-Geld",
"BTCu는 진정한 디지털 골드입니다", "비트코인의 치명적인 결함", "ETH는 인터넷 화폐입니다",
"BTCu là vàng kỹ thuật số thực sự", "Lỗi chí mạng của Bitcoin", "ETH là tiền Internet",
"BTCu è il vero oro digitale", "Il difetto fatale di Bitcoin", "ETH è la moneta di Internet",
"BTCu คือทองดิจิทัลที่แท้จริง", "ข้อบกพร่องร้ายแรงของบิตคอยน์", "ETH คือเงินอินเทอร์เน็ต",
"BTCu gerçek dijital altındır", "Bitcoin'in ölümcül kusuru", "ETH internet parasıdır",
"BTCu to prawdziwe cyfrowe złoto", "Śmiertelna wada Bitcoina", "ETH to pieniądz internetowy",
"BTCu — це справжнє цифрове золото", "Фатальна вада Біткоїна", "ETH — це інтернет-гроші",
"BTCu is het echte digitale goud", "Het fatale defect van Bitcoin", "ETH is het internetgeld",
"BTCu ਹੀ ਅਸਲ ਡਿਜਿਟਲ ਸੋਨਾ ਹੈ", "ਬਿਟਕੋਇਨ ਦੀ ਘਾਤਕ ਖਾਮੀ", "ETH ਇੰਟਰਨੈੱਟ ਮਨੀ ਹੈ",
"BTCu నిజమైన డిజిటల్ బంగారం", "బిట్కాయిన్ యొక్క ఘోరమైన లోపం", "ETH ఇంటర్నెట్ డబ్బు",
"BTCu हे खरे डिजिटल सोने आहे", "बिटकॉइनची घातक त्रुटी", "ETH ही इंटरनेट मुद्रा आहे",
"BTCu உண்மையான டிஜிட்டல் தங்கம்", "பிட்காயின் மாரடித்த குறைபாடு", "ETH இணைய பணமாகும்",
"BTCu حقیقی ڈیجیٹل سونا ہے", "بِٹ کوائن کی مہلک خامی", "ETH انٹرنیٹ پیسہ ہے",
"BTCu iku emas digital sejati", "Kelemahan fatal Bitcoin", "ETH iku dhuwit internet",
"BTCu ni dhahabu halisi ya Kidijitali", "Kasoro kubwa ya Bitcoin", "ETH ni pesa ya Mtandao",
"BTCu طلای دیجیتال واقعی است", "نقص مهلک بیت کوین", "ETH پول اینترنتی است",
"BTCu shine gaske zinariya dijital", "Babban lahani na Bitcoin", "ETH shine kuɗin intanet",
"BTCu သည် စစ်မှန်သော ဒစ်ဂျစ်တယ်ရွှေနဲသည်", "Bitcoin၏ ပြင်းထန်သော ချို့ယွင်းချက်", "ETH သည် အင်တာနက်ငွေ ဖြစ်သည်"
];
/**
* 指定時間だけ待機する関数(APIレート制限対策)
* @param {number} ms - 待機時間(ミリ秒)
* @return {Promise} - 待機完了後に解決されるPromise
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* テキストの先頭30文字以内に指定キーワードが含まれているかチェックする関数
* @param {string} text - チェック対象のテキスト
* @return {boolean} - キーワードが含まれていればtrue、そうでなければfalse
*/
function containsKeyword(text) {
if (!text) return false;
// テキストの先頭30文字を取得
const firstThirtyChars = text.substring(0, 30);
// キーワードのいずれかが含まれているかチェック
return KEYWORDS.some(keyword => firstThirtyChars.includes(keyword));
}
/**
* ポストが条件を満たすかチェックする関数
* @param {Object} postData - ポスト情報
* @param {Object} post - 検証対象のポスト
* @param {number} startTime - 指定期間の開始時間(UNIXタイムスタンプ)
* @param {number} endTime - 指定期間の終了時間(UNIXタイムスタンプ)
* @return {Object} - 検証結果
*/
function validatePost(postData, post, startTime, endTime) {
const errors = [];
const postDetail = postData.data.find(p => p.id === post.post_id);
// データが存在するか確認
if (!postDetail) {
return { status: false, errors: ['Failed to retrieve post data. The post ID may be incorrect, the post may have been deleted, or the post may be protected.'] };
}
// 投稿者情報を取得
const author = postData.includes.users.find(user => user.id === postDetail.author_id);
if (!author) {
return { status: false, errors: ['Failed to retrieve author data. The user may have been deleted or the account may be protected.'] };
}
// 1. author_idが入力値と一致するか
if (postDetail.author_id !== post.author_id) {
errors.push('Author ID does not match');
}
// 2. 非承認アカウントの場合、アカウント作成から1年以上経過しているか
if (!author.verified) {
const userCreatedAt = new Date(author.created_at).getTime();
const oneYearInMs = 365 * 24 * 60 * 60 * 1000;
if (endTime < userCreatedAt + oneYearInMs) {
errors.push('Unverified accounts must be created more than 1 year before the end time');
}
}
// 3. アカウントが公開されているか
if (author.protected) {
errors.push('Account is private');
}
// 4. ハッシュタグに#Bitcoinuが含まれているか
let hasBitcoinuHashtag = false;
if (postDetail.entities && postDetail.entities.hashtags) {
hasBitcoinuHashtag = postDetail.entities.hashtags.some(tag =>
tag.tag.toLowerCase() === 'bitcoinu'
);
}
if (!hasBitcoinuHashtag) {
errors.push('Hashtag #Bitcoinu is not included');
}
// 5. テキストが20文字以上あるか
if (!postDetail.text || postDetail.text.length < 20) {
errors.push('Text must be at least 20 characters');
}
// 6. テキストの先頭30文字以内に指定キーワードが含まれているか
if (!containsKeyword(postDetail.text)) {
errors.push('No specified keyword found in the first 30 characters of the text');
}
// 7. ポストが指定期間内に作成されたか
const postCreatedAt = new Date(postDetail.created_at).getTime();
if (postCreatedAt < startTime || postCreatedAt > endTime) {
errors.push('Post was not created within the specified time period');
}
// 8. インプレッション数が50以上あるか
if (!postDetail.public_metrics || postDetail.public_metrics.impression_count < 50) {
errors.push('Impression count must be at least 50');
}
return {
status: errors.length === 0,
errors: errors.length > 0 ? errors : []
};
}

