JRRF2026 平和島ビットコイン産業 MakerChipをつかったライトニングクレーンゲーム 備忘録・MakerChip作成編
忘れてしまわないうちに備忘録を残しておく。思い出したら随時追記。
MakerChip作成編ではMakerChipを3Dプリンタで作成、さらにNFCタグに書き込むところを記載。下図でいうと赤丸のところ。

MakerChipのデザインと造形条件
JRRF2026で配布したMakerChipが下図。オモテにはサークル名とLightning Networkの雷のシンボル。ウラにはおなじみのビットコインのシンボル。ビットコインのシンボルはパブリックドメインなのでデザイン流用OK。wikimediaからSVGファイルをダウンロードして使ってる。それを直径40mm、厚さ2.6mmにリサイズ。MakerChipの厚さ3.0mmがフォーマット。残りの0.4mmでサークル名を造形。

造形にはBambu lab A1miniを使用。AMSは持ってないので使ってない。0.4mmノズル、スムーズプレートを使用。プロセス設定はシステムデフォルトの0.2mm Standardで造形。スムーズプレートのせいか象足が出るので補正0.2mmかける。
Bの白とオレンジ本体は別々に造形。オレンジにはBの穴をもうけて白のBをはめ込む。オレンジ側にホール補正を+0.1だけかけて広くしてはめやすくする。補正掛け過ぎるとハメた後隙間が目立つのでギリギリ狙いが良い。難しいけど。

ビルドプレート全体
今回は合計480枚つくった。最初の150枚はBambu lab PLAベーシックのオレンジ色で造形。その後はeSUN PLAベーシックのオレンジ色で造形した。Bambuのフィラメントを切らした後に公式ストアから品切れのため買う事できず、Amazonで売ってたeSUNのフィラメントで作ることにした。白はBambuのPLAベーシック ジェイドホワイトのまま変更無し。
1回で15枚作成。スライサーでは完了予測時間2時間34分。実際はBをはめたりNFCタグシールの貼り付け、オレンジから白へのフィラメント交換があるので3時間ぐらいかかったはず。Bの面をベッド側にしているので白のBを反転して造形。スムーズプレートを使用したおかげて面が奇麗になった。

各層の設定など
1.20mm層で右スクロールに一時停止を入れておく。1.20mmが始まる直前に造形が止まるのでその時に白Bをはめる。全部はめたら造形再開。白B裏にオレンジのフィラメントがかかることで接着効果があり完成後に白Bが簡単に取れなくなる。一時停止してはめるのはちょっと手間だが接着剤が不要になるのでヨシ。
(穴寸法公差がきっちりでないので完成後にはめる方式だと白Bが取れてしまうことある)

2.00mmの層にも一時停止をかける。2.00mmの層がNFCタグシールを入れるポケットの蓋になる。ポケットは1.40-2.00mm層の間にあり、0.6mmの深さになっている。NFCタグシールの厚さはノギスで測ると0.1mmだけどチップがある箇所だけ0.3mmほどあった。ギリギリだとよろしくないと思い、余裕を持たせて0.6mmとした。

2.80mmの層にも一時停止をかける。2.80mmの層が始まると一時停止するので本体またはBambuStudioの方でフィラメントのアンロード、ロードして白フィラメントに手動で入れ替える。

LNbitsでLNURL-withdrawを作成
以下Pythonスクリプトを実行してMakerChipのウォレットとなるLNBitsでウォレットと残高、LNURL-withdrawを作成。スクリプト実行前にMakerChip用のアカウントを作成しておくこと。LNBits管理者は任意にウォレットに残高をつけることができる。
MakerChip用アカウント上で必要枚数回ループしてウォレット作成、残高チャージ、LNURL-withdraw作成をしている。作成したLNURL-withdrawはwithdraw_links.jsonに保存している。
<作成スクリプト makeMakerChipWallets.py>
import requests
import json
import time
# --- 設定項目 ---
BASE_URL = "https://ここにLNBitsのドメイン名"
ADMIN_USER = "<MakerChipのLNbitsの管理アカウント名>"
ADMIN_PASS = "管理アカウントのパスワード"
# 以下MakerChip用アカウントが無い場合は作成しておくこと
TARGET_USER = "makerchip" # ウォレットを量産する MakerChip用アカウント
TARGET_PASS = "MakerChip用アカウントのパスワード" # MakerChip用アカウントのパスワード
TARGET_USER_ID = "MakerChip用アカウントのID" # MakerChip用アカウントのID
#
COUNT = 480 # 作る個数
OUTPUT_FILE = "withdraw_links.json"
def get_session_headers(token):
return {"Content-Type": "application/json", "Cookie": f"cookie_access_token={token}"}
def run_batch():
all_links = []
try:
# 1. 管理者ログイン (残高PUT用)
print("管理者ログイン中...")
res_admin = requests.post(f"{BASE_URL}/api/v1/auth", json={"username": ADMIN_USER, "password": ADMIN_PASS})
res_admin.raise_for_status()
admin_token = res_admin.json().get("access_token")
# 2. ターゲットユーザーログイン (ウォレットPOST用)
print(f"ターゲットユーザー({TARGET_USER})ログイン中...")
res_user = requests.post(f"{BASE_URL}/api/v1/auth", json={"username": TARGET_USER, "password": TARGET_PASS})
res_user.raise_for_status()
user_token = res_user.json().get("access_token")
# 3. ループ処理
for i in range(1, COUNT + 1):
print(f"--- [{i}/{COUNT}] 処理中 ---")
# A. ウォレット作成 (既存のユーザーIDを使用)
w_res = requests.post(
f"{BASE_URL}/api/v1/wallet?usr={TARGET_USER_ID}",
json={"name": f"MakerChip{i}"},
headers=get_session_headers(user_token)
)
w_data = w_res.json()
wallet_id = w_data["id"]
wallet_adminkey = w_data["adminkey"]
# B. 残高チャージ (管理者権限)
requests.put(
f"{BASE_URL}/users/api/v1/balance",
json={"id": wallet_id, "amount": 450}, # 引き出し額と同額だと残高不足となる
headers=get_session_headers(admin_token)
).raise_for_status()
# C. Withdrawリンク作成
wd_payload = {
"title": f"平和島ビットコイン産業 MakerChip",
"min_withdrawable": 10, # 最小 10sats
"max_withdrawable": 450,
"uses": 1, # 1回引き出して終わり
"wait_time": 1,
"is_unique": False
}
wd_res = requests.post(
f"{BASE_URL}/withdraw/api/v1/links",
json=wd_payload,
headers={"X-Api-Key": wallet_adminkey, "Content-Type": "application/json"}
)
lnurl = wd_res.json().get("lnurl")
all_links.append({
"index": i,
"lnurl": lnurl,
"wallet_id": wallet_id,
"adminkey": wallet_adminkey
})
# ネットワーク負荷を考慮
time.sleep(0.2)
except Exception as e:
print(f"致命的なエラーが発生しました: {e}")
# ファイル保存
if all_links:
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
json.dump(all_links, f, indent=4)
print(f"\n完了! {len(all_links)}件のデータを {OUTPUT_FILE} に保存しました。")
if __name__ == "__main__":
print(f"MakerChip用ウォレット及びwithdrawリンク、作成開始")
run_batch()
<実行結果 withdraw_links.json>
[
{
"index": 1,
"lnurl": "LNURL1",
"wallet_id": "b8087d994d194f8ca6ce0a00cec17d05",
"adminkey": "2229e4e67b184a5ca193dc0831a6869c"
},
{
"index": 2,
"lnurl": "LNURL1",
"wallet_id": "682c5b944af1442eb4d7168e03f23fda",
"adminkey": "34c6bf6efca14e63a7c88f351d70aec4"
}
]
LNURL-withdrawの書き込み
書き込みスクリプトは以下。前提としてラズパイにパソリが接続、nfcpyでNFCタグが読み書きできること。こちらの記事を参照。そのラズパイでスクリプトを実行する。
MakerChipをパソリに置いてエンターキーを押す。押すと書き込み開始。書き込み終わったら終わったと表示されるのでMakerChipをパソリからどける。またMakerChipを置いて、の繰り返し。
<書き込みスクリプト writeLNURLwithdrawToNFCTag.py>
import nfc
import ndef
import json
import os
import sys
# --- 設定 ---
INPUT_FILE = "withdraw_links.json"
def write_to_tag(lnurl_link, index, wallet_id):
"""NFCタグへの書き込み処理本体"""
# 表示を「ユーザー名」から「ウォレットID」に変更
print(f"\n>>> [{index}] Wallet ID: {wallet_id} の書き込み待機中...")
print(f">>> 書き込むURL: lightning:{lnurl_link[:50]}...")
try:
with nfc.ContactlessFrontend('usb') as clf:
def on_connect(tag):
uri_data = f"lightning:{lnurl_link}"
record = ndef.UriRecord(uri_data)
if not tag.ndef:
print("エラー: NDEFフォーマットされていないタグです。")
return False
try:
tag.ndef.records = [record]
print("✅ 書き込み成功!カードを離してください。")
except Exception as e:
print(f"❌ 書き込み失敗: {e}")
return True
clf.connect(rdwr={'on-connect': on_connect})
except Exception as e:
print(f"リーダー接続エラー: {e}")
return False
return True
def main():
if not os.path.exists(INPUT_FILE):
print(f"エラー: {INPUT_FILE} が見つかりません。")
return
with open(INPUT_FILE, "r", encoding="utf-8") as f:
links_data = json.load(f)
print(f"合計 {len(links_data)} 件のデータを読み込みました。")
print("--------------------------------------------------")
for item in links_data:
idx = item["index"]
lnurl = item["lnurl"]
# JSONにある 'wallet_id' を使用。もし無ければ 'N/A' を表示
w_id = item.get("wallet_id", "N/A")
while True:
cmd = input(f"\n【{idx}/{len(links_data)}枚目】カードを置いて Enter を押してください (qで終了): ")
if cmd.lower() == 'q':
print("終了します。")
sys.exit()
# 引数を user から w_id に変更
success = write_to_tag(lnurl, idx, w_id)
if success:
break
else:
print("もう一度試しますか?")
print("\n==================================================")
print("すべてのカードの書き込み作業が完了しました。お疲れ様でした!")
if __name__ == "__main__":
main()






