Skip to content

Commit 619d628

Browse files
committed
Merge branch 'main' of github.com:coinbase/cdp-sdk into milan/CDPSDK-2436
2 parents 159c285 + 413b179 commit 619d628

110 files changed

Lines changed: 9058 additions & 4484 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# Usage: uv run python solana/transactions/send_sponsored_transaction.py
2+
# uv run python solana/transactions/send_sponsored_transaction.py
3+
# [--sender <sender_address>] - optional, if not provided, a new account will be created and funded from the faucet
4+
# [--destination <destination_address>] - optional, if not provided, a default destination address will be used
5+
# [--amount <amount_in_lamports>] - optional, if not provided, a default amount of 1000 lamports will be used
6+
7+
import argparse
8+
import asyncio
9+
import base64
10+
import time
11+
12+
from solana.rpc.api import Client as SolanaClient
13+
from solders.message import Message
14+
from solders.hash import Hash
15+
from solders.signature import Signature
16+
from solders.pubkey import Pubkey as PublicKey
17+
from solders.system_program import TransferParams, transfer
18+
19+
from cdp import CdpClient
20+
from dotenv import load_dotenv
21+
22+
load_dotenv()
23+
24+
25+
async def create_account(cdp: CdpClient):
26+
print("Creating account...")
27+
account = await cdp.solana.create_account()
28+
print(f"Successfully created account: {account.address}")
29+
return account.address
30+
31+
32+
async def request_faucet(cdp: CdpClient, address: str):
33+
print(f"Requesting SOL from faucet for {address}...")
34+
try:
35+
response = await cdp.solana.request_faucet(address=address, token="sol")
36+
transaction_signature = response.transaction_signature
37+
print(f"Successfully requested SOL from faucet: {transaction_signature}")
38+
return transaction_signature
39+
except Exception as e:
40+
print(f"Faucet request failed: {e}")
41+
if hasattr(e, "body"):
42+
print(f"Faucet error body: {e.body}")
43+
raise
44+
45+
46+
async def wait_for_balance(connection: SolanaClient, address: str):
47+
print("Waiting for faucet funds...")
48+
source_pubkey = PublicKey.from_string(address)
49+
50+
balance = 0
51+
max_attempts = 30
52+
attempts = 0
53+
54+
while balance == 0 and attempts < max_attempts:
55+
try:
56+
balance_resp = connection.get_balance(source_pubkey)
57+
balance = balance_resp.value
58+
if balance == 0:
59+
print("Waiting for faucet funds...")
60+
time.sleep(1)
61+
else:
62+
print(f"Account funded with {balance / 1e9} SOL ({balance} lamports)")
63+
return balance
64+
except Exception as e:
65+
print(f"Error checking balance: {e}")
66+
time.sleep(1)
67+
attempts += 1
68+
69+
if balance == 0:
70+
raise ValueError("Timed out waiting for faucet to fund account")
71+
72+
return balance
73+
74+
75+
async def send_transaction(
76+
cdp: CdpClient,
77+
sender_address: str,
78+
destination_address: str,
79+
amount: int = 1000,
80+
):
81+
connection = SolanaClient("https://api.devnet.solana.com")
82+
83+
source_pubkey = PublicKey.from_string(sender_address)
84+
dest_pubkey = PublicKey.from_string(destination_address)
85+
86+
print(
87+
f"Preparing to send {amount} lamports from {sender_address} to {destination_address}"
88+
)
89+
90+
transfer_params = TransferParams(
91+
from_pubkey=source_pubkey, to_pubkey=dest_pubkey, lamports=amount
92+
)
93+
transfer_instr = transfer(transfer_params)
94+
95+
# A more recent blockhash is set in the backend by CDP
96+
message = Message.new_with_blockhash(
97+
[transfer_instr],
98+
source_pubkey,
99+
Hash.from_string("SysvarRecentB1ockHashes11111111111111111111"),
100+
)
101+
102+
# Create a transaction envelope with signature space
103+
sig_count = bytes([1]) # 1 byte for signature count (1)
104+
empty_sig = bytes([0] * 64) # 64 bytes of zeros for the empty signature
105+
message_bytes = bytes(message) # Get the serialized message bytes
106+
107+
# Concatenate to form the transaction bytes
108+
tx_bytes = sig_count + empty_sig + message_bytes
109+
110+
# Encode to base64 used by CDP API
111+
serialized_tx = base64.b64encode(tx_bytes).decode("utf-8")
112+
113+
print("Sending transaction to network...")
114+
tx_resp = await cdp.solana.send_transaction(
115+
network="solana-devnet",
116+
transaction=serialized_tx,
117+
use_cdp_sponsor=True,
118+
)
119+
signature = tx_resp.transaction_signature
120+
print(f"Solana transaction hash: {signature}")
121+
122+
print("Confirming transaction...")
123+
confirmation = connection.confirm_transaction(
124+
Signature.from_string(signature), commitment="processed"
125+
)
126+
127+
if hasattr(confirmation, "err") and confirmation.err:
128+
raise ValueError(f"Transaction failed: {confirmation.err}")
129+
130+
print(
131+
f"Transaction confirmed: {'failed' if hasattr(confirmation, 'err') and confirmation.err else 'success'}"
132+
)
133+
print(
134+
f"Transaction explorer link: https://explorer.solana.com/tx/{signature}?cluster=devnet"
135+
)
136+
137+
return signature
138+
139+
140+
async def main():
141+
parser = argparse.ArgumentParser(description="Solana transfer script")
142+
parser.add_argument(
143+
"--sender",
144+
help="Sender address (if not provided, a new account will be created)",
145+
)
146+
parser.add_argument(
147+
"--destination",
148+
default="3KzDtddx4i53FBkvCzuDmRbaMozTZoJBb1TToWhz3JfE",
149+
help="Destination address",
150+
)
151+
parser.add_argument(
152+
"--amount",
153+
type=int,
154+
default=1000,
155+
help="Amount in lamports to send (default: 1000)",
156+
)
157+
args = parser.parse_args()
158+
159+
async with CdpClient() as cdp:
160+
connection = SolanaClient("https://api.devnet.solana.com")
161+
162+
try:
163+
sender_address = args.sender
164+
165+
# If no sender address is provided, create a new account and faucet it
166+
if not sender_address:
167+
print(
168+
"No sender address provided. Creating a new account and requesting funds..."
169+
)
170+
sender_address = await create_account(cdp)
171+
await request_faucet(cdp, sender_address)
172+
await wait_for_balance(connection, sender_address)
173+
else:
174+
print(f"Using provided sender address: {sender_address}")
175+
# Check if there's a balance
176+
source_pubkey = PublicKey.from_string(sender_address)
177+
balance_resp = connection.get_balance(source_pubkey)
178+
balance = balance_resp.value
179+
print(
180+
f"Sender account balance: {balance / 1e9} SOL ({balance} lamports)"
181+
)
182+
183+
if balance == 0:
184+
print("Account has zero balance, requesting funds from faucet...")
185+
await request_faucet(cdp, sender_address)
186+
await wait_for_balance(connection, sender_address)
187+
188+
await send_transaction(cdp, sender_address, args.destination, args.amount)
189+
190+
except Exception as error:
191+
print(f"Error in process: {error}")
192+
193+
194+
asyncio.run(main())

examples/python/solana/transactions/send_transaction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Usage: uv run python solana/transactions/send_transaction.py
2-
# uv run python solana/send_transaction.py
2+
# uv run python solana/transactions/send_transaction.py
33
# [--sender <sender_address>] - optional, if not provided, a new account will be created and funded from the faucet
44
# [--destination <destination_address>] - optional, if not provided, a default destination address will be used
55
# [--amount <amount_in_lamports>] - optional, if not provided, a default amount of 1000 lamports will be used

examples/python/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)