Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
aisbf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexlab
aisbf
Commits
d58f1e5e
Commit
d58f1e5e
authored
Apr 22, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
v0.99.50: per-payment crypto addresses, fix crypto topup modal loading
parent
b4d36755
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
100 additions
and
46 deletions
+100
-46
__init__.py
aisbf/__init__.py
+1
-1
wallet.py
aisbf/payments/crypto/wallet.py
+24
-43
migrations.py
aisbf/payments/migrations.py
+66
-0
routes.py
aisbf/payments/wallet/routes.py
+7
-0
pyproject.toml
pyproject.toml
+1
-1
setup.py
setup.py
+1
-1
No files found.
aisbf/__init__.py
View file @
d58f1e5e
...
@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
...
@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
from
.handlers
import
RequestHandler
,
RotationHandler
,
AutoselectHandler
from
.handlers
import
RequestHandler
,
RotationHandler
,
AutoselectHandler
from
.utils
import
count_messages_tokens
,
split_messages_into_chunks
,
get_max_request_tokens_for_model
from
.utils
import
count_messages_tokens
,
split_messages_into_chunks
,
get_max_request_tokens_for_model
__version__
=
"0.99.
49
"
__version__
=
"0.99.
50
"
__all__
=
[
__all__
=
[
# Config
# Config
"config"
,
"config"
,
...
...
aisbf/payments/crypto/wallet.py
View file @
d58f1e5e
...
@@ -144,65 +144,46 @@ class CryptoWalletManager:
...
@@ -144,65 +144,46 @@ class CryptoWalletManager:
'derivation_index'
:
index
'derivation_index'
:
index
}
}
async
def
get_or_create_user_address
(
self
,
user_id
:
int
,
crypto_type
:
str
)
->
str
:
async
def
create_payment_address
(
self
,
user_id
:
int
,
crypto_type
:
str
,
payment_id
:
str
)
->
str
:
"""Get existing address or create new one for user"""
"""Derive a fresh address for each payment request"""
# Check if user already has address
placeholder
=
'?'
if
self
.
db
.
db_type
==
'sqlite'
else
'
%
s'
with
self
.
db
.
_get_connection
()
as
conn
:
cursor
=
conn
.
cursor
()
placeholder
=
'?'
if
self
.
db
.
db_type
==
'sqlite'
else
'
%
s'
cursor
.
execute
(
f
"""
SELECT address FROM user_crypto_addresses
WHERE user_id = {placeholder} AND crypto_type = {placeholder}
"""
,
(
user_id
,
crypto_type
))
existing
=
cursor
.
fetchone
()
if
existing
:
return
existing
[
0
]
# Get next available index
with
self
.
db
.
_get_connection
()
as
conn
:
with
self
.
db
.
_get_connection
()
as
conn
:
cursor
=
conn
.
cursor
()
cursor
=
conn
.
cursor
()
placeholder
=
'?'
if
self
.
db
.
db_type
==
'sqlite'
else
'
%
s'
cursor
.
execute
(
f
"""
cursor
.
execute
(
f
"""
SELECT COALESCE(MAX(derivation_index), -1)
as max_idx
SELECT COALESCE(MAX(derivation_index), -1)
FROM user_crypto_addresses
FROM user_crypto_addresses
WHERE crypto_type = {placeholder}
WHERE crypto_type = {placeholder}
"""
,
(
crypto_type
,))
"""
,
(
crypto_type
,))
max_index
=
cursor
.
fetchone
()
next_index
=
cursor
.
fetchone
()[
0
]
+
1
next_index
=
max_index
[
0
]
+
1
# Derive new address
address_info
=
self
.
derive_address
(
crypto_type
,
next_index
)
address_info
=
self
.
derive_address
(
crypto_type
,
next_index
)
# Store in database
with
self
.
db
.
_get_connection
()
as
conn
:
with
self
.
db
.
_get_connection
()
as
conn
:
cursor
=
conn
.
cursor
()
cursor
=
conn
.
cursor
()
placeholder
=
'?'
if
self
.
db
.
db_type
==
'sqlite'
else
'
%
s'
cursor
.
execute
(
f
"""
cursor
.
execute
(
f
"""
INSERT INTO user_crypto_addresses
INSERT INTO user_crypto_addresses
(user_id, crypto_type, address, derivation_path, derivation_index)
(user_id, crypto_type, address, derivation_path, derivation_index, payment_id)
VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder})
VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder}, {placeholder})
"""
,
(
"""
,
(
user_id
,
crypto_type
,
address_info
[
'address'
],
user_id
,
address_info
[
'derivation_path'
],
address_info
[
'derivation_index'
],
payment_id
))
crypto_type
,
address_info
[
'address'
],
address_info
[
'derivation_path'
],
address_info
[
'derivation_index'
]
))
conn
.
commit
()
conn
.
commit
()
# Create wallet entry
with
self
.
db
.
_get_connection
()
as
conn
:
with
self
.
db
.
_get_connection
()
as
conn
:
cursor
=
conn
.
cursor
()
cursor
=
conn
.
cursor
()
placeholder
=
'?'
if
self
.
db
.
db_type
==
'sqlite'
else
'
%
s'
insert_or_ignore
=
"INSERT OR IGNORE"
if
self
.
db
.
db_type
==
'sqlite'
else
"INSERT"
on_conflict
=
""
if
self
.
db
.
db_type
==
'sqlite'
else
" ON CONFLICT DO NOTHING"
cursor
.
execute
(
f
"""
cursor
.
execute
(
f
"""
INSERT
INTO user_crypto_wallets
{insert_or_ignore}
INTO user_crypto_wallets
(user_id, crypto_type, balance_crypto, balance_fiat)
(user_id, crypto_type, balance_crypto, balance_fiat)
VALUES ({placeholder}, {placeholder}, 0, 0)
VALUES ({placeholder}, {placeholder}, 0, 0)
{on_conflict}
"""
,
(
user_id
,
crypto_type
))
"""
,
(
user_id
,
crypto_type
))
conn
.
commit
()
conn
.
commit
()
logger
.
info
(
f
"Created {crypto_type} address for user {user_id}: {address_info['address']}"
)
logger
.
info
(
f
"Created {crypto_type} payment address for user {user_id} (payment {payment_id}): {address_info['address']}"
)
return
address_info
[
'address'
]
return
address_info
[
'address'
]
async
def
get_or_create_user_address
(
self
,
user_id
:
int
,
crypto_type
:
str
)
->
str
:
"""Legacy: creates a new payment address with a generic payment_id."""
import
uuid
return
await
self
.
create_payment_address
(
user_id
,
crypto_type
,
f
"legacy-{uuid.uuid4().hex[:8]}"
)
aisbf/payments/migrations.py
View file @
d58f1e5e
...
@@ -61,6 +61,7 @@ class PaymentMigrations:
...
@@ -61,6 +61,7 @@ class PaymentMigrations:
self
.
_create_notification_tables
(
cursor
,
auto_increment
,
timestamp_default
,
boolean_type
,
text_type
,
decimal_type
)
self
.
_create_notification_tables
(
cursor
,
auto_increment
,
timestamp_default
,
boolean_type
,
text_type
,
decimal_type
)
self
.
_create_wallet_tables
(
cursor
,
auto_increment
,
timestamp_default
,
boolean_type
,
text_type
,
decimal_type
)
self
.
_create_wallet_tables
(
cursor
,
auto_increment
,
timestamp_default
,
boolean_type
,
text_type
,
decimal_type
)
self
.
_add_stripe_customer_id_column
(
cursor
)
self
.
_add_stripe_customer_id_column
(
cursor
)
self
.
_migrate_per_payment_addresses
(
cursor
)
self
.
_insert_default_data
(
cursor
)
self
.
_insert_default_data
(
cursor
)
conn
.
commit
()
conn
.
commit
()
...
@@ -469,6 +470,71 @@ class PaymentMigrations:
...
@@ -469,6 +470,71 @@ class PaymentMigrations:
)
)
'''
)
'''
)
def
_migrate_per_payment_addresses
(
self
,
cursor
):
"""Migrate user_crypto_addresses to support per-payment addresses (drop unique user+crypto constraint, add payment_id)"""
try
:
if
self
.
db_type
==
'sqlite'
:
cursor
.
execute
(
"PRAGMA table_info(user_crypto_addresses)"
)
columns
=
[
row
[
1
]
for
row
in
cursor
.
fetchall
()]
if
'payment_id'
not
in
columns
:
cursor
.
execute
(
"ALTER TABLE user_crypto_addresses ADD COLUMN payment_id VARCHAR(64)"
)
logger
.
info
(
"✅ Added payment_id column to user_crypto_addresses"
)
# SQLite: recreate table without UNIQUE(user_id, crypto_type) if it exists
# Check via index list
cursor
.
execute
(
"PRAGMA index_list(user_crypto_addresses)"
)
indexes
=
cursor
.
fetchall
()
has_user_crypto_unique
=
any
(
idx
[
2
]
==
1
and
'user_id'
in
(
idx
[
1
]
or
''
)
or
'crypto'
in
(
idx
[
1
]
or
''
)
for
idx
in
indexes
)
if
has_user_crypto_unique
:
# Recreate without the constraint
cursor
.
execute
(
"""
CREATE TABLE IF NOT EXISTS user_crypto_addresses_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
crypto_type VARCHAR(20) NOT NULL,
address VARCHAR(255) NOT NULL UNIQUE,
derivation_path VARCHAR(100) NOT NULL,
derivation_index INTEGER NOT NULL,
payment_id VARCHAR(64),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)
"""
)
cursor
.
execute
(
"""
INSERT INTO user_crypto_addresses_new
(id, user_id, crypto_type, address, derivation_path, derivation_index, created_at)
SELECT id, user_id, crypto_type, address, derivation_path, derivation_index, created_at
FROM user_crypto_addresses
"""
)
cursor
.
execute
(
"DROP TABLE user_crypto_addresses"
)
cursor
.
execute
(
"ALTER TABLE user_crypto_addresses_new RENAME TO user_crypto_addresses"
)
logger
.
info
(
"✅ Recreated user_crypto_addresses without UNIQUE(user_id, crypto_type)"
)
else
:
# mysql
cursor
.
execute
(
"""
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'user_crypto_addresses' AND COLUMN_NAME = 'payment_id'
"""
)
if
not
cursor
.
fetchone
():
cursor
.
execute
(
"ALTER TABLE user_crypto_addresses ADD COLUMN payment_id VARCHAR(64)"
)
logger
.
info
(
"✅ Added payment_id column to user_crypto_addresses"
)
# Drop unique constraint on (user_id, crypto_type) if present
cursor
.
execute
(
"""
SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_NAME = 'user_crypto_addresses'
AND CONSTRAINT_TYPE = 'UNIQUE'
AND CONSTRAINT_NAME != 'address'
"""
)
for
row
in
cursor
.
fetchall
():
try
:
cursor
.
execute
(
f
"ALTER TABLE user_crypto_addresses DROP INDEX `{row[0]}`"
)
logger
.
info
(
f
"✅ Dropped unique constraint {row[0]} from user_crypto_addresses"
)
except
Exception
:
pass
except
Exception
as
e
:
logger
.
warning
(
f
"Migration per-payment addresses: {e}"
)
def
_add_stripe_customer_id_column
(
self
,
cursor
):
def
_add_stripe_customer_id_column
(
self
,
cursor
):
"""Add Stripe customer ID column to users table"""
"""Add Stripe customer ID column to users table"""
try
:
try
:
...
...
aisbf/payments/wallet/routes.py
View file @
d58f1e5e
...
@@ -123,6 +123,13 @@ async def initiate_topup(
...
@@ -123,6 +123,13 @@ async def initiate_topup(
metadata
=
{
"type"
:
"wallet_topup"
}
metadata
=
{
"type"
:
"wallet_topup"
}
)
)
return
{
"order_id"
:
order
.
id
,
"payment_method"
:
"paypal"
}
return
{
"order_id"
:
order
.
id
,
"payment_method"
:
"paypal"
}
elif
topup_data
.
payment_method
in
(
"bitcoin"
,
"ethereum"
,
"usdt"
,
"usdc"
):
import
uuid
crypto_type_map
=
{
"bitcoin"
:
"btc"
,
"ethereum"
:
"eth"
,
"usdt"
:
"usdt"
,
"usdc"
:
"usdc"
}
crypto_type
=
crypto_type_map
[
topup_data
.
payment_method
]
payment_id
=
uuid
.
uuid4
()
.
hex
address
=
await
payment_service
.
wallet_manager
.
create_payment_address
(
user_id
,
crypto_type
,
payment_id
)
return
{
"address"
:
address
,
"payment_method"
:
topup_data
.
payment_method
,
"payment_id"
:
payment_id
}
else
:
else
:
raise
HTTPException
(
raise
HTTPException
(
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
status_code
=
status
.
HTTP_400_BAD_REQUEST
,
...
...
pyproject.toml
View file @
d58f1e5e
...
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
...
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
[project]
name
=
"aisbf"
name
=
"aisbf"
version
=
"0.99.
49
"
version
=
"0.99.
50
"
description
=
"AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
description
=
"AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme
=
"README.md"
readme
=
"README.md"
license
=
"GPL-3.0-or-later"
license
=
"GPL-3.0-or-later"
...
...
setup.py
View file @
d58f1e5e
...
@@ -49,7 +49,7 @@ class InstallCommand(_install):
...
@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup
(
setup
(
name
=
"aisbf"
,
name
=
"aisbf"
,
version
=
"0.99.
49
"
,
version
=
"0.99.
50
"
,
author
=
"AISBF Contributors"
,
author
=
"AISBF Contributors"
,
author_email
=
"stefy@nexlab.net"
,
author_email
=
"stefy@nexlab.net"
,
description
=
"AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
,
description
=
"AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment